Spielkomponenten, Spielmanager und Objekteigenschaften


15

Ich versuche, mich mit komponentenbasiertem Entity-Design zu beschäftigen.

Mein erster Schritt bestand darin, verschiedene Komponenten zu erstellen, die einem Objekt hinzugefügt werden konnten. Für jeden Komponententyp hatte ich einen Manager, der die Aktualisierungsfunktion jeder Komponente aufrief und bei Bedarf Dinge wie den Tastaturstatus usw. übergab.

Als nächstes habe ich das Objekt entfernt und jede Komponente mit einer ID versehen. Ein Objekt wird also durch Komponenten mit denselben IDs definiert.

Jetzt denke ich, dass ich keinen Manager für alle meine Komponenten benötige, zum Beispiel habe ich einen SizeComponent, der nur eine SizeEigenschaft hat). Infolgedessen verfügt der SizeComponentüber keine Aktualisierungsmethode, und die Aktualisierungsmethode des Managers führt keine Aktionen aus.

Mein erster Gedanke war, eine ObjectPropertyKlasse zu haben , die Komponenten abfragen kann, anstatt sie als Eigenschaften von Komponenten zu haben. Ein Objekt hätte also eine Anzahl von ObjectPropertyund ObjectComponent. Komponenten verfügen über Aktualisierungslogik, die das Objekt nach Eigenschaften abfragt. Der Manager würde den Aufruf der Aktualisierungsmethode der Komponente verwalten.

Dies scheint mir eine Überentwicklung zu sein, aber ich glaube nicht, dass ich die Komponenten loswerden kann, da die Manager wissen müssen, welche Objekte welche Komponentenlogik benötigen, um ausgeführt zu werden (ansonsten würde ich die Komponente einfach entfernen vollständig und schieben Sie die Aktualisierungslogik in den Manager).

  1. Ist das (mit ObjectProperty, ObjectComponentund ComponentManagerKlassen) Over-Engineering?
  2. Was wäre eine gute Alternative?

1
Sie haben die richtige Idee, wenn Sie versuchen, das Komponentenmodell zu erlernen, aber Sie müssen besser verstehen, was es zu tun hat - und die einzige Möglichkeit, dies zu tun, besteht darin, ein Spiel [meistens] zu beenden, ohne es zu verwenden. Ich denke, ein zu machen SizeComponentist übertrieben - man kann davon ausgehen, dass die meisten Objekte eine Größe haben - es sind Dinge wie Rendering, KI und Physik, bei denen das Komponentenmodell verwendet wird; Die Größe verhält sich immer gleich - so können Sie diesen Code freigeben.
Jonathan Dickinson


@JonathanDickinson, @Den: Ich denke, dann ist mein Problem, wo ich gemeinsame Eigenschaften speichere. ZB ein Objekt als eine Position, die von a RenderingComponentund a verwendet wird PhysicsComponent. Überlege ich mir, wo ich die Immobilie hinstellen soll? Sollte ich es in irgendein gerade haften, dann hat die andere Frage ein Objekt für die Komponente, die die erforderliche Eigenschaft hat?
George Duckett

Mein vorheriger Kommentar und der dahinter stehende Denkprozess veranlassen mich, eine separate Klasse für eine Eigenschaft (oder eine Gruppe verwandter Eigenschaften) zu haben, die Komponenten abfragen können.
George Duckett

1
Ich mag diese Idee wirklich - es könnte sich lohnen, sie auszuprobieren. Aber es ist wirklich teuer, ein Objekt zu haben, um jede einzelne Immobilie zu beschreiben. Sie können versuchen PhysicalStateInstance(eine pro Objekt) neben einer GravityPhysicsShared(eine pro Spiel); Ich bin jedoch versucht zu sagen, dass dies ein Versuch ist, sich in die Bereiche der Euphorie der Architekten zu wagen, und dass Sie sich nicht in ein Loch hineinversetzen (genau das, was ich mit meinem ersten Komponentensystem getan habe). KUSS.
Jonathan Dickinson

Antworten:


6

