Bin ich mit dieser Komponentenarchitektur auf dem richtigen Weg?


9

Ich habe kürzlich beschlossen, meine Spielarchitektur zu überarbeiten, um tiefe Klassenhierarchien zu beseitigen und sie durch konfigurierbare Komponenten zu ersetzen. Die erste Hierarchie, die ich ersetze, ist die Elementhierarchie, und ich möchte einige Ratschläge, um zu wissen, ob ich auf dem richtigen Weg bin.

Zuvor hatte ich eine Hierarchie, die ungefähr so ​​aussah:

Item -> Equipment -> Weapon
                  -> Armor
                  -> Accessory
     -> SyntehsisItem
     -> BattleUseItem -> HealingItem
                      -> ThrowingItem -> ThrowsAsAttackItem

Unnötig zu erwähnen, dass es langsam chaotisch wurde und dies keine einfache Lösung für Gegenstände war, bei denen es sich um mehrere Typen handeln musste (dh einige Geräte werden für die Objektsynthese verwendet, andere Geräte können geworfen werden usw.).

Ich habe dann versucht, die Funktionalität umzugestalten und in die Basiselementklasse einzufügen. Aber dann bemerkte ich, dass der Artikel viele unbenutzte / überflüssige Daten enthielt. Jetzt versuche ich, eine Komponente wie Architektur zu erstellen, zumindest für meine Gegenstände, bevor ich dies für meine anderen Spielklassen versuche.

Folgendes denke ich derzeit für das Komponenten-Setup:

Ich habe eine Basisgegenstandsklasse mit Steckplätzen für verschiedene Komponenten (z. B. einen Ausrüstungskomponentensteckplatz, einen Heilungskomponentensteckplatz usw. sowie eine Karte für beliebige Komponenten).

class Item
{
    //Basic item properties (name, ID, etc.) excluded
    EquipmentComponent* equipmentComponent;
    HealingComponent* healingComponent;
    SynthesisComponent* synthesisComponent;
    ThrowComponent* throwComponent;
    boost::unordered_map<std::string, std::pair<bool, ItemComponent*> > AdditionalComponents;
} 

Alle Elementkomponenten würden von einer Basis-ItemComponent-Klasse erben, und jeder Komponententyp ist dafür verantwortlich, der Engine mitzuteilen, wie diese Funktionalität implementiert werden soll. Das heißt, die Heilungskomponente teilt der Kampfmechanik mit, wie der Gegenstand als Heilgegenstand verbraucht werden soll, während die Wurfkomponente der Kampfmaschine mitteilt, wie der Gegenstand als Wurfgegenstand behandelt werden soll.

Die Karte wird zum Speichern beliebiger Komponenten verwendet, die keine Kernelementkomponenten sind. Ich kopple es mit einem Bool, um anzugeben, ob der Item Container die ItemComponent verwalten soll oder ob sie von einer externen Quelle verwaltet wird.

Meine Idee hier war, dass ich die Kernkomponenten, die von meiner Spiel-Engine verwendet werden, im Voraus definiere und meine Gegenstandsfabrik die Komponenten zuweist, die der Gegenstand tatsächlich hat, andernfalls sind sie null. Die Karte würde beliebige Komponenten enthalten, die im Allgemeinen von Skriptdateien hinzugefügt / verwendet werden.

Meine Frage ist, ist das ein gutes Design? Wenn nicht, wie kann es verbessert werden? Ich überlegte, alle Komponenten in der Karte zu gruppieren, aber die Verwendung der Zeichenfolgenindizierung schien für die Kernelementkomponenten nicht erforderlich zu sein

Antworten:


8

Es scheint ein sehr vernünftiger erster Schritt zu sein.

Sie entscheiden sich für eine Kombination aus Allgemeinheit (die Karte "Zusätzliche Komponenten") und Suchleistung (die fest codierten Elemente), die möglicherweise eine Voroptimierung darstellt - Ihr Punkt in Bezug auf die allgemeine Ineffizienz von Zeichenfolgen Die Suche ist gut gemacht, aber Sie können dies abmildern, indem Sie Komponenten durch etwas indizieren, das schneller zu hashen ist. Ein Ansatz könnte darin bestehen, jedem Komponententyp eine eindeutige Typ-ID (im Wesentlichen implementieren Sie eine einfache benutzerdefinierte RTTI ) und einen darauf basierenden Index zu geben.

Unabhängig davon würde ich Sie darauf hinweisen, eine öffentliche API für das Item-Objekt bereitzustellen, mit der Sie alle Komponenten - die fest codierten und die zusätzlichen - einheitlich anfordern können. Dies würde es einfacher machen, die zugrunde liegende Darstellung oder das Gleichgewicht von fest codierten / nicht fest codierten Komponenten zu ändern, ohne alle Elementkomponenten-Clients umgestalten zu müssen.

