Unity DOTS: Unsere Top 5 Features

Spieleproduktion, Technik

Zielsetzung

In diesem Artikel teilen wir unsere Erfahrungen mit Unity’s Data Oriented Technology Stack (DOTS) während der Arbeit an unserem DOTS-Projekt „Recursive Reckoning“. Wir zeigen auch einige einfache Codebeispiele aus unserem Projekt, um einen ersten Einblick in die neu veröffentlichten Features von Unity zu geben.

Recursive Reckoning

Gameplay

Die Idee von Recursive Reckoning bestand darin, ein Tower Defense Spiel mit einer in der Mitte fixierten Top-Down-Perspektive zu erstellen, die potentiell einen unendlichen Zoom über mehrere Ebenen bietet, obwohl wir den Zoom derzeit auf 5 Ebenen begrenzt haben. Der Spieler kann die Kamera auch um 360° drehen und bis zu 45° nach unten neigen.

Aufgrund des Unendlichkeits-Aspekts ist es nicht geeignet, jedes Level in zehnfacher Größe des vorherigen aufzubauen. Dieser Ansatz würde sehr schnell an die Grenzen der Game-Engine stoßen. Eine bessere Möglichkeit besteht darin, die visuelle Darstellung von der Simulation zu trennen, indem alle Ebenen mit einer Skalierung von 1 nebeneinander in der Welt aufgebaut werden. Dann werden die Visualisierungen verschoben und in ihrer Größe an ihre Präsentationsebene angepasst. Beispielsweise wird die erste Ebene mit einer Skalierung von 0,1 dargestellt, die zweite mit einer Skalierung von 1 und die dritte mit dem 10-fachen ihrer simulierten Skalierung. Auf diese Weise kann der Spieler bis zu 3 Level gleichzeitig sehen.

Um dem Spieler ein nahtloses Herauszoomen zu ermöglichen, werden die dargestellten Ebenen ausgetauscht, um die nächsten drei Ebenen anzuzeigen. Im vorherigen Beispiel würden die Ebenen 0, 1 und 2 durch die Ebenen 1, 2 und 3 ersetzt. Und natürlich würde die Kameraposition um ihr 10-faches verkleinert werden, um ein nahtloses Erlebnis zu ermöglichen. Um den Ebenenwechsel perfekt zu verbergen, ist das Timing entscheidend. Alles muss im exakt gleichen Frame passieren. Dank der Effizienz von DOTS gibt es dabei keine merkbaren Performanceeinbrüche.

Was ist ECS?

Weil wir Unity’s Entity Component System (ECS) verwenden, stellt die mehr als verdoppelte Menge an Objekten (Entities), für die visuellen Kopien, kein großes Problem dar.

ECS ist sehr effizient, wenn es darum geht, die Daten vieler Entitäten zu ändern. Es zerlegt ein Spiel in Entities, Komponenten und Systeme.

Entities sind nichts anderes als Bezeichner oder Container, die einzelne Spielobjekte darstellen. Komponenten hingegen enthalten die Daten und das Verhalten, die mit bestimmten Aspekten einer Entity verbunden sind, wie z.B. Position, Geschwindigkeit oder Gesundheit. Schließlich sind Systeme für die Verarbeitung und Manipulation von Entitäten basierend auf ihren Komponenten verantwortlich.

Die Prinzipien von ECS drehen sich um datenorientiertes Design und Trennung von Belangen. Durch die zusammenhängende Speicherung ähnlicher Daten im Arbeitsspeicher ermöglicht ECS eine effiziente Datenverarbeitung, da die CPU mit größeren Datenblöcken gleichzeitig arbeiten kann. Dieser Ansatz sorgt für weniger Cache-Misses und reduziert die Latenz beim Speicherzugriff.

Ein wesentlicher Unterschied zwischen ECS und objektorientierter Programmierung (OOP) liegt in ihrer grundlegenden Designphilosophie. OOP konzentriert sich auf die Kapselung des Verhaltens innerhalb von Objekten, während ECS Daten priorisiert und Verhalten in Systeme unterteilt. Diese Trennung ermöglicht eine bessere Leistungsoptimierung, Skalierbarkeit und Parallelität bei der Spieleentwicklung.

Bei der Implementierung bietet ECS deutliche Vorteile gegenüber dem traditionellen MonoBehaviour-Ansatz. Durch den Einsatz von ECS ist es möglich, aufgrund einer geringeren Speicherfragmentierung und einer verbesserten Cache-Auslastung eine bessere Leistung zu erzielen. Darüber hinaus erleichtert ECS die parallele Verarbeitung und ermöglicht so eine effiziente Nutzung von Multi-Core-CPUs. Die Implementierung von ECS erfordert jedoch eine Änderung der Denkweise und einen anderen Ansatz zur Organisation und Verwaltung der Spiellogik im Vergleich zur bekannten MonoBehaviour-basierten Implementierung.

Bist Du bereit, Deine Spielevision zum Leben zu erwecken? Setze Dich noch heute mit uns in Verbindung, um mehr darüber zu erfahren, wie wir Dir mit unseren Spieleentwicklungsdiensten dabei helfen können, deine Vorstellungen und Ideen erfolgreich zu verwirklichen!

