Component-Based Entity Systems in Spielen

Seite 2: Aggregation

Inhaltsverzeichnis

FĂĽr eine andere Art der Architektur sind bestimmte Entities nun nicht mehr als Spezialisierung generischer Entities zu verstehen, sondern als Aggregation unterschiedlicher Komponenten. Jede kapselt einen bestimmten Teil der Eigenschaften des Objekts: So gibt es beispielsweise eine AttackComponent, die angibt, wie viel Schaden eine Spielfigur anrichtet, und eine HealthComponent, die bestimmt, wie viel Lebenspunkte sie hat.

Diese Idee korrespondiert mit den Empfehlungen der Gang of Four, den Urvätern der objektorientierten Programmierung, die bereits in ihrem Buch "Design Patterns: Elements of Reusable Object-Oriented Software" empfehlen, Objektkomposition immer Klassenvererbung vorzuziehen.

Einen ähnlichen Ansatz verfolgt die Game Engine Unity: Jedem Spielobjekt lässt sich eine beliebige Anzahl Behaviours zuweisen, die das Verhalten des Spielobjekts bestimmt. Tatsächlich bot auch Unreal Engine 3 dieses Konzept in Form von ActorComponents an, allerdings war es in der Dokumentation nicht sonderlich präsent.

In Unity werden Spielobjekte aus einer Vielzahl Komponenten zusammengesetzt, beispielweise Transform (Position, Rotation und Skalierung) und Renderer (Abb. 2).

Zunächst führt man eine gemeinsame Schnittstelle IEntityComponent ein. Sie wird von allen Komponenten implementiert, während Entity-Klassen nichts weiter als eine Liste der Komponenten enthalten. Dieses Vorgehen scheint auf den ersten Blick deutlich robuster als der vererbungsbasierte Ansatz: Man kann neue Komponenten hinzufügen oder bearbeiten, ohne andere Eigenschaften zu beeinträchtigen, und die Fehlersuche beschränkt sich stets auf einen stark eingegrenzten Teil der Software.

Das Wichtigste dabei ist, der Versuchung zu widerstehen, Teile des Funktionsumfangs in die Entity-Klasse auszulagern. Selbst häufige Daten, wie die Position in der Spielwelt, sollten stets in eine Komponente ausgelagert werden. Nur so kann sich der Entwickler alle Vorteile des Designansatzes zunutze machen. Glücklicherweise gibt es eine noch ausgefeiltere Variante dieses Ansatzes, die eben jene Vorgehensweise forciert und zusätzliche Vorteile mit sich bringt.

Eine weitere Spielart der Aggregationen zugrunde legenden Architektur kommt völlig ohne Entity-Klasse aus. Die Entities bestehen nun aus nichts weiter als einer eindeutigen ID. Ein dedizierter EntityManager kümmert sich um die Zuordnung der IDs zu den Komponenten – in der Regel über Wörterbücher. Ihn kann man jederzeit bitten, beispielsweise die HealthComponent des Entitys 34 herauszugeben oder dem Entity 67 eine PositionComponent hinzuzufügen. Da es keine Entity-Klasse gibt, können Entities selbst keinerlei Daten oder Funktionen enthalten. Alle Daten verschieben sich damit in die Komponenten, wie zuvor erklärt.

Die Methoden hingegen werden Teil sogenannter Spielsysteme. Jedes System kapselt einen bestimmten Teil der Spiellogik: So gibt es etwa ein PhysicsSystem, ein HealthSystem und ein FightSystem. Sie alle haben über den EntityManager Zugriff auf die Komponenten der Entities und können deren Werte nach Belieben verändern.

Um den Ansatz abzurunden, kommunizieren alle Spielsysteme ausschließlich über Events miteinander. Ein EventManager erlaubt es Systemen, sich für bestimmte Events anzumelden oder selbst Events auszulösen. Folgendes Beispiel soll das Verhalten illustrieren:

Angenommen, das FightSystem entscheidet (wie auch immer), dass das Entity mit der ID 337 jetzt 24 Schadenspunkte nehmen soll. Es leitet ein TakeDamage-Event an den EventManager weiter und hat damit seine Aufgabe für den Augenblick getan. Das HealthSystem interessiert sich für dieses Event und bittet den EntityManager nun um die HealthComponent des Entitys 337. Nachdem das System alle benötigten Berechnungen durchgeführt hat, wie Abzug der Rüstung oder Einbeziehen des Schadenstyps, reduziert es den Lebensenergiewert der HealthComponent um 19. Dann leitet es ein DamageTaken-Event an den EventManager weiter, um anderen Systemen die Möglichkeit zu geben, darauf zu reagieren. So kann nun etwa ein SoundSystem einen Klang abspielen, wie einen Schrei des getroffenen Ritters, oder ein AnimationsSystem dafür sorgen, dass der arme Mann zurücktaumelt.

Dieser Ansatz bietet gewaltige Vorteile gegenüber einer einzigen Methode TakeDamage, die nacheinander in drei Codezeilen einer Entity-Klasse erst Lebensenergie abzieht und dann Klang und Animation abspielt: Durch den ereignisorientierten Ansatz gibt es keinerlei Kopplung zwischen den Systemen. Folglich lässt sich die Datei mit dem AnimationSystem wortwörtlich löschen, und der Rest des Spiels würde reibungslos weiter funktionieren. Im traditionellen Ansatz hätte der Entwickler in dem Fall erst einmal mit Hunderten von Compilerfehlern zu kämpfen.

Jedes System entscheidet selbst, auf welche Ereignisse der Spielwelt es reagiert. FĂĽgt man neue Spielsysteme hinzu oder entfernt bestehende, bleibt der Rest der Logik davon unbetroffen (Abb. 3).