Die einfache Antwort auf Ihre erste Frage lautet: Ja, Sie haben das Design überarbeitet. Das "Wie weit kann ich die Dinge aufschlüsseln?" Frage ist sehr häufig, wenn der nächste Schritt unternommen wird und das zentrale Objekt (normalerweise eine Entität genannt) entfernt wird.

Wenn Sie die Objekte so detailliert aufteilen, dass sie eine eigene Größe haben, ist das Design zu weit gegangen. Ein Datenwert für sich ist keine Komponente. Es ist ein eingebauter Datentyp und kann oft genau so aufgerufen werden, wie Sie sie aufgerufen haben, eine Eigenschaft. Eine Eigenschaft ist keine Komponente, aber eine Komponente enthält Eigenschaften.

Hier sind einige Richtlinien, die ich bei der Entwicklung eines Komponentensystems zu befolgen versuche:

  • Da ist kein Löffel.
    • Dies ist der Schritt, den Sie bereits unternommen haben, um das zentrale Objekt zu entfernen. Dies entfernt die gesamte Debatte darüber, was in das Entity-Objekt und was in eine Komponente eingeht, da jetzt nur noch die Komponenten vorhanden sind.
  • Komponenten sind keine Strukturen
    • Wenn Sie etwas so aufteilen, dass es nur Daten enthält, ist es keine Komponente mehr, sondern lediglich eine Datenstruktur.
    • Eine Komponente sollte alle Funktionen enthalten, die erforderlich sind, um eine bestimmte Aufgabe auf eine bestimmte Weise auszuführen.
    • Die IRenderable-Oberfläche bietet die allgemeine Lösung, um alles im Spiel visuell anzuzeigen. CRenderableSprite und CRenderableModel sind eine Komponentenimplementierung dieser Schnittstelle, die die Besonderheiten für das Rendern in 2D bzw. 3D bereitstellt.
    • IUseable ist die Schnittstelle für etwas, mit dem ein Spieler interagieren kann. CUseableItem ist die Komponente, die entweder die aktive Waffe abfeuert oder den ausgewählten Trank trinkt, während CUseableTrigger der Ort sein kann, an dem ein Spieler in einen Turm hüpft oder einen Hebel betätigt, um die Zugbrücke fallen zu lassen.

Da es sich bei der Richtlinie für Komponenten nicht um Strukturen handelt, wurde die SizeComponent-Komponente zu weit zerlegt. Es enthält nur Daten und was die Größe von etwas definiert, kann variieren. In einer Rendering-Komponente kann es sich beispielsweise um einen 1d-Skalar oder einen 2 / 3d-Vektor handeln. In einer Physikkomponente kann es sich um das Begrenzungsvolumen des Objekts handeln. In einem Inventarobjekt kann es sein, wie viel Platz es in einem 2D-Raster einnimmt.

Versuchen Sie, eine gute Grenze zwischen Theorie und Praxis zu ziehen.

Hoffe das hilft.


Vergessen wir nicht, dass das Aufrufen einer Funktion von einer Schnittstelle auf einigen Plattformen länger
ADB

Ein guter Punkt, den ich mir merken sollte, aber ich habe versucht, sprachunabhängig zu bleiben und sie nur in den allgemeinen Designbegriffen zu verwenden.
James

"Wenn Sie etwas so aufteilen, dass es nur Daten enthält, ist es keine Komponente mehr, sondern lediglich eine Datenstruktur." -- Warum? "Komponente" ist ein so allgemeines Wort, dass es auch Datenstruktur bedeuten kann.
Paul Manta

@PaulManta Ja, es ist ein Oberbegriff, aber der springende Punkt bei dieser Frage und Antwort ist, wo die Grenze gezogen werden muss. Meine Antwort ist, wie Sie zitiert haben, nur ein Vorschlag für eine Faustregel, um genau das zu tun. Wie immer befürworte ich, niemals theoretische oder gestalterische Überlegungen zuzulassen, die die Entwicklung antreiben, sondern sie zu unterstützen.
James

