Gérer des scènes dans Unity sans douleur ni souffrance

Avez-vous déjà eu à réfléchir à la manière de rendre la gestion de scène de votre projet moins pénible? Quand vous avez un jeu assez simple dans lequel il n'y a que quelques scènes qui se succèdent, alors, souvent, tout se passe bien. Mais lorsque le nombre de scènes augmente et que les transitions entre elles deviennent plus compliquées - elles peuvent être chargées dans un ordre différent et le comportement de certaines d'entre elles devrait dépendre des paramètres d'entrée - la tâche devient moins triviale.



Vous trouverez ci-dessous plusieurs approches pour le résoudre, que j'ai vues le plus souvent:



  • Fichiers - lors du passage d'une scène à une autre, toutes les données nécessaires sont écrites dans un fichier JSON / XML, et lorsque la scène suivante est chargée, elles sont lues. À tout le moins, il est lent (en parlant de lecture et d'écriture dans un fichier), et le processus de débogage devient moins pratique.
  • Une énorme classe statique qui gère toutes les transitions de scène possibles. Ils sont très similaires aux objets divins et provoquent assez souvent des fuites de mémoire, ainsi que des douleurs dans le bas du dos lorsqu'un nouveau développeur essaie de comprendre ce qui se passe dans ces mille lignes de code statique.
  • DontDestroyOnLoad GameObject - Cette approche est similaire à la précédente, mais le GameObject est présenté dans une scène avec un tas de liens dans l'inspecteur. En fait, c'est l'un de ces singletons que chacun de nous a vu dans la plupart des projets ...


Je veux vous montrer une approche que j'utilise depuis des années. Cela aide à rendre les transitions plus transparentes pour le développeur, il devient plus facile de comprendre où et ce qui se passe, ainsi que de déboguer.



Dans chaque scène que j'ai SceneController. Il est responsable de la transmission de tous les liens nécessaires et de l'initialisation des objets clés. En un sens, il peut être considéré comme le point d'entrée de la scène. J'utilise une classe pour représenter les arguments, SceneArgset chaque scène a sa propre classe qui représente ses arguments et en hérite 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