Wie werden Physik- oder Grafikkomponenten normalerweise in ein komponentenorientiertes System eingebaut?

19

Ich habe die letzten 48 Stunden mit dem Lesen von Object Component-Systemen verbracht und bin jetzt bereit, mit der Implementierung zu beginnen. Ich habe die Basisklassen Object und Component erstellt, aber jetzt, da ich mit dem Erstellen der eigentlichen Komponenten beginnen muss, bin ich ein bisschen verwirrt. Wenn ich sie in Bezug auf HealthComponent oder etwas, das im Grunde genommen nur eine Eigenschaft wäre, betrachte, ist dies absolut sinnvoll. Wenn es sich um eine allgemeinere Physik- / Grafikkomponente handelt, bin ich etwas verwirrt.

So sieht meine Object-Klasse bisher aus (Wenn Sie Änderungen bemerken, die ich vornehmen sollte, lassen Sie es mich bitte wissen, noch neu in dieser Klasse) ...

typedef unsigned int ID;

class GameObject
{
public:

    GameObject(ID id, Ogre::String name = "");
    ~GameObject();

    ID &getID();
    Ogre::String &getName();

    virtual void update() = 0;

    // Component Functions
    void addComponent(Component *component);
    void removeComponent(Ogre::String familyName);

    template<typename T>
    T* getComponent(Ogre::String familyName)
    {
        return dynamic_cast<T*>(m_components[familyName]);
    }

protected:

    // Properties
    ID m_ID;
    Ogre::String m_Name;
    float m_flVelocity;
    Ogre::Vector3 m_vecPosition;

    // Components
    std::map<std::string,Component*> m_components;
    std::map<std::string,Component*>::iterator m_componentItr;
};

Das Problem, auf das ich stoße, ist, was die allgemeine Bevölkerung in Komponenten wie Physik / Grafik stecken würde. Für Ogre (meine Rendering-Engine) bestehen die sichtbaren Objekte aus mehreren Ogre :: SceneNode (möglicherweise mehreren), um sie an die Szene anzuhängen, Ogre :: Entity (möglicherweise mehreren), um die sichtbaren Netze anzuzeigen, und so weiter. Wäre es am besten, einfach mehrere GraphicComponent's zum Objekt hinzuzufügen und jede GraphicComponent mit einem SceneNode / Entity umgehen zu lassen, oder ist die Idee, eine von jeder Component zu haben, die benötigt wird?

Für die Physik bin ich noch verwirrter. Ich nehme an, dass Sie vielleicht einen RigidBody erstellen und die Masse / interia / etc. Verfolgen. würde Sinn machen. Es fällt mir jedoch schwer zu überlegen, wie ich bestimmte Elemente in eine Komponente einbauen kann.

Sobald ich ein paar dieser "erforderlichen" Komponenten fertiggestellt habe, wird es meiner Meinung nach viel sinnvoller sein. Ab sofort bin ich allerdings noch ein bisschen ratlos.

Aidan Knight
quelle

Antworten:

32

Zunächst einmal, Sie , wenn Build - Komponenten-basierte Systeme, die Sie nicht haben , um den Ansatz des Drehens alles in einer Komponente. Eigentlich sollte man das nicht - es ist so etwas wie ein Neophytenfehler. Meiner Erfahrung nach besteht der beste Weg, Rendering- und Physiksysteme in einer komponentenbasierten Architektur zusammenzufügen, darin, diese Komponenten zu etwas mehr als blöden Containern für das reale Rendering- oder Physikprimitivobjekt zu machen.

Dies hat den zusätzlichen Vorteil, dass das Rendering- und das Physiksystem vom Komponentensystem entkoppelt bleiben.

Für Physiksysteme zum Beispiel führt der Physiksimulator im Allgemeinen eine große Liste von Starrkörperobjekten, die bei jedem Tick bearbeitet werden. Ihr Komponentensystem spielt damit keine Rolle - Ihre Physikkomponente speichert einfach einen Verweis auf den starren Körper, der die physischen Aspekte des Objekts darstellt, zu dem die Komponente gehört, und übersetzt alle Nachrichten aus dem Komponentensystem in geeignete Manipulationen des starren Körpers.

Ähnlich wie beim Rendern verfügt Ihr Renderer über etwas, das die zu rendernden Instanzdaten darstellt, und Ihre visuelle Komponente behält einfach einen Verweis darauf bei.

Es ist relativ einfach - aus irgendeinem Grund scheint es die Leute zu verwirren, aber das liegt oft daran, dass sie zu sehr darüber nachdenken. Da ist nicht viel Magie. Und da es am wichtigsten ist, Dinge zu erledigen, anstatt sich um das perfekte Design zu kümmern, sollten Sie einen Ansatz überlegen, bei dem zumindest einige der Komponenten definierte Schnittstellen aufweisen und direkte Mitglieder des Spielobjekts sind, wie momboco vorgeschlagen hat. Es ist ein ebenso gültiger Ansatz, und es ist in der Regel leichter zu fassen.

Andere Kommentare