1
@ James Das war eine interessante Diskussion. :) chat.stackexchange.com/rooms/2175 Mein größter Kritikpunkt bei Ihrer Implementierung ist, dass Komponenten zu viel darüber wissen, woran andere Komponenten interessiert sind. Ich möchte die Diskussion zu einem späteren Zeitpunkt fortsetzen.
Paul Manta

14

Sie haben bereits eine Antwort akzeptiert, aber hier ist mein Stab bei einem CBS. Ich stellte fest, dass eine generische ComponentKlasse einige Einschränkungen aufweist, und entschied mich für ein von Radical Entertainment auf der GDC 2009 beschriebenes Design, bei dem vorgeschlagen wurde, Komponenten in Attributesund zu trennen Behaviors. (" Theorie und Praxis der Architektur von Spielobjektkomponenten", Marcin Chady)

Ich erkläre meine Entwurfsentscheidungen in einem zweiseitigen Dokument. Ich werde einfach den Link posten, da es zu lang ist, um alles hier einzufügen. Momentan werden nur die Logikkomponenten (nicht auch die Rendering- und Physikkomponenten) behandelt, aber es sollte Ihnen eine Vorstellung davon geben, was ich versucht habe:

http://www.pdf-archive.com/2012/01/08/entity-component-system/preview/page/1

Hier ist ein Auszug aus dem Dokument:

Attribute und Verhalten in Kürze

AttributesVerwalten einer Datenkategorie und jede Logik, die sie haben, ist im Umfang begrenzt. Stellen Sie beispielsweise Healthsicher, dass der aktuelle Wert niemals größer als der Maximalwert ist, und benachrichtigen Sie auch andere Komponenten, wenn der aktuelle Wert einen bestimmten kritischen Wert unterschreitet. Er enthält jedoch keine komplexere Logik. Attributessind von keinem anderen abhängig Attributesoder Behaviors.

BehaviorsSteuern Sie, wie die Entität auf Spielereignisse reagiert, treffen Sie Entscheidungen und ändern Sie die Werte Attributesnach Bedarf. Behaviorssind von einigen der abhängig Attributes, können aber nicht direkt miteinander interagieren - sie reagieren nur auf Attributes’die Änderung von Werten durch die anderen Behaviorsund auf die Ereignisse, die sie gesendet haben.


Bearbeiten: Und hier ist ein Beziehungsdiagramm, das zeigt, wie die Komponenten miteinander kommunizieren:

Kommunikationsdiagramm zwischen Attributen und Verhalten

Ein Implementierungsdetail: Die Entitätsebene EventManagerwird nur erstellt, wenn sie verwendet wird. Die EntityKlasse speichert nur einen Zeiger auf einen EventManager, der nur dann initialisiert wird, wenn eine Komponente dies anfordert.


Edit: Auf eine andere Frage habe ich eine ähnliche Antwort auf diese gegeben. Hier finden Sie möglicherweise eine bessere Erläuterung des Systems:
/gamedev//a/23759/6188


2

Es hängt wirklich von den Eigenschaften ab, die Sie benötigen und wo Sie sie benötigen. Die Menge an Speicher, die Sie haben werden, und die Rechenleistung / den Typ, den Sie verwenden werden. Ich habe Folgendes gesehen und versuche es zu tun:

  • Eigenschaften, die von mehreren Komponenten verwendet, aber nur von einer geändert werden, werden in dieser Komponente gespeichert. Shape ist ein gutes Beispiel in einem Spiel, in dem das KI-System, das Physik-System sowie das Rendering-System Zugriff auf die Grundform benötigen, eine schwere Eigenschaft ist und wenn möglich nur an einer Stelle verbleiben sollte.
  • Eigenschaften wie Position müssen manchmal dupliziert werden. Wenn Sie zum Beispiel mehrere Systeme gleichzeitig ausführen, möchten Sie vermeiden, über Systeme hinweg zu schauen, und möchten stattdessen die Position synchronisieren (Kopieren von der Masterkomponente oder Synchronisieren über Deltas oder bei Bedarf mit einem Kollisionsdurchlauf).
  • Eigenschaften, die von Steuerelementen oder AI-"Absichten" stammen, können in einem dedizierten System gespeichert werden, da sie auf die anderen Systeme angewendet werden können, ohne von außen sichtbar zu sein.
  • Einfache Eigenschaften können kompliziert werden. Manchmal erfordert Ihre Position ein spezielles System, wenn Sie viele Daten gemeinsam nutzen müssen (Position, Ausrichtung, Frame-Delta, Gesamt-Delta-Bewegung, Delta-Bewegung für das aktuelle Frame und für das vorherige Frame, Drehung ...). In diesem Fall müssen Sie mit dem System auf die neuesten Daten der dedizierten Komponente zugreifen und diese möglicherweise über Akkumulatoren (Deltas) ändern.
  • Manchmal können Ihre Eigenschaften in einem unformatierten Array (double *) gespeichert werden und Ihre Komponenten haben einfach Zeiger auf die Arrays, die die verschiedenen Eigenschaften enthalten. Das offensichtlichste Beispiel ist, wenn Sie massive parallele Berechnungen benötigen (CUDA, OpenCL). Ein einziges System für die ordnungsgemäße Verwaltung der Zeiger könnte also von Vorteil sein.

