Komponentenentitätssystem - Aktualisierungen und Anrufaufträge

10

Damit Komponenten in der Lage sind, jeden Frame zu aktualisieren (und diese Funktionalität aus Komponenten herauszulassen, die nicht benötigt werden), kam mir die Idee, eine UpdateComponent-Komponente zu erstellen. Andere Komponenten wie MovableComponent(die Geschwindigkeit halten) würden von der IUpdatableabstrakten Klasse erben . Dies erzwingt MovableComponentdie Implementierung einer Update(gametime dt)Methode und einer anderen RegisterWithUpdater(), die UpdateComponenteinen Zeiger auf die gibt MovableComponent. Viele Komponenten könnten dies tun und dann UpdateComponentalle ihre Update(gametime dt)Methoden aufrufen , ohne sich darum kümmern zu müssen, wer oder was sie sind.

Meine Fragen sind:

  1. Scheint dies etwas zu sein, das normal ist oder von irgendjemandem benutzt wird? Ich kann nichts zu diesem Thema finden.
  2. Wie könnte ich eine Reihenfolge für die Komponenten wie Physik aufrechterhalten und dann die Position ändern? Ist das überhaupt nötig?
  3. Was sind andere Möglichkeiten, um sicherzustellen, dass Komponenten, die in jedem Frame verarbeitet werden sollen, tatsächlich verarbeitet werden?

BEARBEITEN
Ich denke, ich werde überlegen, wie ich dem Entitätsmanager eine Liste der Typen geben kann, die aktualisiert werden können. Dann können ALLE Komponenten dieses Typs aktualisiert werden, anstatt sie pro Entität zu verwalten (die ohnehin nur Indizes in meinem System sind).

Immer noch. Meine Fragen bleiben für mich gültig. Ich weiß nicht, ob dies vernünftig / normal ist oder was andere dazu neigen.

Auch die Leute bei Insomniac sind großartig! /BEARBEITEN

Eingekochter Code für das vorherige Beispiel:

class IUpdatable
{
public:
    virtual void Update(float dt) = 0;
protected:
    virtual void RegisterAsUpdatable() = 0;
};

class Component
{
    ...
};

class MovableComponent: public Component, public IUpdatable
{
public:
    ...
    virtual void Update(float dt);
private:
    ...
    virtual void RegisterWithUpdater();
};

class UpdateComponent: public Component
{
public:
    ...
    void UpdateAll();
    void RegisterUpdatable(Component* component);
    void RemoveUpdatable(Component* component);
private:
    ...
    std::set<Component*> updatables_;
};
ptpaterson
quelle
Haben Sie ein Beispiel für eine Komponente, die nicht aktualisiert werden kann? Das scheint ziemlich nutzlos. Fügen Sie die Aktualisierungsfunktion als virtuelle Funktion in die Komponente ein. Dann aktualisieren Sie einfach alle Komponenten in der Entität, ohne dass die "UpdateComponent"
Maik Semder
Einige Komponenten ähneln eher Datenhaltern. Wie PositionComponent. Viele Objekte können eine Position haben, aber wenn sie stationär sind, was eine Mehrheit sein könnte, können sich all diese zusätzlichen virtuellen Anrufe aufbauen. Oder sagen Sie eine HealthComponent. Das muss nicht in jedem Frame etwas tun, sondern muss nur geändert werden, wenn es nötig ist. Vielleicht ist der Overhead nicht so schlimm, aber meine UpdateComponent war ein Versuch, Update () nicht in JEDER Komponente zu haben.
Ptpaterson
5
Eine Komponente, die nur Daten enthält und keine Funktionalität, um sie im Laufe der Zeit zu ändern, ist per Definition keine Komponente. Es muss einige Funktionen in der Komponente geben, die sich im Laufe der Zeit ändern und daher eine Aktualisierungsfunktion benötigen. Wenn Ihre Komponente keine Aktualisierungsfunktion benötigt, wissen Sie, dass es sich nicht um eine Komponente handelt, und sollten das Design überdenken. Eine Aktualisierungsfunktion in der Gesundheitskomponente ist sinnvoll. Sie möchten beispielsweise, dass Ihr NPC im Laufe der Zeit von Schäden geheilt wird.
Maik Semder
@ Maik Mein Punkt war NICHT, dass es sich um Komponenten handelt, die sich niemals ändern werden. Ich stimme zu, das ist albern. Sie müssen nur nicht jeden Frame aktualisieren, sondern müssen benachrichtigt werden, um ihre Informationen bei Bedarf zu ändern. Im Falle der Gesundheit im Laufe der Zeit würde es eine Gesundheitsbonuskomponente geben, die ein Update hat. Ich glaube nicht, dass es einen Grund gibt, beides zu kombinieren.
Ptpaterson
Ich möchte auch darauf hinweisen, dass der Code, den ich gepostet habe, nur das enthält, was zur Erläuterung des UpdateComponent-Konzepts erforderlich ist. Es schließt alle anderen Formen der Kommunikation zwischen Komponenten aus.
Ptpaterson

Antworten:

16

