Warum ist es eine schlechte Idee, Methoden in Entities und Components zu speichern? (Zusammen mit einigen anderen Entity System-Fragen.)

16

Dies ist eine Fortsetzung dieser Frage, die ich beantwortet habe, die sich jedoch mit einem viel spezifischeren Thema befasst.

Diese Antwort hat mir geholfen, Entity Systems noch besser zu verstehen als den Artikel.

Ich habe den (ja, den) Artikel über Entity Systems gelesen und darin Folgendes erfahren:

Entitäten sind nur eine ID und ein Array von Komponenten (der Artikel besagt, dass das Speichern von Entitäten in Komponenten keine gute Methode ist, aber keine Alternative darstellt).
Komponenten sind Daten, die angeben, was mit einer bestimmten Entität getan werden kann.
Systeme sind die "Methoden", sie führen die Manipulation von Daten an Entitäten durch.

Dies scheint in vielen Situationen sehr praktisch zu sein, aber die Tatsache, dass Komponenten nur Datenklassen sind, stört mich. Wie könnte ich beispielsweise meine Vector2D-Klasse (Position) in einem Entitätssystem implementieren?

Die Vector2D-Klasse enthält Daten: x- und y-Koordinaten, aber auch Methoden , die für ihre Nützlichkeit von entscheidender Bedeutung sind und die Klasse von nur einem Array mit zwei Elementen unterscheiden. Beispiel Methoden sind: add(), rotate(point, r, angle), substract(), normalize(), und alle anderen Standards, nützlich und absolut Methoden erforderlich , dass die Positionen (die Instanzen der Vector2D Klasse) haben sollten.

Wenn die Komponente nur ein Datenbehälter wäre, könnte sie diese Methoden nicht haben!

Eine Lösung, die wahrscheinlich auftauchen könnte, wäre, sie in Systemen zu implementieren, aber das scheint sehr kontraintuitiv zu sein. Diese Methoden sind Dinge, die ich jetzt ausführen möchte. Sie müssen vollständig und einsatzbereit sein. Ich möchte nicht warten, MovementSystembis einige teure Nachrichten gelesen wurden, die ihn anweisen, eine Berechnung für die Position einer Entität durchzuführen!

Und in dem Artikel heißt es ganz klar, dass nur Systeme eine Funktion haben sollten , und die einzige Erklärung dafür, die ich finden konnte, war "OOP vermeiden". Erstens verstehe ich nicht, warum ich auf die Verwendung von Methoden in Entitäten und Komponenten verzichten soll. Der Speicheraufwand ist praktisch gleich, und in Verbindung mit Systemen sollten diese auf interessante Weise sehr einfach zu implementieren und zu kombinieren sein. Die Systeme könnten zum Beispiel nur Entitäten / Komponenten mit grundlegender Logik versorgen, die die Implementierung selbst kennen. Wenn Sie mich fragen, ist dies im Grunde genommen die Übernahme der Goodies sowohl von ES als auch von OOP. Dies ist laut dem Autor des Artikels nicht möglich, scheint mir jedoch eine gute Praxis zu sein.

Denken Sie so darüber nach. Es gibt viele verschiedene Arten von Zeichenobjekten in einem Spiel. Einfache alte Bilder, Animationen ( update(), getCurrentFrame()usw.), Kombinationen dieser primitiven Typen und alle von ihnen könnten einfach eine draw()Methode für das Rendersystem bereitstellen, das sich dann nicht nur darum kümmern muss, wie das Sprite einer Entität implementiert wird über die Schnittstelle (zeichnen) und die Position. Und dann bräuchte ich nur ein Animationssystem, das animationsspezifische Methoden aufruft, die nichts mit dem Rendern zu tun haben.

Und nur eine andere Sache ... Gibt es wirklich eine Alternative zu Arrays, wenn es um die Speicherung von Komponenten geht? Ich sehe keinen anderen Ort für die Speicherung von Komponenten als Arrays innerhalb einer Entity-Klasse ...

Vielleicht ist dies ein besserer Ansatz: Speichern Sie Komponenten als einfache Eigenschaften von Entitäten. Beispielsweise würde eine Positionskomponente angeklebt entity.position.

Der einzige andere Weg wäre, eine Art seltsame Nachschlagetabelle in Systemen zu haben, die auf verschiedene Entitäten verweist. Aber das scheint sehr ineffizient und komplizierter zu sein, als nur Komponenten in der Entität zu speichern.