Diese Prinzipien haben ihre Grenzen. Natürlich müssen Sie die Geometrie auf den Renderer übertragen, aber Sie möchten sie wahrscheinlich nicht von dort abrufen. Die Master-Geometrie wird in der Physik-Engine gespeichert, falls dort Deformationen auftreten, und mit dem Renderer synchronisiert (von Zeit zu Zeit abhängig von der Entfernung der Objekte). In gewisser Weise werden Sie es also trotzdem duplizieren.

Es gibt keine perfekten Systeme. Und einige Spiele sind mit einem einfacheren System besser dran, während andere komplexere systemübergreifende Synchronisationen erfordern.

Stellen Sie zunächst sicher, dass auf alle Eigenschaften von Ihren Komponenten aus auf einfache Weise zugegriffen werden kann, damit Sie die Art und Weise ändern können, in der Sie die Eigenschaften transparent speichern, sobald Sie mit der Feinabstimmung Ihrer Systeme beginnen.

Es ist keine Schande, einige Eigenschaften zu kopieren. Wenn einige Komponenten eine lokale Kopie enthalten müssen, ist das Kopieren und Synchronisieren manchmal effizienter als der Zugriff auf einen "externen" Wert.

Das Synchronisieren muss auch nicht bei jedem Frame erfolgen. Einige Komponenten können seltener synchronisiert werden als andere. Render-Komponenten sind oft ein gutes Beispiel. Diejenigen, die nicht mit den Spielern interagieren, können weniger häufig synchronisiert werden, genauso wie diejenigen, die weit entfernt sind. Die weit und außerhalb des Kamerafeldes können noch seltener synchronisiert werden.


Wenn es um Ihre Größenkomponente geht, könnte diese wahrscheinlich in Ihrer Positionskomponente gebündelt sein:

  • Nicht alle Entitäten mit einer Größe haben eine physikalische Komponente, z. B. Bereiche. Daher ist es nicht in Ihrem Interesse, sie mit der Physik zu bündeln.
  • Größe wird ohne Position wohl keine Rolle spielen
  • Alle Objekte mit einer Position werden wahrscheinlich eine Größe haben (die für Skripte, Physik, KI, Rendering ... verwendet werden kann).
  • Die Größe wird wahrscheinlich nicht bei jedem Zyklus aktualisiert, die Position jedoch möglicherweise.

Anstelle der Größe können Sie auch einen Größenmodifikator verwenden, der möglicherweise einfacher zu handhaben ist.

Bezüglich der Speicherung aller Eigenschaften in einem generischen Eigenschaftenspeichersystem ... Ich bin mir nicht sicher, ob Sie in die richtige Richtung gehen ... Konzentrieren Sie sich auf die Eigenschaften, die für Ihr Spiel von zentraler Bedeutung sind, und erstellen Sie Komponenten, die so viele verwandte Eigenschaften wie möglich bündeln. Solange Sie den Zugriff auf diese Eigenschaften ordnungsgemäß abstrahieren (z. B. durch Getter in den Komponenten, die sie benötigen), sollten Sie sie später verschieben, kopieren und synchronisieren können, ohne die Logik zu stark zu beeinträchtigen.