Einer der Hauptvorteile eines Komponentensystems ist die Möglichkeit, Caching-Muster zu nutzen - guter Icache und Vorhersage, weil Sie immer wieder denselben Code ausführen, guter Dcache, weil Sie die Objekte in homogenen Pools zuordnen können und weil die vtables, wenn jeden, bleib heiß.

Durch die Art und Weise, wie Sie Ihre Komponenten strukturiert haben, verschwindet dieser Vorteil vollständig und kann im Vergleich zu einem vererbungsbasierten System zu einer Leistungsbeeinträchtigung werden, da Sie mit vtables weitaus mehr virtuelle Anrufe und mehr Objekte tätigen.

Sie sollten Pools pro Typ speichern und jeden Typ unabhängig iterieren, um Aktualisierungen durchzuführen.

Scheint dies etwas zu sein, das normal ist oder von irgendjemandem benutzt wird? Ich kann nichts zu diesem Thema finden.

Es ist nicht so häufig in großen Spielen, weil es nicht vorteilhaft ist. Es ist in vielen Spielen üblich, aber technisch nicht interessant, deshalb schreibt niemand darüber.

Wie könnte ich eine Reihenfolge für die Komponenten wie Physik aufrechterhalten und dann die Position ändern? Ist das überhaupt nötig?

Code in Sprachen wie C ++ hat eine natürliche Möglichkeit, die Ausführung zu ordnen: Geben Sie die Anweisungen in dieser Reihenfolge ein.

for (PhysicsComponent *c : physics_components)
    c->update(dt);
for (PositionComponent *c : position_components)
    c->update(dt);

In Wirklichkeit macht das keinen Sinn, da kein robustes Physiksystem so aufgebaut ist - Sie können nicht ein einziges Physikobjekt aktualisieren. Stattdessen würde der Code eher so aussehen:

physics_step();
for (PositionComponent *c : position_components)
    c->update(dt);
// Updates the position data from the physics data.

quelle
Vielen Dank. Ich habe dieses ganze Projekt mit Blick auf Daten (nicht wie datengesteuert) gestartet, um Cache-Fehler und dergleichen zu vermeiden. Aber als ich anfing zu programmieren, beschloss ich, etwas zu machen, das dot com funktionierte. Es stellte sich heraus, dass es mir schwerer gemacht hat. Und im Ernst, dieser Artikel über Schlaflosigkeit war großartig!
Ptpaterson
@ Joe +1 für eine sehr gute Antwort. Obwohl ich gerne wissen würde, was ein Icache und ein Dcache sind. Vielen Dank
Ray Dey
Anweisungscache und Datencache. Jeder Einführungstext zur Computerarchitektur sollte sie abdecken.
@ptpaterson: Beachten Sie, dass die Insomniac-Darstellung einen kleinen Fehler enthält: Die Komponentenklasse muss ihren Dienstplanindex enthalten, oder Sie benötigen eine separate Zuordnung von Poolindizes zu Dienstplanindizes.
3

Was Sie sprechen, ist vernünftig und ziemlich häufig, denke ich. Dies könnte Ihnen weitere Informationen geben.

großartig
quelle
Ich glaube, ich bin vor ein paar Wochen auf diesen Artikel gestoßen. Ich war in der Lage, einige sehr einfache Versionen eines komponentenbasierten Entitätssystems zusammen zu codieren, von denen jede sehr unterschiedlich ist, wenn ich verschiedene Dinge ausprobiere. Ich versuche, alle Artikel und Beispiele zu etwas zusammenzuführen, das ich will und kann. Vielen Dank!
Ptpaterson
0

Ich mag diese Ansätze:

Kurz: Vermeiden Sie es, das Aktualisierungsverhalten innerhalb der Komponenten beizubehalten. Komponenten sind kein Verhalten. Das Verhalten (einschließlich Aktualisierungen) kann in einigen Subsystemen mit einer Instanz implementiert werden. Dieser Ansatz kann auch dazu beitragen, ähnliche Verhaltensweisen für mehrere Komponenten stapelweise zu verarbeiten (möglicherweise mithilfe von parallel_for- oder SIMD-Anweisungen für Komponentendaten).

Die IUpdatable-Idee und Update-Methode (gametime dt) scheint etwas zu restriktiv zu sein und führt zusätzliche Abhängigkeiten ein. Es kann in Ordnung sein, wenn Sie keinen Subsystemansatz verwenden, aber wenn Sie ihn verwenden, ist IUpdatable eine redundante Hierarchieebene. Schließlich sollte das MovingSystem wissen, dass es die Location- und / oder Velocity-Komponenten für alle Entitäten mit diesen Komponenten direkt aktualisieren muss, sodass keine IUpdatable-Zwischenkomponente erforderlich ist.

Sie können die aktualisierbare Komponente jedoch als Mechanismus verwenden, um die Aktualisierung einiger Entitäten zu überspringen, obwohl diese über Standort- und / oder Geschwindigkeitskomponenten verfügen. Die aktualisierbare Komponente könnte ein Bool-Flag haben, das gesetzt werden kann falseund das jedem aktualisierbaren Subsystem signalisiert, dass diese bestimmte Entität derzeit nicht aktualisiert werden sollte (obwohl in diesem Kontext Freezable ein geeigneterer Name für die Komponente zu sein scheint).

JustAMartin
quelle