jcora
quelle
Alexandre, nehmen Sie gerade viele Änderungen vor, um ein weiteres Abzeichen zu erhalten? Weil das ungezogen ungezogen ist, stößt es immer wieder an eine Menge uralter Fäden.
jhocking

Antworten:

25

Ich denke, es ist völlig in Ordnung, über einfache Methoden zum Zugreifen, Aktualisieren oder Bearbeiten der Daten in Komponenten zu verfügen. Ich denke, die Funktionalität, die von Komponenten ferngehalten werden sollte, ist logische Funktionalität. Utility-Funktionen sind in Ordnung. Denken Sie daran, dass das Entity-Component-System nur eine Richtlinie ist und keine strengen Regeln, die Sie befolgen müssen. Gehen Sie nicht aus dem Weg, um ihnen zu folgen. Wenn Sie der Meinung sind, dass es sinnvoller ist, es in eine Richtung zu tun, dann tun Sie es auf diese Weise :)

BEARBEITEN

Um zu verdeutlichen, ist es Ihr Ziel, OOP nicht zu vermeiden . Das wäre in den meisten der heute gebräuchlichen Sprachen ziemlich schwierig. Sie versuchen, die Vererbung zu minimieren , was ein großer Aspekt von OOP ist, aber nicht erforderlich. Sie möchten die Vererbung von Object-> MobileObject-> Creature-> Bipedal-> Human Type aufheben.

Es ist jedoch in Ordnung, eine Vererbung zu haben! Sie haben es mit einer Sprache zu tun, die stark von der Vererbung beeinflusst wird. Es ist sehr schwierig, keine davon zu verwenden. Beispielsweise können Sie eine ComponentKlasse oder Schnittstelle haben, die alle Ihre anderen Komponenten erweitern oder implementieren. Gleiche Sache mit deiner SystemKlasse. Das macht die Sache viel einfacher. Ich empfehle Ihnen dringend, einen Blick auf das Artemis- Framework zu werfen . Es ist Open Source und hat einige Beispielprojekte. Öffnen Sie diese Dinge und sehen Sie, wie es funktioniert.

Für Artemis werden Entitäten einfach in einem Array gespeichert. Ihre Komponenten werden jedoch in einem Array oder Arrays (getrennt von den Entitäten) gespeichert. Das Array der obersten Ebene gruppiert das Array der unteren Ebene nach Komponententyp. Jeder Komponententyp hat also ein eigenes Array. Das Array der unteren Ebene wird nach Entitäts-ID indiziert. (Jetzt bin ich mir nicht sicher, ob ich es so machen würde, aber so wird es hier gemacht). Artemis verwendet Entitäts-IDs erneut, sodass die maximale Entitäts-ID nicht größer als Ihre aktuelle Anzahl von Entitäten wird. Sie können jedoch weiterhin über spärliche Arrays verfügen, wenn die Komponente keine häufig verwendete Komponente ist. Wie auch immer, ich werde das nicht zu sehr auseinander nehmen. Diese Methode zum Speichern von Entitäten und deren Komponenten scheint zu funktionieren. Ich denke, es wäre ein großartiger erster Versuch, ein eigenes System zu implementieren.

Die Entitäten und Komponenten werden in einem separaten Manager gespeichert.

Die Strategie, die Sie erwähnen, Entities dazu zu bringen, ihre eigenen Komponenten zu speichern ( entity.position), widerspricht dem Entity-Komponententhema, ist jedoch völlig akzeptabel, wenn Sie der Meinung sind, dass dies am sinnvollsten ist.