Sie können auch in Betracht ziehen, "Dummy" -No-Op-Versionen jeder der fest codierten Komponenten bereitzustellen und sicherzustellen, dass sie immer zugewiesen sind. Sie können dann Referenzelemente anstelle von Zeigern verwenden und müssen nie nach einem NULL-Zeiger suchen vor der Interaktion mit einer der fest codierten Komponentenklassen. Die Kosten für den dynamischen Versand für die Interaktion mit den Mitgliedern dieser Komponente fallen weiterhin an. Dies würde jedoch auch bei Zeigerelementen auftreten. Dies ist eher ein Problem der Code-Sauberkeit, da die Auswirkungen auf die Leistung aller Wahrscheinlichkeit nach vernachlässigbar sind.

Ich denke nicht, dass es eine großartige Idee ist, zwei verschiedene Arten von Gültigkeitsbereichen für die Lebensdauer zu haben (mit anderen Worten, ich denke nicht, dass der Bool, den Sie in der zusätzlichen Komponentenzuordnung haben, eine großartige Idee ist). Dies verkompliziert das System und impliziert, dass Zerstörung und Freisetzung von Ressourcen nicht besonders deterministisch sein werden. Die API für Ihre Komponenten wäre viel klarer, wenn Sie sich für die eine oder andere Lebensdauerverwaltungsstrategie entscheiden würden - entweder verwaltet die Entität die Komponentenlebensdauer oder das Subsystem, das Komponenten realisiert, tut dies (ich bevorzuge letzteres, weil es besser mit der Außenbordkomponente gepaart wird Ansatz, den ich als nächstes diskutieren werde).

Der große Nachteil, den ich bei Ihrem Ansatz sehe, ist, dass Sie alle Komponenten im "Entity" -Objekt zusammenfassen, was eigentlich nicht immer das beste Design ist. Aus meiner verwandten Antwort auf eine andere komponentenbasierte Frage:

Ihr Ansatz, eine große Karte von Komponenten und einen update () -Aufruf im Spielobjekt zu verwenden, ist ziemlich suboptimal (und eine häufige Gefahr für diejenigen, die diese Art von Systemen zum ersten Mal erstellen). Dies führt zu einer sehr schlechten Cache-Kohärenz während des Updates und ermöglicht es Ihnen nicht, die Parallelität und den Trend zum SIMD-ähnlichen Prozess großer Datenmengen oder Verhaltensweisen gleichzeitig zu nutzen. Es ist oft besser, ein Design zu verwenden, bei dem das Spielobjekt seine Komponenten nicht aktualisiert, sondern das für die Komponente selbst verantwortliche Subsystem sie alle auf einmal aktualisiert.

Sie verfolgen im Wesentlichen den gleichen Ansatz, indem Sie die Komponenten in der Elemententität speichern (was wiederum ein völlig akzeptabler erster Schritt ist). Möglicherweise stellen Sie fest, dass der Großteil des Zugriffs auf Komponenten, deren Leistung Sie befürchten, nur darin besteht, diese zu aktualisieren, und wenn Sie sich für einen stärker externen Ansatz für die Komponentenorganisation entscheiden, bei dem die Komponenten in einem Cache-kohärent gehalten werden Dank einer effizienten (für ihre Domäne) Datenstruktur durch ein Subsystem, das ihre Anforderungen am besten versteht, können Sie eine viel bessere, parallelisierbarere Aktualisierungsleistung erzielen.

Aber ich weise darauf nur als etwas hin, das als zukünftige Richtung in Betracht gezogen werden sollte - Sie möchten sicher nicht über Bord gehen, um dies zu überarbeiten; Sie können durch ständiges Refactoring einen schrittweisen Übergang vornehmen oder feststellen, dass Ihre aktuelle Implementierung Ihren Anforderungen perfekt entspricht und nicht wiederholt werden muss.


1
+1 für den Vorschlag, das Item-Objekt zu entfernen. Während es im Vorfeld mehr Arbeit geben wird, wird es am Ende ein besseres Komponentensystem produzieren.
James

Ich hatte ein paar weitere Fragen, war mir nicht sicher, ob ich ein neues Topiuc starten sollte, also versuche es zuerst hier: Für meine Gegenstandsklasse gibt es keine Methoden, die ich evert frame (oder sogar close) aufrufen werde. Für meine Grafik-Subsysteme nehme ich Ihren Rat an und behalte alle zu aktualisierenden Objekte unter dem System. Die andere Frage, die ich hatte, ist, wie ich mit Komponentenprüfungen umgehe. Wie zum Beispiel möchte ich sehen, ob ich ein Element als X verwenden kann, also würde ich natürlich prüfen, ob das Element die für die Ausführung von X erforderliche Komponente enthält. Ist dies der richtige Weg, dies zu tun?
Nochmals vielen
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.