2
Übrigens +1 oder -1, weil mein aktueller Mitarbeiter seit dem 9. November 666 ist ... Es ist gruselig.
Coyote

1

Wenn Komponenten willkürlich zu Entitäten hinzugefügt werden können, müssen Sie die Möglichkeit haben, abzufragen, ob eine bestimmte Komponente in einer Entität vorhanden ist, und einen Verweis darauf abzurufen. Sie können also eine von ObjectComponent abgeleitete Liste von Objekten durchlaufen, bis Sie die gewünschte gefunden und zurückgegeben haben. Sie würden jedoch ein Objekt des richtigen Typs zurückgeben.

In C ++ oder C # bedeutet dies normalerweise, dass Sie eine Template-Methode für die Entität haben, wie T GetComponent<T>(). Und sobald Sie diese Referenz haben, wissen Sie genau, welche Mitgliedsdaten darin enthalten sind. Greifen Sie einfach direkt darauf zu.

In etwas wie Lua oder Python haben Sie nicht unbedingt einen expliziten Typ dieses Objekts, und das ist Ihnen wahrscheinlich auch egal. Aber auch hier können Sie einfach auf die Mitgliedsvariable zugreifen und alle Ausnahmen behandeln, die beim Versuch auftreten, auf etwas zuzugreifen, das nicht vorhanden ist.

Das Abfragen von Objekteigenschaften klingt explizit so, als ob Sie die Arbeit duplizieren würden, die die Sprache für Sie leisten kann, entweder zur Kompilierungszeit für statisch typisierte Sprachen oder zur Laufzeit für dynamisch typisierte Sprachen.


Ich verstehe, wie man stark typisierte Komponenten von einer Entität erhält (unter Verwendung von Generika oder ähnlichem). Meine Frage bezieht sich eher darauf, wohin diese Eigenschaften gehen sollen, insbesondere dann, wenn eine Eigenschaft von mehreren Komponenten verwendet wird und von keiner einzelnen Komponente behauptet werden kann, sie zu besitzen. Siehe meinen 3. und 4. Kommentar zur Frage.
George Duckett

Wählen Sie einfach eine vorhandene aus, wenn sie passt, oder faktorisieren Sie die Eigenschaft in eine neue Komponente, wenn dies nicht der Fall ist. Zum Beispiel hat Unity eine 'Transform'-Komponente, die nur die Position ist, und wenn etwas anderes die Position des Objekts ändern muss, tun sie dies über diese Komponente.
Kylotan

1

"Ich denke, dann ist mein Problem, wo ich gemeinsame Eigenschaften speichere. ZB ein Objekt als eine Position, die von einer RenderingComponent und einer PhysicsComponent verwendet wird. Überlege ich mir, wo ich die Eigenschaft ablegen soll? Soll ich einfach bleiben hat es dann in beiden Fällen die andere Abfrage ein Objekt für die Komponente, die die benötigte Eigenschaft hat? "

Die Sache ist, dass RenderingComponent die Position verwendet, aber die PhysicsComponent sie bereitstellt . Sie müssen nur jedem Benutzer mitteilen, welchen Anbieter er verwenden soll. Im Idealfall agnostisch, sonst besteht eine Abhängigkeit.

"... bei meiner Frage geht es eher darum, wohin diese Eigenschaften gehen sollen, insbesondere, wo eine Eigenschaft von mehreren Komponenten verwendet wird und von keiner einzelnen Komponente behauptet werden kann, sie zu besitzen. Siehe meinen 3. und 4. Kommentar zu der Frage."

Es gibt keine gemeinsame Regel. Hängt von der jeweiligen Immobilie ab (siehe oben).

Erstelle ein Spiel mit einer hässlichen, aber komponentenbasierten Architektur und überarbeite es dann.