MichaelHouse
quelle
1
Hmm, das vereinfacht die Situation sehr, danke! Ich dachte, es gäbe etwas Magisches wie "Du wirst es später bereuen" und ich konnte es einfach nicht sehen!
JCORA
1
Na, ich benutze sie total in meinem Entitätskomponentensystem. Ich habe sogar einige Komponenten, die von einem gemeinsamen Elternteil erben, keuchen . Ich glaube, Sie würden es nur bedauern, wenn Sie versuchen würden, mit solchen Methoden nicht umzugehen. Es geht darum, das zu tun, was für Sie am sinnvollsten ist. Wenn es sinnvoll ist, Vererbung zu verwenden oder einige Methoden in eine Komponente einzufügen, wählen Sie diese aus.
MichaelHouse
2
Ich habe aus meiner letzten Antwort zu diesem Thema gelernt. Disclaimer: Ich sage nicht, dass dies der Weg ist, es zu tun. :)
MichaelHouse
1
Ja, ich weiß, wie entmutigend es sein kann, ein neues Paradigma zu lernen. Glücklicherweise können Sie Aspekte des alten Paradigmas verwenden, um die Dinge einfacher zu machen! Ich habe meine Antwort mit Speicherinformationen aktualisiert. Wenn Sie sich Artemis ansehen, sehen Sie sich das EntityManageran, in dem die Dinge aufbewahrt werden.
MichaelHouse
1
Nett! Das wird ein ziemlich süßer Motor, wenn es fertig ist. Viel Glück damit! Vielen Dank für interessante Fragen.
MichaelHouse
10

Ich stimme diesem Artikel nicht besonders zu, daher wird meine Antwort meiner Meinung nach etwas kritisch sein.

Dies scheint in vielen Situationen sehr praktisch zu sein, aber die Tatsache, dass Komponenten nur Datenklassen sind, stört mich. Wie könnte ich beispielsweise meine Vector2D-Klasse (Position) in einem Entitätssystem implementieren?

Damit soll nicht sichergestellt werden, dass nichts in Ihrem Programm etwas anderes als eine Entitäts-ID, eine Komponente oder ein System ist - es soll sichergestellt werden, dass Entitätsdaten und -verhalten durch das Zusammensetzen von Objekten erstellt werden, anstatt entweder einen komplexen Vererbungsbaum zu verwenden oder es zu versuchen Fassen Sie alle möglichen Funktionen in einem Objekt zusammen. Um diese Komponenten und Systeme zu implementieren, werden Sie mit Sicherheit normale Daten wie Vektoren haben, die in den meisten Sprachen am besten als Klasse dargestellt werden.

Ignorieren Sie das Bit im Artikel, das besagt, dass dies nicht OOP ist - es ist genauso OOP wie jeder andere Ansatz. Wenn die meisten Compiler oder Sprachlaufzeiten Objektmethoden implementieren, ist dies im Grunde wie jede andere Funktion, außer dass es ein verstecktes Argument namens thisoder gibt self, das auf eine Stelle im Speicher verweist, an der die Daten dieses Objekts gespeichert sind. In einem komponentenbasierten System kann die Entitäts-ID verwendet werden, um zu ermitteln, wo sich die relevanten Komponenten (und damit Daten) für eine bestimmte Entität befinden. Daher entspricht die Entity-ID einem this / self-Zeiger, und die Konzepte sind im Grunde dasselbe, nur ein wenig neu angeordnet.

Und in dem Artikel heißt es ganz klar, dass nur Systeme eine Funktion haben sollten, und die einzige Erklärung dafür, die ich finden konnte, war "OOP vermeiden". Erstens verstehe ich nicht, warum ich auf die Verwendung von Methoden in Entitäten und Komponenten verzichten soll.

Gut. Methoden sind eine effektive Methode, um Ihren Code zu organisieren. Das Wichtigste, was Sie von der Idee "OOP vermeiden" wegnehmen sollten, ist zu vermeiden, Vererbung überall zu verwenden, um die Funktionalität zu erweitern. Teilen Sie die Funktionalität stattdessen in Komponenten auf, die kombiniert werden können, um dasselbe zu tun.

Denken Sie so darüber nach. Es gibt viele verschiedene Arten von Zeichenobjekten in einem Spiel. Einfache alte Bilder, Animationen (update (), getCurrentFrame () usw.), Kombinationen dieser primitiven Typen, und alle von ihnen könnten dem Rendersystem einfach eine draw () -Methode bereitstellen [...]

Die Idee eines komponentenbasierten Systems ist, dass Sie keine separaten Klassen für diese haben würden, sondern eine einzelne Objekt- / Entitätsklasse, und das Bild wäre ein Objekt / eine Entität mit einem ImageRenderer, die Animationen wären ein Objekt / Entität, die über einen AnimationRenderer usw. verfügt. Die relevanten Systeme wissen, wie diese Komponenten gerendert werden, sodass keine Basisklasse mit einer Draw () - Methode erforderlich ist.