Unsere Top 5 DOTS-Aspekte

In den letzten Monaten haben wir an einem Spielprojekt gearbeitet, basierend auf Unity’s Data Oriented Technology Stack. Das Ziel des Projekts bestand darin, die Arbeit mit DOTS auszuprobieren und zu lernen und vielleicht sogar ein lustiges kleines Spiel daraus zu machen. Wir haben uns hauptsächlich auf das Entities Package (ECS) konzentriert, haben aber auch, wo es möglich war, das Job System und den Burst Compiler genutzt. Hier sind unsere Top 5 Aspekte, die uns bei der Arbeit mit DOTS am besten gefallen haben, basierend auf unserem Tower-Defense-Projekt „Recursive Reckoning“.

1. Performance

Es ist nicht notwendig, jede Codezeile zu optimieren, um im Vergleich zu Mono-Scripting eine ordentliche Leistungssteigerung zu erzielen. Wenn die Grundprinzipien des ECS-Konzepts eingehalten werden, wird es bei den meisten Spielen nicht viele Probleme hinsichtlich der Leistung geben.

Die ECS-Architektur strukturiert ähnliche Daten im Speicher als nebeneinander liegende Blöcke, sodass die CPU problemlos darauf zugreifen und sie als Ganzes ohne viele Speichersprünge verarbeiten kann.

Hier sind zwei interessante Videos, die die Performance von MonoBehaviour und DOTS vergleichen bei Skripten: Unity DOTS: Comparing performance und Physik: How many Rigidbodies can Unity support ? [2020 DOTS Edition]

2. Code Clean erzwungen

Unity DOTS ermutigt Entwickler, saubere und effiziente Programmierpraktiken anzuwenden. Im Gegensatz zu herkömmlichen Ansätzen, bei denen sich Entwickler möglicherweise auf Abstraktionen auf hoher Ebene verlassen, zwingt DOTS sie dazu, darüber nachzudenken, wie sie ihre Systeme entwerfen und implementieren. Die datenorientierte Denkweise fördert eine explizitere und organisiertere Codestruktur, was zu einer verbesserten Lesbarkeit und Wartbarkeit des Codes führt.

Durch die Zerlegung der Spiellogik in kleinere, wiederverwendbare Komponenten kann eine klare Trennung der Belange gewährleistet werden. Diese Modularität ermöglicht ein besseres Code-Verständnis und erleichtert die Identifizierung und Behebung auftretender Probleme. Darüber hinaus verhindert DOTS unnötige Objektzuweisungen und fördert die Verwendung von Arrays mit fester Größe, wodurch die Speicherfragmentierung verringert und die Gesamtleistung verbessert wird.

3. Modularität

Aufgrund der ECS-Architektur werden Spielmechaniken auch für andere Projekte sehr gut wiederverwendbar sein. Zum Beispiel kann die Gesundheits-Mechanik, die wir für unser DOTS-Projekt Recursive Reckoning erstellt haben, einfach in ein neues Projekt kopiert werden und ist dann so gut wie Plug-and-Play: Es muss nur das Authoring-Skript an eine Entity geheftet werden und es hat einen Gesundheitswert, dem Schaden zugefügt werden kann.

Das Gesundheitsmodul besteht beispielsweise aus folgenden Teilen:

  • Health Component: enthält Float-Variablen für die aktuelle und maximale Gesundheit einer Entity
  • Damage Buffer: Liste der Schadenswerte aus verschiedenen Quellen, die am Ende jedes Frames summiert und von der aktuellen Gesundheit abgezogen werden
  • Health Authoring: MonoBehaviour-Skript, das im Inspektorfenster des Editors an ein GameObject angehängt werden kann, wird über seine Baker-Klasse in eine Entity mit Health-Komponenten konvertiert
  • Health Aspekt: ​​ermöglicht einfachen Zugriff auf eine Reihe von Komponenten, was technisch nicht erforderlich ist, aber eine saubere Möglichkeit des Komponentenzugriffs darstellt
  • Apply Damage System: plant den Job „Apply Damage“ am Ende jedes Frames so, dass er einmal parallel für jede Entity ausgeführt wird, die über die Komponenten des Health Aspect verfügt
  • Apply Damage Job: Summiert alle Damage Buffer Werte und subtrahiert das Ergebnis von der aktuellen Gesundheit. Verwendet den Health Aspect, um auf die benötigten Komponenten zuzugreifen
  • Destroy On No Health System: plant den Job „Destroy On No Health“ zur parallelen Ausführung in jedem Frame so, dass er einmal für jede Entity ausgeführt wird, die über die Komponenten des Health Aspect verfügt
  • Destroy On No Health Job: markiert jede Entity mit 0 Gesundheit zur Zerstörung, nutzt den Health Aspect um auf die Gesundheit zuzugreifen
4. Hybrider Ansatz

