Verwalten von Szenen in Unity ohne Schmerzen oder Leiden

Mussten Sie jemals darüber nachdenken, wie Sie das Szenenmanagement in Ihrem Projekt weniger schmerzhaft gestalten können? Wenn Sie ein ziemlich einfaches Spiel haben, in dem nur wenige Szenen nacheinander ablaufen, läuft oft alles reibungslos. Wenn jedoch die Anzahl der Szenen zunimmt und die Übergänge zwischen ihnen komplizierter werden - sie können in einer anderen Reihenfolge geladen werden und das Verhalten einiger von ihnen sollte von den Eingabeparametern abhängen - wird die Aufgabe weniger trivial.



Im Folgenden sind einige Lösungsansätze aufgeführt, die ich am häufigsten gesehen habe:



  • Dateien - Beim Wechsel von einer Szene zur anderen werden alle erforderlichen Daten in eine JSON / XML-Datei geschrieben und beim Laden der nächsten Szene zurückgelesen. Zumindest ist es langsam (wenn man vom Lesen und Schreiben einer Datei spricht), und der Debugging-Prozess wird weniger bequem.
  • Eine riesige statische Klasse , die alle möglichen Szenenübergänge verarbeitet. Sie sind göttlichen Objekten sehr ähnlich und verursachen häufig Speicherlecks sowie Schmerzen im unteren Rückenbereich, wenn ein neuer Entwickler versucht zu verstehen, was in diesen tausend Zeilen statischen Codes vor sich geht.
  • DontDestroyOnLoad GameObject - Dieser Ansatz ähnelt dem vorherigen, aber das GameObject wird in einer Szene mit einer Reihe von Links im Inspector dargestellt. Tatsächlich ist dies eines dieser Singletons, die jeder von uns in den meisten Projekten gesehen hat ...


Ich möchte Ihnen einen Ansatz zeigen, den ich seit Jahren verwende. Es hilft, Übergänge für den Entwickler transparenter zu machen, es wird einfacher zu verstehen, wo und was passiert, und auch zu debuggen.



In jeder Szene, die ich habe SceneController. Es ist verantwortlich für die Weiterleitung aller erforderlichen Links und die Initialisierung von Schlüsselobjekten. In gewissem Sinne kann es als Einstiegspunkt der Szene betrachtet werden. Ich benutze eine Klasse, um Argumente darzustellen, SceneArgsund jede Szene hat ihre eigene Klasse, die ihre Argumente darstellt und von ihr erbt 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