Auf dieser Seite wird erklärt, wie Sie ScriptableObject-basierte Enums in Ihrem Unity-Projekt verwenden können.
Dies ist der dritte 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.
Letztendlich ist die beste Code-Architektur diejenige, die zu Ihrem Projekt und Ihrem Team passt.
Enums sind ein praktischer Weg, um einen festen Satz von benannten Werten in Ihrem Code zu verwalten. Sie sind jedoch mit einigen Einschränkungen verbunden. Da serialisierte Enum-Werte als Ganzzahlen und nicht als symbolische Namen gespeichert werden, kann das Entfernen oder Umordnen eines Wertes zu einem falschen oder unerwarteten Verhalten führen. Das bedeutet, dass Enums, besonders wenn man viele davon hat, bei der Unity-Entwicklung Kopfschmerzen bereiten können.
Der Standardansatz
So sieht eine typische Aufzählung aus:
[System.Serializable]
public enum HandGestures
{
Felsen,
Papier,
Schere
}
Sie können ein Enum mit dem System.Serializable-Attribut serialisieren und es erscheint im Inspector.
Das Problem
Das Umordnen oder Löschen eines Wertes kann zu Problemen führen. Da jeder Wert intern eine ganze Zahl ist, kann das, was er darstellt, etwas anderes werden. Im gegebenen Beispiel würde das Löschen des Wertes Papier dazu führen, dass die Schere den Wert 1 annimmt.
Oder, wenn wir einen Wert wie im folgenden Beispiel hinzufügen.
Der ausgewählte Aufzählungswert würde sich ändern, wenn er nach dem gelöschten Eintrag kommt.
Dies kann zu Problemen bei der Pflege und Aktualisierung von Projekten führen, insbesondere wenn Ihre Aufzählung zahlreiche Werte enthält. Sie können dieses Problem entschärfen, indem Sie ein leeres oder unbenutztes Element lassen oder explizit ganzzahlige Werte festlegen. Beide Lösungen sind jedoch nicht ideal.
SkriptableObject-basierte Enums bieten Ihnen die Funktionalität traditioneller Enums, werden aber als einzelne Assets gespeichert. Sehen Sie sich zum Beispiel das PlayerIDSO ScriptableObject im PaddleBallSO-Projekt im folgenden Beispiel an.
Im Wesentlichen ist dies ein leeres ScriptableObject.
Damit können Sie eine Reihe von ScriptableObject-Assets im Projekt erstellen, z. B. P1, P2, usw. Auch wenn sie keine Daten enthalten, können Sie ScriptableObjects zum Vergleich verwenden. Erstellen Sie einfach ein neues ScriptableObject-Asset im Projekt und geben Sie ihm einen Namen.
Sie können innerhalb des Projekts so viele Player-IDs erstellen, wie Sie benötigen, und problemlos zwischen ihnen wechseln. Ändern Sie einfach das zugewiesene Asset im GameDataSO-Skript.
Wenn Sie auf Gleichheit prüfen, funktioniert dies ähnlich wie bei einer Aufzählung. Beziehen sich zwei Variablen auf das gleiche ScriptableObject? Wenn ja, handelt es sich um denselben Gegenstandstyp. Ansonsten sind sie es nicht.
Auch ohne zusätzliche Daten stellt das ScriptableObject eine Kategorie oder einen Elementtyp dar.
In PaddleBallSO wird die PlayerIDSO zu einer Teambezeichnung. Zur Unterscheidung zwischen den beiden Paddles verwenden wir in der GameDataSO die Assets P1 und P2.
Das GameSetup-Skript weist jedem Paddel eine Spieler-ID zu. Während des Spiels vergleichen die Paddle-Skripte die Spielereingaben mit der angegebenen Team-ID.
Dies gilt für alle Arten von Multiplayer-Spielen. Alternativ können Sie sie auch überall dort einsetzen, wo Sie nach einer Aufzählung greifen.
Da es sich bei ScriptableObjects lediglich um Zuweisungen im Inspektor handelt, gelten für sie nicht die gleichen Probleme bei der Umbenennung und Neuordnung.
Möchten Sie die Namen der Bezeichner in "Player1" bzw. "Player2" ändern? Sie können das tun, und alles funktioniert weiter. Weitere ScriptableObjects hinzufügen? Kein Problem - die Asset-Zuordnung im Inspektor bleibt erhalten.
Dieses Verhalten ist nützlich für die Gestaltung des Spielverlaufs. In der Muster-Demo klicken Sie auf die Schaltfläche Enum wechseln, um die Teams zu wechseln. Ein MonoBehaviour auf dem DemoBall aktualisiert den SpriteRenderer entsprechend.
Verursacht der Ball beim Aufprall auf einen Block Schaden? Finden Sie es heraus, indem Sie einen schnellen Test der Gleichheit durchführen. Das folgende Codebeispiel zeigt eine Möglichkeit, sie zu vergleichen.
Diese Methode kann feststellen, ob zwei GameObjects im selben Team sind, was bei der Überprüfung von Freund-Feind-Interaktionen nützlich ist. Dieser einfache Vergleich lässt sich auf die Aufnahme von Gegenständen, Schaden oder alles andere anwenden, was ein "Team" oder eine "Ausrichtung" hat.
Am meisten Spaß macht es, wenn Sie Ihren ScriptableObjects Logik hinzufügen. Im Gegensatz zu einer herkömmlichen Aufzählung kann ein ScriptableObject nicht nur Daten enthalten, sondern auch Felder und Methoden.
Verwenden Sie diese, damit jedes ScriptableObject eine spezielle Vergleichslogik haben kann. Sie könnten zum Beispiel ein ScriptableObject haben, das spezielle Schadenseffekte definiert (z.B. Kälte, Hitze, Elektrizität, Magie usw.).
Wenn Ihre Anwendung ein Inventarsystem zum Ausrüsten von Spielgegenständen benötigt, können ScriptableObjects Gegenstandstypen oder Waffenslots darstellen. Dürfen bestimmte Charaktere bestimmte Gegenstände nicht besitzen? Sind manche Gegenstände magisch oder haben sie besondere Fähigkeiten? ScriptableObject-basierte Enums können Methoden hinzufügen, um dies zu überprüfen.
Das MonoBehaviour DemoBall aus dem vorherigen Beispiel enthält die Methode AreEqual zum Vergleich von ScriptableObjects. Wenn Sie das Verhalten erweitern, können Sie die Vergleichslogik innerhalb des ScriptableObjects selbst bündeln.
In der Patterns-Demo könnten Sie den Ball so verändern, dass er beim Zusammenstoß mit einem Objekt selektiver ist. Schauen Sie sich ein allgemeines Element für Kollisionen im folgenden Codebeispiel an.
Damit könnte man ähnliche Ergebnisse erzielen wie mit der aktuellen Demo, aber sie hat jetzt ein m_Weakness-Feld. Dadurch kann jedes ScriptableObject ein anderes ScriptableObject definieren, das bei einer Kollision zerstört wird.
Anstatt die AreEqual-Methode aufzurufen, verwaltet jedes ScriptableObject einfach seine eigene Vergleichslogik.
Das Ergebnis ist flexibler und erweiterbar. Anstatt den Ball einen Block eines anderen Teams zerstören zu lassen, können Sie gezielt vorgehen. Mehrere Bälle in der Szene können verschiedene Blöcke zerstören, abhängig von ihren individuellen CollisionItems.
Dies schafft die Voraussetzungen für andere, komplexere Interaktionen. Wenn Sie ein Stein-Papier-Schere-System erstellen wollten, könnten Sie drei ScriptableObjects definieren: Stein, Papier und Schere. Jede könnte ihre eigene m_Weakness haben und die IsWinner-Methode verwenden, um die Interaktionen zu behandeln.
Im Gegensatz zu enums machen ScriptableObjects diesen Prozess modular und anpassungsfähig. Sie müssen sich nicht auf zusätzliche Datenstrukturen verlassen oder zusätzliche Logik hinzufügen, um mit einem separaten Datensatz zu synchronisieren. Fügen Sie einfach ein zusätzliches Feld und/oder eine Methode hinzu, um die Logik zu handhaben.
Wenn Sie sich erst einmal mit ScriptableObject-basierten Enums vertraut gemacht haben, werden Sie feststellen, dass sie Ihre Arbeitsabläufe verbessern können, insbesondere bei der Arbeit mit Teamkollegen. Da es sich um Assets handelt, führt ihre Aktualisierung zu weniger Konflikten bei der Zusammenführung, wodurch das Risiko von Datenverlusten verringert wird.
Das Hinzufügen neuer ScriptableObject-basierter Enums ist genau wie das Erstellen eines weiteren Assets. Anders als bei traditionellen Enums wird das Hinzufügen neuer Werte Ihren bestehenden Code nicht beeinträchtigen. Außerdem verfügt Unity bereits über integrierte Tools zum Suchen, Filtern und Organisieren, wie bei jedem anderen Asset auch.
Die Drag-and-Drop-Benutzeroberfläche des Editors ermöglicht es Ihren Designern außerdem, die Spieldaten ohne zusätzliche Unterstützung durch einen Softwareentwickler zu erweitern. Sie müssen zwar immer noch koordinieren, wie Sie die Felder einrichten, aber die Designer können diese Felder dann selbst mit Daten füllen.
ScriptableObject-basierte Enums sind eine weitere Ressource, die Ihr Team zur Verbesserung der Zusammenarbeit und Effizienz nutzen kann.
Lesen Sie mehr über Entwurfsmuster mit ScriptableObjects in unserem technischen E-Book Erstellen einer modularen Spielarchitektur in Unity mit ScriptableObjects. Weitere Informationen über gängige Entwurfsmuster für die Unity-Entwicklung finden Sie auch unter Verbessern Sie Ihren Code mit Programmiermustern für Spiele.