[...] die sich dann nicht darum kümmern muss, wie das Sprite einer Entität implementiert wird, sondern nur um die Schnittstelle (draw) und die Position. Und dann bräuchte ich nur ein Animationssystem, das animationsspezifische Methoden aufruft, die nichts mit dem Rendern zu tun haben.

Sicher, aber das funktioniert nicht gut mit Komponenten. Sie haben 3 Möglichkeiten:

  • Jede Komponente implementiert diese Schnittstelle und verfügt über eine Draw () -Methode, auch wenn nichts gezeichnet wird. Wenn Sie dies für jede Funktionalität tun würden, würden die Komponenten ziemlich hässlich aussehen.
  • Nur Komponenten, die etwas zu zeichnen haben, implementieren die Schnittstelle - aber wer entscheidet, auf welchen Komponenten Draw () aufgerufen wird? Muss ein System irgendwie jede Komponente abfragen, um festzustellen, welche Schnittstelle unterstützt wird? Das wäre fehleranfällig und möglicherweise schwierig in einigen Sprachen zu implementieren.
  • Komponenten werden immer nur von ihrem eigenen System gehandhabt (wie im verlinkten Artikel beschrieben). In diesem Fall spielt die Schnittstelle keine Rolle, da ein System genau weiß, mit welcher Klasse oder welchem ​​Objekttyp es arbeitet.

Und nur eine andere Sache ... Gibt es wirklich eine Alternative zu Arrays, wenn es um die Speicherung von Komponenten geht? Ich sehe keinen anderen Ort für die Speicherung von Komponenten als Arrays innerhalb einer Entity-Klasse ...

Sie können die Komponenten im System ablegen. Das Array ist nicht das Problem, aber wo Sie die Komponente speichern.

Kylotan
quelle
+1 Danke für einen anderen Standpunkt. Es ist gut, ein paar zu bekommen, wenn man sich mit einem so zweideutigen Thema befasst! Wenn Sie Komponenten im System speichern, bedeutet dies, dass Komponenten immer nur von einem System geändert werden können? Beispielsweise würden sowohl das Zeichnungssystem als auch das Bewegungssystem auf die Positionskomponente zugreifen. Wo lagerst du es?
MichaelHouse
Nun, sie würden nur einen Zeiger auf diese Komponenten speichern, die, soweit es mich betrifft, irgendwo sein können ... Warum würden Sie Komponenten auch in Systemen speichern? Hat das einen Vorteil?
Jcora
Habe ich recht, @Kylotan? So würde ich es machen, es scheint logisch ...
jcora
Im Beispiel von Adam / T-Machine ist beabsichtigt, dass es 1 System pro Komponente gibt, aber ein System könnte sicherlich auf andere Komponenten zugreifen und diese modifizieren. (Dies behindert die Multithreading-Vorteile von Komponenten, aber das ist eine andere Sache.)
Kylotan
1
Das Speichern von Komponenten im System ermöglicht eine bessere Referenzlokalität für dieses System. Dieses System arbeitet (im Allgemeinen) nur mit diesen Daten. Warum also den gesamten Arbeitsspeicher Ihres Computers von Entität zu Entität durchsuchen, um sie abzurufen? Es hilft auch bei der Parallelität, da Sie ein ganzes System und seine Daten in MMOs auf einen Kern oder Prozessor (oder sogar auf einen separaten Computer) legen können. Diese Vorteile verringern sich erneut, wenn ein System auf mehr als einen Komponententyp zugreift. Dies sollte berücksichtigt werden, wenn entschieden wird, wo die Verantwortlichkeiten für Komponenten / Systeme aufgeteilt werden sollen.
Kylotan
2

Ein Vektor ist Daten. Die Funktionen ähneln eher Utility-Funktionen - sie sind nicht spezifisch für diese Dateninstanz, sondern können unabhängig auf alle Vektoren angewendet werden. Eine schöne Art darüber nachzudenken ist: Können diese Funktionen als statische Methoden umgeschrieben werden? Wenn ja, ist es nur ein Dienstprogramm.

Matt Kemp
quelle
Ich weiß das, aber das Problem ist, dass das Aufrufen von Methoden schneller ist und von einem System vor Ort durchgeführt werden kann, oder von allem anderen, das möglicherweise zur Manipulation der Position einer Entität erforderlich ist. Ich erklärte, dass die Frage viel mehr zu bieten hat als nur das, glaube ich.
JCORA