How we reimagined working with scenes in Unity

Unity, as an engine, has a number of disadvantages, which, thanks to its customization capabilities and code generation tools, can be solved.

Now I will tell you about how we wrote a plugin for Unity based on post-processing projects and the CodeDom code generator.

Problem

In Unity, loading scenes is done through a string identifier. It is not stable, which means that it is easily changeable without obvious consequences. For example, when you rename a scene, everything will fly, but it will be revealed only at the very end at the stage of execution.

The problem shows up quickly in frequently used scenes, but can be difficult to detect when it comes to small additive scenes or scenes that are rarely used.

Decision

When adding a scene to a project, a class of the same name is generated with the Load method.

If we add a Menu scene, the Menu class will be generated in the project, and in the future we can start the scene as follows:

Menu.Load();

Yes, static method is not the best layout. But it seemed to me a laconic and convenient design. Generation occurs automatically, the source code of this class:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace IJunior.TypedScenes
{   
    public class Menu : TypedScene
    {
        private const string GUID = "a3ac3ba38209c7744b9e05301cbfa453";
        
        public static void Load()
        {
            LoadScene(GUID);
        }
    }
}

In an amicable way, the class should be static, since it is not supposed to be instantiated from it. This is a bug that we will fix. As you can see from this fragment, the code we do not cling to the name, but to the scene GUID, which is more reliable. The base class itself looks like this:

namespace IJunior.TypedScenes
{
    public abstract class TypedScene
    {
        protected static void LoadScene(string guid)
        {
            var path = AssetDatabase.GUIDToAssetPath(guid);
            SceneManager.LoadScene(path);
        }

        protected static void LoadScene<T>(string guid, T argument)
        {
            var path = AssetDatabase.GUIDToAssetPath(guid);

            UnityAction<Scene, Scene> handler = null;
            handler = (from, to) =>
            {
                if (to.name == Path.GetFileNameWithoutExtension(path))
                {
                    SceneManager.activeSceneChanged -= handler;
                    HandleSceneLoaders(argument);
                }
            };

            SceneManager.activeSceneChanged += handler;
            SceneManager.LoadScene(path);
        }

        private static void HandleSceneLoaders<T>(T loadingModel)
        {
            foreach (var rootObjects in SceneManager.GetActiveScene().GetRootGameObjects())
            {
                foreach (var handler in rootObjects.GetComponentsInChildren<ISceneLoadHandler<T>>())
                {
                    handler.OnSceneLoaded(loadingModel);
                }
            }
        }
    }
}

- .

- ( , ), .

, .

, Game , .

.

using IJunior.TypedScenes;
using System.Collections.Generic;
using UnityEngine;

public class GameLoadHandler : MonoBehaviour, ISceneLoadHandler<IEnumerable<Player>>
{
    public void OnSceneLoaded(IEnumerable<Player> players)
    {
        foreach (var player in players)
        {
            //make avatars
        }
    }
}

, .

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace IJunior.TypedScenes
{
    public class Game : TypedScene
    {
        private const string GUID = "976661b7057d74e41abb6eb799024ada";
        
        public static void Load(System.Collections.Generic.IEnumerable<Player> argument)
        {
            LoadScene(GUID, argument);
        }
    }
}

. .. N , N . - .

, , , .

N?

YouTube .

. , , , .

, ?

. :

public class GameArguments
{
    public IEnumerable<Player> Players { get; set; }
}

, , , .

, : , . , .

ID .

PlayerPerfs

. , PlayerPrefs . , , .

ASPNet

- View ASPNet Core. ViewData ViewModel. Unity - .

Unity , - , View ASPNet. Additive ( , , ), .

, , , , .

Proof-of-concept. , .

GitHub repository - https://github.com/HolyMonkey/unity-typed-scenes

If you're interested, I'll ask Vladislav in the next article to tell you how he worked with Code Dom in Unity and how to work with post-processing using the example of what we discussed today.




All Articles