Dies hat nichts mit Ihren direkten Fragen zu tun, aber dies sind Dinge, die mir beim Betrachten Ihres Codes aufgefallen sind:

Warum mischen Sie Ogre :: String und std :: string in Ihrem Spielobjekt, das angeblich kein Grafikobjekt ist und daher keine Abhängigkeit von Ogre haben sollte? Ich würde vorschlagen, alle direkten Verweise auf Ogre zu entfernen, außer in der Rendering-Komponente selbst, in der Sie Ihre Transformation durchführen sollten.

update () ist rein virtuell, was bedeutet, dass man GameObject unterordnen muss, was genau das Gegenteil der Richtung ist, in die man mit einer Komponentenarchitektur gehen möchte. Der Hauptzweck bei der Verwendung von Komponenten besteht darin, die Aggregation der Funktionalität der Vererbung vorzuziehen.

Sie könnten von einer Verwendung von profitieren const(insbesondere, wenn Sie als Referenz zurückkehren). Ich bin mir auch nicht sicher, ob Geschwindigkeit wirklich ein Skalar sein soll (oder ob Geschwindigkeit und Position in dem Spielobjekt in der Art einer übermäßig generischen Komponentenmethode vorhanden sein sollen, wie Sie es zu wollen scheinen).

Ihr Ansatz, eine große Karte mit 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 bauen). Dies führt zu einer sehr schlechten Cache-Kohärenz während des Updates und verhindert, dass Sie die Parallelität und den Trend zu einem SIMD-ähnlichen Prozess mit großen Daten- oder Verhaltensmengen gleichzeitig nutzen können. Oft ist es 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. Dies könnte eine Lektüre wert sein.


quelle
Diese Antwort war ausgezeichnet. Ich habe immer noch keinen Weg gefunden, um Absätze in Kommentaren zu platzieren (Eingabetaste reicht nur ein), aber ich werde auf diese Antworten eingehen. Ihre Beschreibung des Objekt- / Komponentensystems ergab sehr viel Sinn. Um im Fall einer PhysicsComponent im Konstruktor der Component zu verdeutlichen, könnte ich einfach die CreateRigidBody () -Funktion meines PhysicsManager aufrufen und dann einen Verweis darauf in der Component speichern?
Aidan Knight
Soweit der "Andere Kommentare" Teil, danke dafür. Ich habe Strings gemischt, weil ich generell alle Ogre-Funktionen als Basis für meine Projekte bevorzuge, die Ogre verwenden, aber ich habe damit begonnen, sie im Object / Component-System hochzuschalten, da Map / Pair / etc fehlte. Sie haben jedoch Recht, und ich habe sie alle zu den Mitgliedern der std konvertiert, um sie von Ogre zu trennen.
Aidan Knight
1
+1 Das ist die erste vernünftige Antwort in Bezug auf ein komponentenbasiertes Modell, das ich hier seit
einiger Zeit
5
Es ermöglicht eine bessere Kohärenz (sowohl der Daten als auch des Anweisungscaches) und bietet Ihnen die Möglichkeit, die Aktualisierungen auf bestimmte Threads oder Worker-Prozesse (z. B. SPUs) zu verlagern. In einigen Fällen müssten die Komponenten nicht einmal aktualisiert werden, was bedeutet, dass Sie den Overhead eines No-Op-Aufrufs einer update () -Methode vermeiden können. Hilft Ihnen auch beim Aufbau datenorientierter Systeme - es dreht sich alles um Massenverarbeitung, die die modernen Trends in der CPU-Architektur nutzt.
2
+1 für "Es ist am wichtigsten, Dinge zu erledigen, anstatt sich über das perfekte Design
hinwegzusetzen
5

Die Komponentenarchitektur ist eine Alternative, um die großen Hierarchien zu vermeiden, die durch die Vererbung aller Elemente von demselben Knoten und die damit verbundenen Kopplungsprobleme entstehen.

Sie zeigen eine Komponentenarchitektur mit "generischen" Komponenten. Sie haben die grundlegende Logik, um "generische" Komponenten anzuhängen und zu trennen. Eine Architektur mit generischen Komponenten ist schwieriger zu erreichen, da Sie nicht wissen, um welche Komponente es sich handelt. Der Vorteil ist, dass dieser Ansatz erweiterbar ist.

Mit definierten Komponenten wird eine gültige Komponentenarchitektur erreicht. Mit definiert meine ich, die Schnittstelle ist definiert, nicht die Implementierung. Zum Beispiel kann GameObject einen IPhysicInstance, Transformation, IMesh, IAnimationController haben. Dies hat den Vorteil, dass Sie die Benutzeroberfläche kennen und das GameObject weiß, wie es mit jeder Komponente umgeht.

Auf den Begriff der Überverallgemeinerung reagieren

Ich denke, dass die Konzepte nicht klar sind. Wenn ich Komponente sage, heißt das nicht, dass alle Komponenten eines Spielobjekts von einer Komponentenschnittstelle oder Ähnlichem erben müssen. Dies ist jedoch der Ansatz für generische Komponenten. Ich denke, das ist das Konzept, das Sie als Komponente bezeichnen.

