Nachdem ich einige Dokumentationen über Entity-Component-Systeme gelesen hatte, entschied ich mich, meine zu implementieren. Bisher habe ich eine Weltklasse, die die Entitäten und den Systemmanager (Systeme) enthält, eine Entitätsklasse, die die Komponenten als std :: map enthält, und einige Systeme. Ich halte Entitäten als std :: vector in World. Bisher kein Problem. Was mich verwirrt, ist die Iteration von Entitäten. Ich kann mir das nicht genau vorstellen, also kann ich diesen Teil immer noch nicht implementieren. Sollte jedes System eine lokale Liste von Entitäten enthalten, an denen sie interessiert sind? Oder sollte ich einfach die Entitäten in der Weltklasse durchlaufen und eine verschachtelte Schleife erstellen, um Systeme zu durchlaufen und zu überprüfen, ob die Entität die Komponenten enthält, an denen das System interessiert ist? Ich meine :
for (entity x : listofentities) {
for (system y : listofsystems) {
if ((x.componentBitmask & y.bitmask) == y.bitmask)
y.update(x, deltatime)
}
}
Ich denke jedoch, dass ein Bitmaskensystem die Flexibilität beim Einbetten einer Skriptsprache blockieren wird. Oder lokale Listen für jedes System erhöhen die Speichernutzung für Klassen. Ich bin furchtbar verwirrt.
quelle
Antworten:
Es ist ein traditioneller Raum-Zeit-Kompromiss .
Während das Durchlaufen aller Entitäten und das Überprüfen ihrer Signaturen direkt zum Code führt, kann es mit zunehmender Anzahl von Systemen ineffizient werden. Stellen Sie sich ein spezialisiertes System vor (lassen Sie es eingeben), das unter Tausenden von nicht verwandten Entitäten nach seiner wahrscheinlich einzigen interessierenden Entität sucht .
Trotzdem kann dieser Ansatz abhängig von Ihren Zielen immer noch gut genug sein.
Wenn Sie sich Sorgen um die Geschwindigkeit machen, sollten Sie natürlich auch andere Lösungen in Betracht ziehen.
Genau. Dies ist ein Standardansatz, der Ihnen eine anständige Leistung bieten sollte und relativ einfach zu implementieren ist. Der Speicheraufwand ist meiner Meinung nach vernachlässigbar - wir sprechen über das Speichern von Zeigern.
Nun, wie diese "Listen von Interesse" zu pflegen sind, mag nicht so offensichtlich sein. Was den Datencontainer betrifft,
std::vector<entity*> targets
ist die Klasse innerhalb des Systems vollkommen ausreichend. Was ich jetzt mache, ist Folgendes:Wann immer ich einer Entität eine Komponente hinzufüge:
Iterierte durch alle Systeme der Welt und wenn es ein System ist , dessen Unterschrift nicht der Fall ist die aktuelle Signatur des Unternehmens übereinstimmen und nicht die neue Signatur übereinstimmen, wird es offensichtlich , dass wir den Zeiger auf unser Unternehmen dort push_back sollte.
Das Entfernen einer Entität ist völlig analog, mit dem einzigen Unterschied, den wir entfernen, wenn ein System mit unserer aktuellen Signatur übereinstimmt (was bedeutet, dass die Entität vorhanden war) und nicht mit der neuen Signatur übereinstimmt (was bedeutet, dass die Entität nicht mehr vorhanden sein sollte ).
Jetzt können Sie die Verwendung von std :: list in Betracht ziehen, da das Entfernen aus dem Vektor O (n) ist, ganz zu schweigen davon, dass Sie jedes Mal, wenn Sie aus der Mitte entfernen, einen großen Datenblock verschieben müssten. Eigentlich müssen Sie das nicht - da uns die Verarbeitungsreihenfolge auf dieser Ebene egal ist, können wir einfach std :: remove aufrufen und damit leben, dass wir bei jedem Löschen nur eine O (n) -Suche nach unserer durchführen müssen zu entfernende Entität.
std :: list würde Ihnen O (1) entfernen geben, aber auf der anderen Seite haben Sie ein bisschen zusätzlichen Speicheraufwand. Denken Sie auch daran, dass Sie die meiste Zeit Entitäten verarbeiten und nicht entfernen - und dies wird mit std :: vector sicherlich schneller erledigt.
Wenn Sie sehr leistungskritisch sind, können Sie auch ein anderes Datenzugriffsmuster in Betracht ziehen , aber in beiden Fällen führen Sie eine Art "Listen von Interesse". Denken Sie jedoch daran, dass es kein Problem sein sollte, die Entitätsverarbeitungsmethoden des Systems zu verbessern, wenn Ihre Framerate aufgrund dieser Abstraktionen so stark abstrahiert bleibt. Wählen Sie daher zunächst die Methode, die für Sie am einfachsten zu codieren ist dann profilieren und bei Bedarf verbessern.
quelle
Es gibt einen Ansatz, der erwägenswert ist, wo jedes System die mit sich selbst verbundenen Komponenten besitzt und die Entitäten nur auf sie verweisen. Grundsätzlich
Entity
sieht Ihre (vereinfachte) Klasse folgendermaßen aus:Wenn Sie sagen, dass eine
RigidBody
Komponente an eine angehängt istEntity
, fordern Sie sie von IhremPhysics
System an. Das System erstellt die Komponente und lässt die Entität einen Zeiger darauf behalten. Ihr System sieht dann so aus:Dies mag zunächst etwas kontraintuitiv aussehen, aber der Vorteil liegt in der Art und Weise, wie Komponentenentitätssysteme ihren Status aktualisieren. Oft durchlaufen Sie Ihre Systeme und fordern sie auf, die zugehörigen Komponenten zu aktualisieren
Die Stärke, alle Komponenten des Systems im zusammenhängenden Speicher zu haben, besteht darin, dass Ihr System, wenn es über jede Komponente iteriert und diese aktualisiert, im Grunde nur noch etwas tun muss
Es muss nicht über alle Entitäten iterieren, die möglicherweise keine Komponente haben, die aktualisiert werden muss, und es kann auch zu einer sehr guten Cache-Leistung führen, da alle Komponenten zusammenhängend gespeichert werden. Dies ist einer, wenn nicht der größte Vorteil dieser Methode. Sie haben oft Hunderte und Tausende von Komponenten gleichzeitig und können genauso gut versuchen, so leistungsfähig wie möglich zu sein.
An diesem Punkt
World
durchlaufen Sie nur die Systeme und rufen sieupdate
auf, ohne dass Sie auch Entitäten iterieren müssen. Es ist (imho) besseres Design, weil dann die Verantwortlichkeiten der Systeme viel klarer sind.Natürlich gibt es eine Vielzahl solcher Designs, daher müssen Sie die Anforderungen Ihres Spiels sorgfältig abwägen und das am besten geeignete auswählen, aber wie wir hier sehen können, können manchmal die kleinen Designdetails einen Unterschied machen.
quelle
Meiner Meinung nach besteht eine gute Architektur darin, eine Komponentenschicht in den Entitäten zu erstellen und die Verwaltung jedes Systems in dieser Komponentenschicht zu trennen. Das Logiksystem verfügt beispielsweise über einige Logikkomponenten, die sich auf ihre Entität auswirken, und speichert die gemeinsamen Attribute, die für alle Komponenten in der Entität gemeinsam genutzt werden.
Wenn Sie danach die Objekte jedes Systems an verschiedenen Punkten oder in einer bestimmten Reihenfolge verwalten möchten, ist es besser, eine Liste der aktiven Komponenten in jedem System zu erstellen. Alle Listen von Zeigern, die Sie in den Systemen erstellen und verwalten können, sind weniger als eine geladene Ressource.
quelle