Gestionar escenas en Unity sin dolor ni sufrimiento

¿Alguna vez ha tenido que pensar en cómo hacer que la gestión de escenas en su proyecto sea menos dolorosa? Cuando tienes un juego bastante simple en el que solo hay unas pocas escenas una tras otra, entonces, a menudo, todo va bien. Pero cuando el número de escenas aumenta y las transiciones entre ellas se vuelven más complicadas (se pueden cargar en un orden diferente y el comportamiento de algunas de ellas debería depender de los parámetros de entrada) la tarea se vuelve menos trivial.



A continuación se muestran varios enfoques para resolverlo que he visto con mayor frecuencia:



  • Archivos : al pasar de una escena a otra, todos los datos necesarios se escriben en un archivo JSON / XML y, cuando se carga la siguiente escena, se vuelven a leer. Como mínimo, es lento (hablando de leer y escribir en un archivo) y el proceso de depuración se vuelve menos conveniente.
  • Una clase estática enorme que maneja todas las posibles transiciones de escena. Son muy similares a los objetos divinos y muy a menudo provocan pérdidas de memoria, así como dolor en la zona lumbar cuando un nuevo desarrollador intenta entender lo que sucede en estas mil líneas de código estático.
  • DontDestroyOnLoad GameObject: este enfoque es similar al anterior, pero el GameObject se presenta en una escena con un montón de enlaces en el Inspector. De hecho, este es uno de esos singletons que cada uno de nosotros ha visto en la mayoría de proyectos ...


Quiero mostrarles un enfoque que he estado usando durante años. Ayuda a que las transiciones sean más transparentes para el desarrollador, resulta más fácil comprender dónde y qué está sucediendo, y también depurar.



En cada escena que tengo SceneController. Él es responsable de reenviar todos los enlaces necesarios e inicializar los objetos clave. En cierto sentido, puede considerarse el punto de entrada a la escena. Utilizo una clase para representar argumentos, SceneArgsy cada escena tiene su propia clase que representa sus argumentos y hereda de ella SceneArgs.



public abstract class SceneArgs
{
    public bool IsNull { get; private set; }
}


, , SceneController.



public abstract class SceneController<TController, TArgs> : MonoBehaviour
        where TController : SceneController<TController, TArgs>
        where TArgs       : SceneArgs, new()
{
    protected TArgs Args { get; private set; }

    private void Awake()
    {
        Args = SceneManager.GetArgs<Tcontroller, TArgs>();

        OnAwake();
    }

    protected virtual void OnAwake() {}
}


. , params object[] args. . , . , , — , , ( ) , , . , IDE , . params object[] args , , , . ( ), . where, SceneController.



, name buildIndex , LoadScene() LoadSceneAsync() Unity API. , SceneControllerAttribute, . , buildIndex , , , .



[AttributeUsage(AttributeTargets.Class)]
public sealed class SceneControllerAttribute : Attribute
{
    public string SceneName { get; private set; }

    public SceneControllerAttribute(string name)
    {
        SceneName = name;
    }
}


, MainMenu. , :



public sealed class MainMenuArgs : SceneArgs
{
    // args' properties
}



[SceneControllerAttribute]
public sealed class MainMenuController : SceneController<MainMenuController, MainMenuArgs>
{
    protected override void OnAwake()
    {
        // scene initialization
    }
}


, ( , ). , . SceneManager. , , . . — . .



public static class SceneManager
{
    private static readonly Dictionary<Type,  SceneArgs> args;

    static SceneManager()
    {
        args = new Dictionary<Type,  SceneArgs>();
    }

    private static T GetAttribute<T>(Type type) where T : Attribute
    {
        object[] attributes = type.GetCustomAttributes(true);

        foreach (object attribute in attributes)
            if (attribute is T targetAttribute)
                return targetAttribute;

        return null;
    }

    public static AsyncOperation OpenSceneWithArgs<TController, TArgs>(TArgs sceneArgs)
        where TController   : SceneController<TController, TArgs>
        where TArgs         :  SceneArgs, new()
    {
        Type                     type       = typeof(TController);
        SceneControllerAttribute attribute  = GetAttribute<SceneControllerAttribute>(type);

        if (attribute == null)
            throw new NullReferenceException($"You're trying to load scene controller without {nameof(SceneControllerAttribute)}");

        string sceneName = attribute.SceneName;

        if (sceneArgs == null)
            args.Add(type, new TArgs { IsNull = true });

        return UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(sceneName);
    }

    public static TArgs GetArgs<TController, TArgs>()
        where TController   : SceneController<TController, TArgs>
        where TArgs         :  SceneArgs, new()
    {
        Type type = typeof(TController);

        if (!args.ContainsKey(type) || args[type] == null)
            return new TArgs { IsNull = true };

        TArgs sceneArgs = (TArgs)args[type];

        args.Remove(type);

        return sceneArgs;
    }
}


. OpenSceneWithArgs() (TController) , , (TArgs) , , (sceneArgs). , SceneManager , TController SceneControllerAttribute. , , TController. sceneArgs . - , TArgs IsNull true. , Unity API LoadSceneAsyn() , SceneControllerAttribute.



Awake(). , SceneController, TController SceneManager.GetArgs(), , , .



, SceneManager, . , . . !




All Articles