Ich glaube nicht, dass ich recht verstehe, was PhysicsComponentich dann tun soll. Ich betrachte es als das Simulieren des Objekts in einer physischen Umgebung, was mich zu dieser Verwirrung führt: Nicht alle Dinge, die gerendert werden müssen, müssen simuliert werden, daher erscheint es mir falsch, sie hinzuzufügen, PhysicsComponentwenn ich sie hinzufüge, RenderingComponentweil sie eine Position enthalten das RenderingComponentnutzt. Ich könnte mir leicht vorstellen, mit einem Netz von miteinander verbundenen Komponenten zu enden, was bedeutet, dass alle / die meisten zu jeder Entität hinzugefügt werden müssen.
George Duckett

Ich hatte tatsächlich eine ähnliche Situation :). Ich habe eine PhysicsBodyComponent und eine SimpleSpatialComponent. Beide geben Position, Winkel und Größe an. Aber der erste nimmt an der Physiksimulation teil und hat zusätzliche relevante Eigenschaften, und der zweite enthält nur diese räumlichen Daten. Wenn Sie eine eigene phys-Engine haben, können Sie sogar die erstere von der letzteren erben.
Den

"Ich könnte mir leicht vorstellen, mit einem Netz von miteinander verbundenen Komponenten zu enden, was bedeutet, dass alle / die meisten zu jeder Entität hinzugefügt werden müssen." Das liegt daran, dass Sie keinen tatsächlichen Spielprototyp haben. Wir sprechen hier über einige grundlegende Komponenten. Kein Wunder, dass sie überall eingesetzt werden.
Den

1

Dein Bauch sagt dir, dass du das ThingProperty, ThingComponentund ThingManagerfür jede ThingArt von Komponente einen kleinen Overkill hast . Ich denke es ist richtig.

Sie müssen jedoch eine Möglichkeit haben, die verwandten Komponenten im Hinblick darauf zu verfolgen, welche Systeme sie verwenden, zu welcher Entität sie gehören usw.

TransformPropertywird ziemlich verbreitet sein. Aber wer ist dafür verantwortlich, das Rendering-System? Das physikalische System? Das Soundsystem? Warum muss sich eine TransformKomponente selbst aktualisieren?

Die Lösung besteht darin, jede Art von Code außerhalb von Gettern, Setzern und Initialisierern aus Ihren Eigenschaften zu entfernen. Komponenten sind Daten, die von Systemen im Spiel verwendet werden, um verschiedene Aufgaben wie Rendern, KI, Soundwiedergabe, Bewegung usw. auszuführen.

Lesen Sie mehr über Artemis: http://piemaster.net/2011/07/entity-component-artemis/

Wenn Sie sich den Code ansehen, werden Sie feststellen, dass er auf Systemen basiert, deren Abhängigkeiten als Listen von deklariert werden ComponentTypes. Sie schreiben jede Ihrer SystemKlassen und deklarieren in der Methode constructor / init, von welchen Typen das System abhängt.

Während der Einrichtung von Ebenen oder Ähnlichem erstellen Sie Ihre Entitäten und fügen ihnen Komponenten hinzu. Anschließend weisen Sie diese Entität an, Artemis Bericht zu erstatten, und Artemis ermittelt anhand der Zusammensetzung dieser Entität, welche Systeme über diese Entität informiert werden möchten.

Während der Aktualisierungsphase Ihrer Schleife haben Sie Systemnun eine Liste der zu aktualisierenden Entitäten. Jetzt können Sie Granularität der Komponenten, so dass Sie verrückt Systeme entwickeln können, bauen Einheiten aus a ModelComponent, TransformComponent, FliesLikeSupermanComponent, und SocketInfoComponent, und tun Sie etwas seltsam wie machen eine fliegende Untertasse , dass Fliegen zwischen den Kunden zu einem Multiplayer - Spiel verbunden. Okay, vielleicht nicht, aber die Idee ist, dass die Dinge entkoppelt und flexibel bleiben.

Artemis ist nicht perfekt und die Beispiele auf der Website sind ein wenig grundlegend, aber die Trennung von Code und Daten ist stark. Es ist auch gut für Ihren Cache, wenn Sie es richtig machen. Artemis macht das wahrscheinlich nicht richtig, aber es ist gut, daraus zu lernen.

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.