Eine Hauptsorge bezüglich DOTS ist die Befürchtung, dass es irgendwann das alte Mono-Scripting-Backend ersetzen wird. Aber genau das Gegenteil ist der Fall. Die aktuellen Zukunftspläne von Unity für DOTS sehen vor, das im Editor verfügbare Toolset zu erweitern. Es ist auch bereits möglich, einen hybriden Ansatz zu nutzen und die Stärken beider Welten zu kombinieren.

Durch die Verwendung einer SystemBase-Klasse als Schnittstelle ist es möglich, Eingaben von unserer Benutzeroberfläche und unserem InputSystem zu erhalten und beispielsweise eine neue Turm-Entity an der Cursorposition zu erzeugen, wenn der Spieler die linke Maustaste drückt.

Der hybride Ansatz verwendet außerdem Authoring- und Baker-Skripte, um GameObjects innerhalb von Subscenes in Entities zu konvertieren.

Hier ist ein Beispiel für ein Authoring-MonoBehaviour mit seiner Baker-Klasse:

public class HealthAuthoring : MonoBehaviour {
public float value;
}
public class HealthBaker : Baker {
public override void Bake(HealthAuthoring authoring) {
var entity = GetEntity(TransformUsageFlags.Dynamic);
AddComponent(entity, new HealthComponent {Value = authoring.value, Max = authoring.value});
AddBuffer(entity);
}
}
5. Entity Jobs

Als einer der Grundpfeiler von DOTS ist auch das Jobsystem auf Effizienz ausgelegt. Sowohl in der Verarbeitung als auch in der Programmierung. Es ermöglicht unter anderem eine einfache Parallelisierung von Aufgaben und nutzt damit die heutigen Multicore-CPUs optimal aus.

Eine besondere Art von Jobs sind Entity-Jobs. Sie verarbeiten die Daten vieler Entities auf effiziente Weise. Es ist lediglich erforderlich, eine Funktion zu erstellen, die für jede Entity einmal ausgeführt werden soll und diese Funktion als Job zur Ausführung einzureihen. Das Jobsystem kümmert sich um Abhängigkeiten und Ausführungsreihenfolge, um häufige Probleme bei der parallelen Programmierung zu vermeiden, wie z.B. Race Conditions oder Deadlocks.

Hier ist ein kleines Beispiel für einen Job:

[BurstCompile]
public partial struct DestroyOnNoHealthJob : IJobEntity {
public EntityCommandBuffer.ParallelWriter Ecb;

private void Execute([EntityIndexInQuery] int sortKey, HealthAspect health) {
if (!health.isReadyToDie) return;
Ecb.DestroyEntity(sortKey, health.Entity);
}
}

Und so wird der Job zur Ausführung eingeplant:

[BurstCompile]
public partial struct DestroyOnNoHealthSystem : ISystem {
[BurstCompile]
public void OnCreate(ref SystemState state) {
state.RequireForUpdate();
}

[BurstCompile]
public void OnUpdate(ref SystemState state) {
var ecbSingleton = SystemAPI.GetSingleton();

new DestroyOnNoHealthJob {
Ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter(),
}.ScheduleParallel();
}

Nützliche Tools

Unity hat einige Debug-Tools für den Editor erstellt, um bei der DOTS-Entwicklung zu helfen. Sie sind unter Fenster > Entitäten verfügbar. Besonders hilfreich war das “System”-Fenster, welches die Ausführungsreihenfolge aller vorgefertigten und benutzerdefinierten Systeme anzeigt. Außerdem hat der Inspektor ein Upgrade zum Debuggen von Entities und ihren angehängten Komponenten erhalten.

Persönliches Fazit

Alles in allem bin ich sehr zufrieden mit dem Data Oriented Technology Stack, der dieses Jahr in Version 1.0 veröffentlicht wurde.
Ich sehe mich in Zukunft mit DOTS, Unity Physics und all den neuen Funktionen herumspielen, nur weil ich dieses Mal Spaß daran hatte. Diese Technologie bietet großes Potenzial und ich bin gespannt, was noch kommen wird, wenn sie mehr Aufmerksamkeit bekommt und mehr Entwickler die Chance nutzen, die sich ihnen mit diesem leistungsstarken Tool bietet.

Fragen & Wünsche

Wir hoffen, dass Dir unser Artikel gefällt und möchten Dich dazu einladen, uns Deine Gedanken und Fragen zu dem Thema mitzuteilen. Wenn Du Fragen, Kommentare oder Feedback zum Inhalt dieses Artikels hast, zögere bitte nicht, uns auch diese im Kommentarbereich anzuvertrauen. Wir freuen uns immer, von unseren Lesern zu hören und uns an sinnvollen Diskussionen über die Spieleentwicklung zu beteiligen.

Frage uns einfach alles was Du wissen willst und wir werden unser Bestes tun, um die Antworten zu geben, nach denen Du suchst. Vielen Dank für Deine Unterstützung und wir freuen uns darauf, von Dir zu hören!

Kommentar Formular

Write comment

* These fields are required

Kommentare

Comments

No Comments

Weitere spannende Beiträge

Unity UI Toolkit

Unity modernisiert die Bentuzeroberflächen und Funktionalitäten für Developer. UIToolkit soll eine vereinfachte Lösung für UI-Element Erstellung und…