Gerenciando cenas na Unidade sem dor ou sofrimento

Você já teve que pensar em como tornar o gerenciamento de cena em seu projeto menos doloroso? Quando você tem um jogo bastante simples no qual existem apenas algumas cenas acontecendo uma após a outra, então, freqüentemente, tudo corre bem. Mas quando o número de cenas aumenta e as transições entre elas se tornam mais complicadas - elas podem ser carregadas em uma ordem diferente e o comportamento de algumas delas deve depender dos parâmetros de entrada - a tarefa se torna menos trivial.



Abaixo estão várias abordagens para resolvê-lo que tenho visto com mais frequência:



  • Arquivos - ao passar de uma cena para outra, todos os dados necessários são gravados em um arquivo JSON / XML e, quando a próxima cena é carregada, eles são lidos de volta. No mínimo, é lento (falando sobre leitura e gravação em um arquivo) e o processo de depuração se torna menos conveniente.
  • Uma enorme classe estática que lida com todas as transições de cena possíveis. Eles são muito semelhantes a objetos divinos e muitas vezes causam vazamentos de memória, bem como dores na parte inferior das costas quando um novo desenvolvedor tenta entender o que está acontecendo nessas mil linhas de código estático.
  • DontDestroyOnLoad GameObject - Esta abordagem é semelhante à anterior, mas o GameObject é apresentado em uma cena com vários links no Inspetor. Na verdade, este é um daqueles singletons que cada um de nós viu na maioria dos projetos ...


Quero mostrar uma abordagem que venho usando há anos. Ajuda a tornar as transições mais transparentes para o desenvolvedor, torna mais fácil entender onde e o que está acontecendo e também depurar.



Em todas as cenas que tenho SceneController. Ele é responsável por encaminhar todos os links necessários e inicializar objetos-chave. Em certo sentido, pode ser considerado o ponto de entrada da cena. Eu uso uma classe para representar argumentos, SceneArgse cada cena tem sua própria classe que representa seus argumentos e herda dela 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