Objektmanagement sinnvoll gestalten


  • Mod

    cooky451 schrieb:

    rapso schrieb:

    schau dir mal component based entities an, so sollte man das heute eher machen.

    Pass auf, nachher liest volkard hier noch mit. 🤡

    emm, willst du mich damit auf einen 'insider' hinweisen? ich versteh nur bahnhof.



  • http://www.c-plusplus.net/forum/294974

    Der Thread könnte auch für den TE ganz interessant sein.


  • Mod

    er scheint bewust schlechte implementierungen vom component pattern zu nutzen als beispiele dagegen, sowas sollte einen stutzig machen. z.B. Sphagetti code

    volkard schrieb:

    foreach(ding in welt)
          ding->tuwas();
       foreach(ding in welt)
          ding->zeichneDich();
    

    Das wird ja unter Umständen zu

    foreach(ding in gegnerEinheiten)
          ding->tuwas();
       foreach(ding in eigeneEinheiten)
          ding->tuwas();
       foreach(ding in gegnerGebäude)
          ding->tuwas();
       foreach(ding in eigeneGebäude)
          ding->tuwas();
       ...
    

    ich empfehle sowas wie http://www.insomniacgames.com/a-dynamic-component-architecture-for-high-performance-gameplay/ als gegenpol.



  • @rapso
    Gibt es dazu eigentlich auch das Video? Wäre bestimmt mal interessant zu hören, was dazu gesagt wird.
    Falls du noch mehr Material hast, wäre ich recht interessiert. Insbesondere an einer schönen Implementierung in C++, da kann man sich manchmal noch Dinge abschauen.
    (Viel zu oft aber leider auch nicht..)

    Ich hätte da noch anzubieten:
    http://scottbilas.com/games/dungeon-siege/



  • Cyphron schrieb:

    Eine Klasse ObjectManager der die Daten der Models Lädt und sie verwaltet.

    Eine Klasse ObjectInstanceManager die eine Menge von ObjectInstances beinhaltet.

    Jetzt musst du nur noch was an den Namen tun.
    Etwas Manager zu nennen ist immer verdächtig.

    Wie wärs bspw. mit ObjectFactory und ObjectInstanceContainer ?



  • Danke erst mal für die zahlreichen Antworten.

    Ich habe mich jetzt ein bisschen mit Component-Based Objects auseinander gesetzt. Mir ist jedoch noch einiges unklar.

    Ich habe z.B. eine Klasse Spielfigur.

    So wie ich das verstehe benötigt diese Spielfigur folgende Komponenten:

    Renderer
    Position-Move-Rotator
    Lebenspunkte

    die Komponenten Speichere ich in einem Vector.

    Wie können würde ich nun den Renderer Implementieren? Bzw. wie Kommunizieren die Komponenten untereinander? Ich mir noch eine Komponente Meshdata vorstellen welche die zu Rendernden Daten enthält.

    Ich möchte jetzt nicht Wissen wie man Objekte konkret Rendert sondern woher der Renderer seine Daten erhält. wie man die Position-Move-Rotator Componete dabei einbindet welche ja für das Rendern benötigt wird.

    Das ganze erinnert mich auch ein bischen an das Strategie Pattern...

    Mit freundlichen Grüßen

    Cyphron



  • @nurf
    Was ist das bitte für eine komische "Manager"-Phobie hier? Der Name ist nun mal treffend, Factory dagegen würde es nicht treffen, denn die Klasse ist nun mal keine Factory. Edit: ObjectInstanceContainer wäre vielleicht noch ok, aber da die Klasse sich nun mal auch um das Entfernen von ungültig gewordenen Objekten etc. kümmert, halte ich Manager immer noch für treffender.

    @Cyphron
    Guck dir mal meine Beispielimplementierung aus dem verlinkten Thread an. Ich muss zugeben selbst nicht ganz zufrieden damit zu sein, aber so funktioniert es zumindest. Zur Kommunikation unter den Komponenten gibt es verschiedene Ansätze. Einer wäre z.B. über "Nachrichten".
    Ich glaube aber die einfachste Lösung ist es, wenn man einfach eine getComponent()-Methode aufruft. Der Nachteil ist, dass ein paar Komponenten sich untereinander kennen müssen, aber die Vorteile überwiegen: Es ist schnell und man kann Fehler gut loggen. (Wenn irgendeine Komponente ein benötigtes Interface nicht bekommen kann, kann man leicht loggen wo was schief gelaufen ist. Mit Nachrichten die dann unerwarteterweise einfach ins Lehre laufen funktioniert das leider nicht so gut.)



  • meinst du:
    http://www.c-plusplus.net/forum/p2141957#2141957

    virtual void tick(Entity &entity, std::list<Entity> &entities, float elapsed_time) = 0;
    

    Also jedes Entyti ruft alle seine Komponenten auf und übergibt sich als ersten Parameter selbst? Somit wäre die Kommunikation der Komponenten innerhalb eines Objektes klar.

    ---

    Was bedeutet der 2. Parameter „entities“? Sind das alle Objekte die existieren? Wenn ja wie sind diese denn innerhalb deiner Entytät bekannt? Macht dies denn Sinn?

    ---

    Eine weitere Frage wäre wo du festlegst welche Komponenten zu welchem Objekt gehören. Machst du dies im Konstruktor der Entytät?

    ---

    Sagen wir wir hätten eine Klasse Panzer und eine Klasse Fahrzeug.

    Die Klasse Fahrzeug beinhaltet:
    Bewegen

    Die Klasse Panzer beinhaltet:
    Bewegen
    Schießen

    sollte man die Panzer Klasse ableiten? (im Bezug auf 3.)

    ---

    cooky451 schrieb:

    @nurf
    Was ist das bitte für eine komische "Manager"-Phobie hier?

    Vielleicht meint er die derzeitigen Korruptionsfälle bei Media-Saturm Manager... aber warum er jetzt Umwelt verschmutzende Fabriken als alternativen nennt ist mir schleierhaft 😉



  • Cyphron schrieb:

    Also jedes Entyti ruft alle seine Komponenten auf und übergibt sich als ersten Parameter selbst? Somit wäre die Kommunikation der Komponenten innerhalb eines Objektes klar.

    Jo.

    Cyphron schrieb:

    Was bedeutet der 2. Parameter „entities“? Sind das alle Objekte die existieren? Wenn ja wie sind diese denn innerhalb deiner Entytät bekannt? Macht dies denn Sinn?

    Das sind alle Entitäten der Welt. Ist hier halt eine std::list, kannst du aber auch zu std::vector machen, oder ein Template nutzen, oder gleich deinen Manager übergeben. Aber wenn ein Objekt sich jetzt z.B. nach vorne bewegt, muss es ja erkennen, ob da irgendwas vor ihm steht. Nicht jede Komponente braucht Zugriff darauf, aber die Schnittstelle muss natürlich einheitlich bleiben - und eine Referenz mehr macht den Kohl bei virtuellen Funktionen auch nicht mehr fett.

    Cyphron schrieb:

    Eine weitere Frage wäre wo du festlegst welche Komponenten zu welchem Objekt gehören. Machst du dies im Konstruktor der Entytät?

    Nein. Mein Programm kennt keine konkreten Entitäten, das ist alles gescriptet. Ich kann dir leider noch nicht sagen ob sich das so auch auszahlt, musst du selbst wissen, wie du das machst. Grundsätzlich funktionieren tut das aber.

    Cyphron schrieb:

    sollte man die Panzer Klasse ableiten? (im Bezug auf 3.)

    Nein. Du hast in den Objekten keine Vererbung mehr. Eine Entität ist wirklich nur noch die Summe seiner Teile und wird letztendlich auch nur als Halter für seine Komponenten gebraucht.

    Edit:
    Und um das noch mal zu betonen: Ich denke das System lohnt sich wirklich nur, wenn man in seinem Spiel sehr viele sehr verschiedenartige Objekte hat. In einem Spiel in dem es den Spieler, Wände und Gegner gibt halte ich das nicht für sinnvoll.



  • Also ist gibt es von einer Entytät auch nur eine Instanz? Sonst wäre die Entytätenliste irgendwie fehlt am Platz. Hab ich da noch etwas übersehen?

    Wäre es nicht besser eine Basisklasse zu haben wie Panzer (siehe vorherigen Beitrag) und dann mehrere Instanzen der Klasse Panzer den ObjectInstaceManager hinzuzufügen?

    cooky451 schrieb:

    Edit:
    Und um das noch mal zu betonen: Ich denke das System lohnt sich wirklich nur, wenn man in seinem Spiel sehr viele sehr verschiedenartige Objekte hat. In einem Spiel in dem es den Spieler, Wände und Gegner gibt halte ich das nicht für sinnvoll.

    Geplant ist dies schon. Es gefällt mir soweit ich es verstanden habe schon ganz gut.

    Edit:

    Bzw. Definiere sehr viele



  • Cyphron schrieb:

    Also ist gibt es von einer Entytät auch nur eine Instanz? Sonst wäre die Entytätenliste irgendwie fehlt am Platz. Hab ich da noch etwas übersehen?

    Wie meinen? Also eine Entität ist ein Objekt. Jeder Baum wäre z.B. eine Entität, jeder Spieler, jeder Gegner, jedes Licht, jeder Trigger, ...

    Cyphron schrieb:

    Wäre es nicht besser eine Basisklasse zu haben wie Panzer (siehe vorherigen Beitrag) und dann mehrere Instanzen der Klasse Panzer den ObjectInstaceManager hinzuzufügen?

    Wie gesagt, die Klasse "Panzer" kannst du optimalerweise halt auch scripten. Aber wenn du sie in C++ hartgecoded haben möchtest, geht das natürlich auch.



  • Ok dann habe ich dich Missverstanden. Da du dies durch ein Script Definierst gibt es auf C++ Ebene eine Solche Klasse bei dir nicht.

    Eine Scriptanbindung ist vorgesehen. Weiter bin ich da noch nicht.

    Auf jeden Fall schonmal vielen Dank bis hier her!



  • rapso schrieb:

    schau dir mal component based entities an, so sollte man das heute eher machen.

    Ich hab mir dazu ein paar Dinge im Inet durchgelesen (u.A. auch deine Links). Was mir dabei nicht ganz klar ist (weil ich nirgends ein Beispiel dazu gefunden habe - die Beschreibungen drücken sich da alle recht elegant drumrum)... wie lässt man Components interagieren?
    Also das was in deinem Link kurz unter "Closing Thoughts" (letzte Seite) angeschnitten wird.

    Da sind "2 Options" angeführt...

    1. Eine Komponente auswählen der die Daten "gehören"
      Das bedeutet doch dass die Komponente der die Daten "gehören" immer vorhanden sein muss, wenn die Komponente die sich die Daten "ausborgt" gebraucht wird ...?
      Und natürlich muss die "ausborgende" Komponente Zugriff auf die "besitzende" Komponente haben. Wie macht die das? Über "das System" mittels Entity-ID + Component-Type?

    2. "Scatter-gather"
      Ich bin mir nicht sicher was damit gemeint ist...
      Heisst das dass die Daten (z.B. aktuelle Position oder Geschwindigkeit) in beiden Komponenten vorhanden sind, und vom "System" rumkopiert werden bevor/nachdem die unterschiedlichen Component-Typen upgedated werden? Wenn ja, woher weiss das System was wo rumkopiert werden muss?

    Falls du dazu weitere Links hättest, oder selbst eine kurze (oder auch lange) Erklärung schreiben magst, wäre das fein 🙂



  • Ich denke cooky451 hat hierfür in dem von ihm verlinkten Thread schon ein gutes Beispiel gegeben (siehe: http://www.c-plusplus.net/forum/p2141957#2141957)

    Man müßte nun nur noch von seiner Componet Klasse ableiten und die abgeleiteten Klassen der Entität hinzufügen.

    In der "MainLoop" deines Programmes wird das für jeden Schleifendurchlauf die tick Funktion Jeder Entität aufgerufen, welche dann die tick Funktion der eigenen Komponenten aufruft.

    Und natürlich muss die "ausborgende" Komponente Zugriff auf die "besitzende" Komponente haben.

    so:

    virtual void tick(Entity &entity, std::list<Entity> &entities, float elapsed_time) = 0;
    


  • hustbaer schrieb:

    rapso schrieb:

    schau dir mal component based entities an, so sollte man das heute eher machen.

    Ich hab mir dazu ein paar Dinge im Inet durchgelesen (u.A. auch deine Links). Was mir dabei nicht ganz klar ist (weil ich nirgends ein Beispiel dazu gefunden habe - die Beschreibungen drücken sich da alle recht elegant drumrum)... wie lässt man Components interagieren?
    Also das was in deinem Link kurz unter "Closing Thoughts" (letzte Seite) angeschnitten wird.

    Da sind "2 Options" angeführt...

    1. Eine Komponente auswählen der die Daten "gehören"
      Das bedeutet doch dass die Komponente der die Daten "gehören" immer vorhanden sein muss, wenn die Komponente die sich die Daten "ausborgt" gebraucht wird ...?
      Und natürlich muss die "ausborgende" Komponente Zugriff auf die "besitzende" Komponente haben. Wie macht die das? Über "das System" mittels Entity-ID + Component-Type?

    2. "Scatter-gather"
      Ich bin mir nicht sicher was damit gemeint ist...
      Heisst das dass die Daten (z.B. aktuelle Position oder Geschwindigkeit) in beiden Komponenten vorhanden sind, und vom "System" rumkopiert werden bevor/nachdem die unterschiedlichen Component-Typen upgedated werden? Wenn ja, woher weiss das System was wo rumkopiert werden muss?

    Falls du dazu weitere Links hättest, oder selbst eine kurze (oder auch lange) Erklärung schreiben magst, wäre das fein 🙂

    Ich würde 1) wählen, weil es schlicht einfacher ist und keine Daten synchronisiert werden müssen. Dafür ist es halt weniger chache-freundlich und man braucht vielleicht beim multithreaden mehr locks.
    Das management der entities mache ich einfach so:
    Zum Konstruieren wird ein entity_pattern mit components gefüllt. Das funktioniert etwa so:

    entity_pattern pattern;
    pattern.add_component( ComponentA() );
    pattern.add_component( xy );
    

    Dann wird das ganze einem zentralen entity-Verwaltungssystem übergeben. Das ist dann dafür zuständig, die Komponent auf verschiedene streng typisierte container zu verteilen.

    entity_system sys;
    sys.add_entity( pattern );
    component_container< ComponentA > &container = sys.get_container< ComponentA >();
    container.for_each( []( ComponentA ){} );
    

    Das Kopieren der components in entity_system funktioniert so, dass ich nachdem jede Komponente zuerst in einem component_container<...> speichere. Zurückgegeben wird dann ein pointer auf das neu konstruierte Objekt. Diese werden dann in einer Klasse [/c]entity[c] gespeichert. Nachdem alle Komponenten auf diese Weise kopiert wurden, wird jeder neu konstruierten Komponente automatisch dieses entity-Objekt übergeben, über das die Komponente sich dann pointer holen kann:

    class ComponentA
    {
    private:
        ComponentB *ptr;
    public:
        void constructed( entity &ent )  
        {
            ptr = ent.query< ComponentB >();
            if( ptr == 0 )
                throw XY;
        }
    };
    

    Das entity-Objekt wird nach der Konstruktion nicht mehr gebraucht und wieder gelöscht.
    Die ganze Technik verwendet massiv type erasure (aber nur während der Konstruktion, danach braucht man keine virtual function calls mehr).



  • Mich würden hauptsächlich Infos aus der Praxis interessieren.

    Auch Beispiele aus der Praxis wären interessant, wo man sieht welche Components es gibt, wie diese zusammenspielen. Wie man "shared data" vielleicht in bestimmten Fällen ganz vermeiden kann, wie man es macht wenn es nicht geht etc.

    Auch was multithreading angeht, denn auch da gibt es ja viele gangbare Wege.

    Ich hab einfach immer noch ein wenig Schwierigkeiten mir vorzustellen wie das in einem echten Spiel aussehen könnte.

    Und natürlich eine Umsetzung, wo es auch wirklich was bringt. Dein Beispiel macht z.B. das Erzeugen von Entities/Components relativ teuer. Wenn das selten genug passiert, dann macht das natürlich nix. Wenn man aber pro Frame tausende Partikel spawnen will, dann macht es vermutlich schon was.



  • hustbaer schrieb:

    Und natürlich eine Umsetzung, wo es auch wirklich was bringt. Dein Beispiel macht z.B. das Erzeugen von Entities/Components relativ teuer. Wenn das selten genug passiert, dann macht das natürlich nix. Wenn man aber pro Frame tausende Partikel spawnen will, dann macht es vermutlich schon was.

    Partikel macht man (ich?) weder mit Komponenten noch sonst irgendwie als eigenständiges Objekt. Nur der Emitter wäre bei mir ein Entity.



  • Dann bin ich mal gespannt in welcher Weise ein Partikel so wie man (du) es mach(s)t anders ist als eine Komponente. Erzähl mal 🙂



  • Man implementiert eine Emitterklasse als Komponente. Diese Klasse hält dann Dinge wie maximal/minimale Lebenszeit, Schwerkraft, Textur, .. und natürlich einen Container mit Partikeln, wobei ein Partikel nur eine Struktur ist die Position, Geschwindigkeit, Farbe, Startzeit, Lebensdauer etc. hält.



  • OK. Einer von uns beiden hat das "component based entities" System falsch verstanden.

    Ich bin z.B. der Meinung dass es den Grundprinzipien widerspricht, dass jeder Emitter seinen eigenen Container von Partikeln hat.
    Wozu auch?, alles in einen einzigen globalen Container rein.

    Dann hast du also einen globalen Container, der Dinge (Structs, wie auch immer man es nennen will) eines ganz bestimmten Typs speichert. Über diese Dinge lässt man pro Frame eine ganz bestimmte "Transformation" laufen, tut sie dann alle auf die selbe Art und Weise rendern etc.

    Und wenn ich das Prinzip richtig verstanden habe, dann nennt man genau das "Komponente". Die Partikel kommen dabei ohne Entity aus, OK, macht ja nix. If it walks like a component and quacks like a component...


Anmelden zum Antworten