Auf dieser Seite wird erklärt, wie man ScriptableObjects als Logikcontainer verwendet. Auf diese Weise können Sie sie als Delegate-Objekte behandeln, also als kleine Bündel von Aktionen, die Sie bei Bedarf aufrufen können.
Dies ist der vierte Teil einer Serie von sechs Mini-Handbüchern, die Unity-Entwickler mit der zum E-Book gehörenden Demo unterstützen sollen, Erstellen einer modularen Spielarchitektur in Unity mit ScriptableObjects.
Die Demo ist von der Mechanik klassischer Arcade-Spiele inspiriert und zeigt, wie Sie mit ScriptableObjects Komponenten erstellen können, die testbar, skalierbar und designfreundlich sind.
Das E-Book, das Demoprojekt und diese Mini-Guides bieten zusammen die besten Praktiken für die Verwendung von Programmierdesignmustern mit der ScriptableObject-Klasse in Ihrem Unity-Projekt. Mit diesen Tipps können Sie Ihren Code vereinfachen, die Speichernutzung reduzieren und die Wiederverwendbarkeit von Code fördern.
Diese Reihe umfasst die folgenden Artikel:
Bevor Sie sich in das ScriptableObject-Demoprojekt und diese Reihe von Mini-Leitfäden vertiefen, sollten Sie daran denken, dass Entwurfsmuster im Grunde nur Ideen sind. Sie werden nicht auf jede Situation zutreffen. Diese Techniken können Ihnen helfen, neue Wege für die Arbeit mit Unity und ScriptableObjects zu finden.
Jedes Muster hat Vor- und Nachteile. Wählen Sie nur diejenigen aus, die für Ihr spezifisches Projekt von Bedeutung sind. Sind Ihre Designer stark auf den Unity-Editor angewiesen? Ein ScriptableObject-basiertes Muster könnte eine gute Wahl sein, um die Zusammenarbeit mit Ihren Entwicklern zu erleichtern.
Letztlich ist die beste Code-Architektur diejenige, die zu Ihrem Projekt und Ihrem Team passt.
Mithilfe des Strategiemusters können Sie eine Schnittstelle oder eine ScriptableObject-Basisklasse definieren und diese Delegatenobjekte dann zur Laufzeit austauschbar machen.
Eine Anwendung besteht darin, Algorithmen für bestimmte Aufgaben in einem ScriptableObject zu kapseln und dieses ScriptableObject dann im Zusammenhang mit etwas anderem zu verwenden.
Wenn Sie zum Beispiel ein KI- oder Pfadfindungssystem für eine EnemyUnit-Klasse schreiben, könnten Sie ein ScriptableObject mit einer Pfadsuchtechnik (wie A*, Dijkstra usw.) erstellen.
Die EnemyUnit selbst würde eigentlich keine Pfadfindungslogik enthalten. Stattdessen würde es einen Verweis auf ein separates "Strategie"-SkriptableObject behalten. Der Vorteil dieses Konzepts ist, dass man einfach durch den Austausch von Objekten auf einen anderen Algorithmus umschalten kann. Dies ist eine Möglichkeit, verschiedene Verhaltensweisen zur Laufzeit zu wählen.
Wenn das MonoBehaviour eine Aufgabe ausführen muss, ruft es die externen Methoden des ScriptableObjects auf und nicht seine eigenen. Das ScriptableObject könnte zum Beispiel öffentliche Methoden für MoveUnit oder SetTarget enthalten, um die gegnerische Einheit zu steuern und ein Ziel anzugeben.
Sie können dieses Muster mit einer abstrakten Basisklasse oder einer Schnittstelle verbessern. Das bedeutet, dass jedes ScriptableObject, das die Strategie implementiert, mit einem anderen ausgetauscht werden kann. Dieses im laufenden Betrieb austauschbare ScriptableObject wird in das MonoBehaviour, das es referenziert, "eingesteckt" - sogar während der Laufzeit.
Wenn die EnemyUnit ihr Verhalten aufgrund von Spielbedingungen ändern muss, kann der äußere Kontext (das MonoBehaviour) auf diese Bedingungen prüfen. Dann kann es ein anderes ScriptableObject als Antwort einfügen.
Indem Sie die Implementierungsdetails in ein ScriptableObject auslagern, können Sie auch eine bessere Aufteilung der Zuständigkeiten innerhalb Ihres Teams erreichen. Ein Entwickler könnte sich auf den Algorithmus innerhalb des ScriptableObjects konzentrieren, während ein anderer an dem MonoBehaviour-Kontext arbeitet.
Um dieses steckbare Verhalten zu erzeugen, stellen Sie sicher, dass:
- Definieren Sie eine Basisklasse oder Schnittstelle für die Strategie: Diese Klasse oder Schnittstelle sollte die für die Ausführung der Strategie erforderlichen Methoden und Eigenschaften enthalten.
- Erstellen Sie die ScriptableObject-Klassen: Sie können unterschiedliche Umsetzungen der Strategie bieten. Sie könnten zum Beispiel eine Klasse erstellen, die einen einfachen KI-Algorithmus implementiert, und eine andere Klasse, die einen komplexeren Algorithmus implementiert.
- Erstellen Sie ein ScriptableObject, das die Strategie implementiert: Ergänzen Sie die fehlende Logik und füllen Sie den Inspector mit allen erforderlichen Werten.
- Verwenden Sie die Strategie im Kontext: Rufen Sie im MonoBehaviour die im ScriptableObject implementierten Methoden und Eigenschaften auf. Um den Aufruf dieser Methoden zu erleichtern, geben Sie die Abhängigkeiten als Parameter an.
Wenn Sie Ihren Code auf diese Weise organisieren, ist es einfacher, zwischen verschiedenen Implementierungen derselben Strategie zu wechseln. Dieses steckbare Verhalten erleichtert dann die Fehlersuche und Wartung.
Ein Algorithmus oder eine Strategie muss nicht kompliziert sein. Das PaddleBallSO-Projekt demonstriert zum Beispiel ein recht einfaches Audiowiedergabesystem im SimpleAudioDelegate.
Die abstrakte Klasse AudioDelegateSO definiert eine einzelne Play-Methode, die einen AudioSource-Parameter akzeptiert. Die konkrete Implementierung setzt dies dann außer Kraft.
Die Unterklasse SimpleAudioDelegateSO definiert ein Array von AudioClips. Sie wählt einen zufälligen Clip aus und gibt ihn mit der überschriebenen Play-Methode wieder. Dadurch wird eine Variation der Tonhöhe und Lautstärke innerhalb eines benutzerdefinierten Bereichs hinzugefügt.
Obwohl es sich nur um ein paar Zeilen handelt, können Sie mit dem folgenden Codeschnipsel viele verschiedene Audioeffekte erzeugen.
Dieses spezielle Beispiel ist zwar nicht wirklich für eine intensive Nutzung von Audio geeignet, aber es wird hier als grundlegendes Beispiel für die Nutzung von ScriptableObjects in einem Strategiemuster vorgestellt.
Ein Designer kann viele verschiedene ScriptableObjects erstellen, um Soundeffekte darzustellen, ohne den Code zu berühren. Auch hier ist nur minimale Unterstützung durch einen Entwickler erforderlich, sobald das Basis-ScriptableObject fertig ist.
In PaddleBallSO kann nun jeder eine neue Reihe von Geräuschen einrichten, die abgespielt werden, wenn der Ball eine der Levelwände trifft. Die Designer gewinnen an kreativer Unabhängigkeit und Flexibilität, da sie vollständig im Editor arbeiten. Durch diesen Ansatz werden Programmierressourcen freigesetzt, da die Entwickler nicht mehr bei jeder Designentscheidung mitwirken müssen.
Sie können das Audiobeispiel auch in der Musterdemo sehen. Jeder Sound leitet sich von einem etwas anderen SimpleAudioDelegateSO-Asset ab, mit kleinen Abweichungen zwischen den Instanzen.
In diesem Beispiel enthält jede Ecke eine AudioSource. Ein benutzerdefinierter AudioModifier MonoBehaviour verwendet einen ScriptableObject-basierten Delegaten zur Tonwiedergabe.
Die Unterschiede in der Tonhöhe ergeben sich nur aus den Einstellungen der einzelnen ScriptableObject-Assets (BeepHighPitched_SO, BeepLowPitched_SO usw.).
Die Verwendung eines ScriptableObjects zur Steuerung der Aktionslogik kann es Ihrem Designteam erleichtern, mit Ideen zu experimentieren. Dadurch kann ein Designer unabhängiger von einem Entwickler arbeiten.
Das PaddleBallSO-Projekt verwendet das Strategiemuster auch in seinem Zielsystem. Obwohl dies nicht etwas ist, das zur Laufzeit variieren muss, bietet die Kapselung jedes Objekts in einem ScriptableObject eine flexible Möglichkeit, Win-Lose-Bedingungen zu testen.
Die abstrakte Basisklasse ObjectiveSO enthält Werte wie den Namen des Ziels und die Angabe, ob es abgeschlossen wurde.
Die konkreten Unterklassen, wie z. B. ScoreObjectiveSO, implementieren dann die eigentliche Logik, wie die einzelnen Ziele zu erreichen sind. Dazu wird die CompleteObjective-Methode von ObjectiveSO außer Kraft gesetzt und die Logik der Gewinnbedingung hinzugefügt.
Muss der Spieler eine bestimmte Punktzahl erreichen oder eine bestimmte Anzahl von Gegnern besiegen? Müssen sie einen bestimmten Ort erreichen oder einen bestimmten Gegenstand abholen? Dies sind übliche Siegbedingungen, die zu skriptfähigen Objektzielen werden könnten.
Der ObjectiveManager dient als größerer Kontext für die ScriptableObjects. Es verwaltet eine Liste von ObjectiveSOs und überprüft anhand der einzelnen ScriptableObjects, ob sie vollständig sind. Das Spiel ist zu Ende, wenn jedes ObjectiveSO einen Status der Fertigstellung anzeigt.
Die ScoreObjectiveSO zeigt zum Beispiel eine Möglichkeit, ein Scoring Objective zu implementieren:
- Eine benutzerdefinierte PlayerScore-Struktur stimmt mit der Spieler-ID, einem UI-Element in der Schnittstelle und dem tatsächlichen Punktestandwert überein.
- Jedes Mal, wenn die ScoreManager-Komponente aktualisiert wird, überprüft das Ziel die Siegbedingung.
- Wenn die Punktzahl des Spielers den Wert m_TargetScore erreicht oder überschreitet, wird das PlayerScore-Objekt als Ereignis gesendet.
Der ObjectiveManager kümmert sich nur darum, dass alle angegebenen Ziele abgeschlossen sind. Sie ist sich der Details innerhalb der einzelnen Ziele nicht bewusst.
Auch hier ist das Ziel die Modularität. Auf diese Weise können Sie jedes ObjectiveSO individuell anpassen, ohne die bereits vorhandenen zu beeinträchtigen.
Das PaddleBallSO-Spiel hat eigentlich nur ein Ziel. Erreicht einer der Spieler das Siegpunktziel, endet das Spiel.
Sie können dieses System jedoch erweitern oder Ziele kombinieren, um ein komplexeres Zielsystem zu schaffen. Experimentieren Sie und schauen Sie, ob Sie neue Spielmodi entwickeln können (z. B. eine Mindestpunktzahl erreichen, bevor die Zeit abläuft).
Da die Logik in einem ScriptableObject gekapselt ist, können Sie jedes ObjectiveSO gegen ein anderes austauschen. Um eine neue Siegbedingung zu erstellen, muss lediglich die Liste im ObjectiveManager neu konfiguriert werden. Das Ziel ist in gewisser Weise in den umgebenden Kontext "einsteckbar".
Ein praktischer Aspekt der ObjectiveSO ist das Ereignis, das zum Senden von Nachrichten zwischen GameObjects verwendet wird. Als Nächstes werden wir untersuchen, wie diese ereignisgesteuerte Architektur mit ScriptableObjects umgesetzt werden kann.
Lesen Sie mehr über Entwurfsmuster mit ScriptableObjects im E-Book Modulare Spielarchitektur in Unity mit ScriptableObjects erstellen. Weitere Informationen über gängige Entwurfsmuster für die Unity-Entwicklung finden Sie auch unter Verbessern Sie Ihren Code mit Programmiermustern für Spiele.