Die Komponentenarchitektur ist eine weitere Architektur, die für das Laufzeitobjektmodell vorhanden ist. Es ist nicht das Einzige und nicht das Beste für alle Fälle, aber die Erfahrung hat gezeigt, dass es viele Vorteile wie niedrige Kopplung bietet.

Das Hauptmerkmal, das die Komponentenarchitektur auszeichnet, ist das Konvertieren der Beziehung is-a in has-a. Zum Beispiel ist eine Objektarchitektur -a:

ist eine Architektur

Anstelle einer Objektarchitektur hat -a (Komponenten):

hat-eine Architektur

Es gibt auch eigenschaftsorientierte Architekturen:

Eine normale Objektarchitektur sieht beispielsweise so aus:

  • Object1

    • Position = (0, 0, 0)
    • Orientierung = (0, 43, 0)
  • Objekt2

    • Position = (10, 0, 0)
    • Orientierung = (0, 0, 30)

Eine eigenschaftsorientierte Architektur:

  • Position

    • Objekt1 = (0, 0, 0)
    • Objekt2 = (10, 0, 0)
  • Orientierung

    • Objekt1 = (0, 43, 0)
    • Objekt2 = (0, 0, 30)

Ist die Objektmodellarchitektur besser? In der Leistungsperspektive ist es besser, die Eigenschaft zu zentrieren, weil es Cache-freundlicher ist.

Die Komponente bezieht sich auf die Beziehung is-a anstelle von has-a. Eine Komponente kann eine Transformationsmatrix, eine Quaternion usw. sein. Die Beziehung zum Spielobjekt ist jedoch eine Beziehung. Das Spielobjekt hat keine Transformation, da es vererbt wurde. Das Spielobjekt hat (Relation hat-a) die Transformation. Die Transformation ist dann eine Komponente.

Das Spielobjekt ist wie ein Hub. Wenn Sie eine Komponente möchten, die immer vorhanden ist, sollte sich die Komponente (wie die Matrix) möglicherweise im Objekt befinden und kein Zeiger.

Es gibt kein perfektes Spielobjekt für alle Fälle, es hängt von der Engine und den Bibliotheken ab, die die Engine verwendet. Vielleicht möchte ich eine Kamera ohne Mesh. Das Problem mit is-a Architektur ist, dass, wenn das Spielobjekt ein Netz hat, die Kamera, die vom Spielobjekt erbt, ein Netz haben muss. Mit Komponenten verfügt die Kamera über eine Transformation, eine Steuerung für die Bewegung und alles, was Sie benötigen.

Weitere Informationen hierzu finden Sie im Kapitel "14.2. Runtime Object Model Architecture" aus dem Buch "Game Engine Architecture" von "Jason Gregory".

Von dort werden UML-Diagramme extrahiert

momboco
quelle
Ich bin mit dem letzten Absatz nicht einverstanden, es gibt eine Tendenz zur Überverallgemeinerung. Komponentenbasiertes Modell bedeutet nicht, dass jede Funktionalität eine Komponente sein muss, dennoch müssen Beziehungen zwischen Funktionen und Daten berücksichtigt werden. Ein AnimationController ohne Mesh ist nutzlos. Platzieren Sie ihn dort, wo er hingehört, im Mesh. Ein Game-Objekt ohne Transformation ist kein Game-Objekt, es gehört per Definition in die 3D-Welt, also lege es als normales Mitglied in das Game-Objekt. Es gelten weiterhin die normalen Regeln für die Kapselungsfunktionalität und die Daten.
Maik Semder
@Maik Semder: Ich stimme dir bei der Überverallgemeinerung im Allgemeinen zu, aber ich denke, dass der Begriff Komponente nicht klar ist. Ich habe meine Antwort bearbeitet. Lies es und gib mir deine Eindrücke.
Momboco
@momboco naja, du hast immer weniger die gleichen Punkte wiederholt;) Transform und AnimationController werden immer noch als Komponenten beschrieben, was aus meiner Sicht eine Überbeanspruchung des Komponentenmodells ist. Der Schlüssel ist zu definieren, was in eine Komponente
eingefügt
Wenn etwas benötigt wird, damit das Game-Object (GO) überhaupt funktioniert, mit anderen Worten, wenn es nicht optional, sondern obligatorisch ist, ist es keine Komponente. Komponenten sollen optional sein. Die Transformation ist ein gutes Beispiel, sie ist für ein GO überhaupt nicht optional, ein GO ohne Transformation macht keinen Sinn. Andererseits braucht nicht jeder GO Physik, so dass dies eine Komponente sein kann.
Maik Semder
Wenn es eine starke Beziehung zwischen zwei Funktionen gibt, wie zwischen AnimationController (AC) und Mesh, dann unterbrechen Sie diese Beziehung keinesfalls, indem Sie den AC in eine Komponente drücken. Im Gegensatz dazu ist der Mesh eine perfekte Komponente, aber ein AC gehört dazu in die Masche.
Maik Semder