Das gleichzeitige Arbeiten mit mehreren Szenen in Unity kann eine Herausforderung sein. Die Optimierung dieses Workflows hat enorme Auswirkungen auf die Leistung Ihres Spiels und die Produktivität Ihres Teams. Heute geben wir Ihnen Tipps zum Einrichten von Workflows mit Scene, die auf größere Projekte skaliert werden können.
Die meisten Spiele haben mehrere Ebenen und Ebenen enthalten oft mehr als eine Szene. In Spielen, in denen die Szenen relativ klein sind, können Sie sie mit Prefabs in verschiedene Teile aufteilen. Um sie jedoch während des Spiels zu verbinden oder zu instanziieren, müssen Sie auf alle diese Fertighäuser verweisen. Dies bedeutet, dass die Verwendung von Szenen effizienter wird, wenn Ihr Spiel größer wird und diese Links mehr Speicherplatz beanspruchen.
Sie können die Ebenen in eine oder mehrere Unity-Szenen aufteilen. Der beste Weg, um sie zu verwalten, wird zum entscheidenden Punkt. Sie können sofort im Editor mehr Szenen öffnen und zur Laufzeit der Verwendung von Multi-Szene Bearbeitung Funktion . Das Aufteilen von Ebenen in mehrere Szenen erleichtert auch die Teamarbeit, da Zusammenführungskonflikte in Tools für die Zusammenarbeit wie Git, SVN, Unity Collaborate und mehr vermieden werden.
Verwalten Sie mehrere Szenen, um ein Level zu erstellen
Im folgenden Video zeigen wir Ihnen, wie Sie ein Level effizienter laden können, indem Sie die Spielelogik und verschiedene Teile des Levels in mehrere separate Unity-Szenen aufteilen. Dann werden unter Verwendung des Zusatzstoff Szene-Lademodus , wenn diese Szenen laden, laden wir und entladen die notwendigen Teile zusammen mit der Spiellogik , die nicht überall gehen. Wir verwenden Fertighäuser als Anker für Szenen, was auch bei der Arbeit im Team viel Flexibilität bietet, da jede Szene Teil des Levels ist und separat bearbeitet werden kann.
Sie können diese Szenen weiterhin im Bearbeitungsmodus laden und jederzeit auf Wiedergabe drücken, um sie alle zusammen zu rendern, während Sie am Level-Design arbeiten.
Wir werden zwei verschiedene Methoden zum Laden dieser Szenen zeigen. Die erste basiert auf der Distanz, die sich gut für nicht-innere Ebenen wie die offene Welt eignet. Diese Technik ist auch für einige visuelle Effekte (wie Nebel) nützlich, um den Lade- und Entladevorgang zu verbergen.
Die zweite Methode verwendet einen Trigger , um zu überprüfen, welche Szenen geladen werden müssen. Dies ist effizienter bei der Arbeit mit Innenräumen.
Nachdem wir alles innerhalb des Levels herausgefunden haben, können wir eine zusätzliche Ebene hinzufügen, um die Level selbst besser zu verwalten.
Steuern mehrerer Spielebenen mit ScriptableObjects
Wir möchten die verschiedenen Szenen in jedem Level sowie alle Levels während des gesamten Spiels verfolgen. Eine Möglichkeit, dies zu erreichen, besteht darin, statische Variablen und Singletones in MonoBehaviour-Skripten zu verwenden. Diese Lösung ist jedoch nicht so reibungslos. Die Verwendung eines Singletons impliziert enge Verbindungen zwischen Ihren Systemen, daher ist es nicht streng modular. Systeme können nicht separat existieren und sind immer voneinander abhängig.
Ein weiteres Problem betrifft die Verwendung statischer Variablen. Da Sie sie im Inspektor nicht sehen können, müssen Sie sie durch Code definieren, was es Künstlern oder Leveldesignern erschwert, das Spiel zu testen. Wenn Sie Daten für verschiedene Szenen gemeinsam nutzen möchten, verwenden Sie statische Variablen in Verbindung mit DontDestroyOnLoad. Letztere sollten jedoch nach Möglichkeit vermieden werden.
Zum Speichern von Informationen zu verschiedenen Szenen können Sie ScriptableObject verwenden , eine serialisierbare Klasse, die hauptsächlich zum Speichern von Daten verwendet wird. Im Gegensatz zu MonoBehaviour-Skripten, die als an GameObjects gebundene Komponenten verwendet werden, sind ScriptableObjects an kein GameObject gebunden und können daher von verschiedenen Szenen im gesamten Projekt verwendet werden.
Es wäre schön, diese Struktur sowohl für Levels als auch für Menüszenen in Ihrem Spiel verwenden zu können. Erstellen Sie dazu eine GameScene-Klasse, die verschiedene allgemeine Eigenschaften für Ebenen und Menüs enthält.
public class GameScene : ScriptableObject
{
[Header("Information")]
public string sceneName;
public string shortDescription;
[Header("Sounds")]
public AudioClip music;
[Range(0.0f, 1.0f)]
public float musicVolume;
[Header("Visuals")]
public PostProcessProfile postprocess;
}
Beachten Sie, dass die Klasse von ScriptableObject und nicht von MonoBehaviour erbt. Sie können so viele Eigenschaften hinzufügen, wie für Ihr Spiel benötigt werden. Nach diesem Schritt können Sie die Level- und Menüklassen erstellen, die von der soeben erstellten GameScene-Klasse erben, sodass sie auch ScriptableObjects sind.
[CreateAssetMenu(fileName = "NewLevel", menuName = "Scene Data/Level")]
public class Level : GameScene
{
// ,
[Header("Level specific")]
public int enemiesCount;
}
Durch Hinzufügen des CreateAssetMenu- Attributs oben können Sie eine neue Ebene über das Menü "Assets" in Unity erstellen. Sie können dasselbe für die Menüklasse tun. Sie können auch eine Aufzählung hinzufügen, um den Menütyp im Inspektor auswählen zu können.
public enum Type
{
Main_Menu,
Pause_Menu
}
[CreateAssetMenu(fileName = "NewMenu", menuName = "Scene Data/Menu")]
public class Menu : GameScene
{
// ,
[Header("Menu specific")]
public Type type;
}
Nachdem Sie nun Ebenen und Menüs erstellen können, fügen wir der Einfachheit halber eine Datenbank hinzu, in der diese (Ebenen und Menüs) aufgelistet sind. Sie können auch einen Index hinzufügen, um den aktuellen Level des Spielers zu verfolgen. Sie können dann Methoden hinzufügen, um ein neues Spiel zu laden (in diesem Fall wird das erste Level geladen), das aktuelle Level zu wiederholen und zum nächsten Level zu gelangen. Beachten Sie, dass in diesen drei Methoden nur der Index geändert wird, sodass Sie eine Methode erstellen können, die die Ebene nach Index lädt, um sie wiederzuverwenden.
[CreateAssetMenu(fileName = "sceneDB", menuName = "Scene Data/Database")]
public class ScenesData : ScriptableObject
{
public List<Level> levels = new List<Level>();
public List<Menu> menus = new List<Menu>();
public int CurrentLevelIndex=1;
/*
*
*/
//
public void LoadLevelWithIndex(int index)
{
if (index <= levels.Count)
{
//
SceneManager.LoadSceneAsync("Gameplay" + index.ToString());
//
SceneManager.LoadSceneAsync("Level" + index.ToString() + "Part1", LoadSceneMode.Additive);
}
// ,
else CurrentLevelIndex =1;
}
//
public void NextLevel()
{
CurrentLevelIndex++;
LoadLevelWithIndex(CurrentLevelIndex);
}
//
public void RestartLevel()
{
LoadLevelWithIndex(CurrentLevelIndex);
}
// ,
public void NewGame()
{
LoadLevelWithIndex(1);
}
/*
*
*/
//
public void LoadMainMenu()
{
SceneManager.LoadSceneAsync(menus[(int)Type.Main_Menu].sceneName);
}
//
public void LoadPauseMenu()
{
SceneManager.LoadSceneAsync(menus[(int)Type.Pause_Menu].sceneName);
}
Es gibt auch Menümethoden, und Sie können den zuvor erstellten Aufzählungstyp verwenden, um das gewünschte Menü zu laden. Stellen Sie lediglich sicher, dass die Reihenfolge in der Aufzählung und die Reihenfolge in der Menüliste identisch sind.
Schließlich können Sie jetzt eine Datenbankebene, ein Menü oder ein ScriptableObject aus dem Menü "Assets" erstellen, indem Sie mit der rechten Maustaste in das Projektfenster klicken.
Fügen Sie von dort aus einfach die gewünschten Ebenen und Menüs hinzu, passen Sie die Parameter an und fügen Sie sie dann der Szenendatenbank hinzu. Das folgende Beispiel zeigt, wie die Daten von Level1, MainMenu und Scenes aussehen.
Es ist Zeit, diese Methoden aufzurufen. In diesem Beispiel ruft die Schaltfläche "Nächste Ebene" in der Benutzeroberfläche, die angezeigt wird, wenn der Spieler das Ende der Ebene erreicht, die NextLevel-Methode auf. Um eine Methode an eine Schaltfläche zu binden, klicken Sie auf die Schaltfläche mit dem Ereignis On Click plus der Button-Komponente, um ein neues Ereignis hinzuzufügen. Ziehen Sie dann das Scene Data ScriptableObject in das Objektfeld und wählen Sie die NextLevel-Methode aus ScenesData aus (siehe Abbildung unten).
Jetzt können Sie den gleichen Vorgang für andere Schaltflächen ausführen - spielen Sie den Pegel erneut ab oder gehen Sie zum Hauptmenü und so weiter. Sie können auch von jedem anderen Skript aus auf ScriptableObject verweisen, um auf verschiedene Eigenschaften wie AudioClip für Hintergrundmusik oder Nachbearbeitungsprofile zuzugreifen und diese auf der Ebene zu verwenden.
Tipps zur Minimierung von Fehlern in Ihren Prozessen
Minimieren
des Ladens / Entladens Im im Video gezeigten ScenePartLoader-Skript können Sie sehen, dass der Player mehrmals in den Collider ein- und aussteigen kann, wodurch die Szene neu geladen und entladen wird. Um dies zu vermeiden, können Sie eine Coroutine hinzufügen, bevor Sie die Methoden zum Laden und Entladen der Szene im Skript aufrufen, und die Coroutine stoppen, wenn der Player den Auslöser verlässt.
Regeln der Namensgebung
Ein weiterer globaler Tipp ist die Verwendung strenger Namenskonventionen in Ihrem Projekt. Das Team sollte im Voraus vereinbaren, wie die verschiedenen Arten von Assets benannt werden sollen, von Skripten und Szenen bis hin zu Materialien und anderen Dingen im Projekt. Dies erleichtert die Arbeit an dem Projekt und unterstützt es nicht nur für Sie, sondern auch für Ihre Teamkollegen. Es ist immer eine gute Idee, aber in diesem speziellen Fall ist es sehr wichtig, Szenen mit ScriptableObjects zu verwalten. In unserem Beispiel wurde ein einfacher Ansatz verwendet, der auf Szenennamen basiert. Es gibt jedoch viele verschiedene Lösungen, die weniger vom Szenennamen abhängen. Sie sollten einen stringbasierten Ansatz vermeiden, da diese Szene beim Umbenennen einer Unity-Szene in diesem Kontext nicht an anderer Stelle im Spiel geladen wird.
Spezialwerkzeug
Eine Möglichkeit, sich während des Spiels nicht auf Namen zu verlassen, besteht darin, Ihr Skript so zu konfigurieren, dass Szenen vom Typ Objekt sind . Auf diese Weise können Sie eine Szenenressource per Drag & Drop in den Inspektor ziehen und ihren Namen dann leise im Skript abrufen. Da es sich jedoch um eine Editor-Klasse handelt, haben Sie zur Laufzeit keinen Zugriff auf die AssetDatabase- Klasse. Daher müssen Sie beide Daten für eine Lösung kombinieren, die im Editor funktioniert, menschliches Versagen verhindert und zur Laufzeit weiterhin funktioniert. In der ISerializationCallbackReceiver- Schnittstelle finden Sie ein Beispiel für die Implementierung eines Objekts, das nach der Serialisierung den Zeichenfolgenpfad aus dem Scene-Asset abrufen und zur Laufzeit speichern kann.
Alternativ können Sie auch einen eigenen Inspektor erstellen, um das schnelle Hinzufügen von Szenen zu Build-Einstellungen mithilfe von Schaltflächen zu vereinfachen , anstatt sie manuell über dieses Menü hinzuzufügen und synchron zu halten.
Ein Beispiel für diese Art von Tool finden Sie in dieser fantastischen Open Source-Implementierung des Entwicklers JohannesMP (es ist keine offizielle Unity-Ressource).
Lassen Sie uns wissen, was Sie denken
Dieser Beitrag zeigt nur eine Möglichkeit, wie ScriptableObjects Ihren Workflow verbessern kann, wenn Sie mit mehreren Szenen in Kombination mit Fertighäusern arbeiten. Verschiedene Spiele verwenden völlig unterschiedliche Methoden zur Steuerung von Szenen - keine einzige Lösung passt auf alle Spielstrukturen gleichzeitig. Es ist sinnvoll, eigene Tools zu implementieren, die zu Ihrer Projektorganisation passen.
Wir hoffen, dass diese Informationen Ihnen bei Ihrem Projekt helfen oder Sie vielleicht dazu inspirieren, Ihre eigenen Tools für das Szenenmanagement zu erstellen.
Lassen Sie uns in den Kommentaren wissen, wenn Sie Fragen haben. Wir würden gerne hören, mit welchen Techniken Sie Szenen in Ihrem Spiel manipulieren. Sie können auch andere Anwendungsfälle vorschlagen, die Sie in zukünftigen Beiträgen zur Prüfung vorschlagen möchten.