Objektmanagement sinnvoll gestalten
-
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...
-
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? -
"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...
-
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? -
"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...
-
hustbaer schrieb:
Ich bin z.B. der Meinung dass es den Grundprinzipien widerspricht, dass jeder Emitter seinen eigenen Container von Partikeln hat.
Ja, in unserer natürlichen Umgebung würde man einfach jedes Teilchen dort rein packen. Aber man baut (normalerweise) ein Spiel - keine perfekte Simulation.
hustbaer schrieb:
Wozu auch?
Performance ohne ernsthafte Einbußen an Funktionalität oder Übersicht. Viel Performance. Der "globale" Objektcontainer ist für so etwas doch gar nicht geschaffen. Die ganzen Speicheranforderungen und Freigaben an sich verschlucken doch schon zu viel. Nein, nein. Wenn man das separat handhabt kann man alles in einen Vektor mit fester Größe stecken und dann swapen etc. Jedenfalls kann man getrennt von dem ganzen anderen Kram experimentieren. Letztlich verhalten Partikel sich in einem Spiel halt auch nicht so, wie man es vielleicht erwartet. Man kann nicht gegen laufen, sie interagieren nicht, sind eigentlich gar nicht da. Nur Grafik. Daher muss auch kein anderes Objekt von ihnen wissen -> spart wieder Rechenleistung.
-
Ich glaube du verstehst unter Komponente ganz was anderes als ich.
Nochmal konkret die Frage: was hat deiner Meinung nach eine Komponente, was ein Partikel nicht hat (nicht haben muss)?EDIT
Um das nochmal klarzustellen: ich habe nicht vorgeschlagen alle Komponenten überhaupt in einen einzigen Container zu packen. Sondern alle Partikel-Komponenten in einen einzigen Partikel-Komponenten-Container. Bzw. vermutlich sogar noch feiner: jeder Partikel-Typ bekommt einen eigenen Container - man wird ja u.U. verschiedene Partikel haben. Aber eben nur einen pro Typ, nicht einen pro Emitter.Wenn du 200 Rauchpartikel-Emitter in einer Szene hast, die alle die selben Rauchpartikel auswerfen... wo macht es dann bitte Sinn diese Rauchpartikel in 200 verschiedenen Containern zu speichern? Und wie soll das bitte zu besserer Performance führen? Das ist doch total widersinnig. Mal ganz davon abgesehen dass man den Emitter künstlich am Leben halten müsste, wenn er "eigentlich" schon weg wäre, nur damit seine Partikel nicht verschwinden.
/EDIT
-
meint cooky451 nicht, das er für jeden partikel-effeckt den er im Spiel anwendet einen container hat? Klingt für mich zumindest durchaus logisch, da es ja eigentlich keinen sinn macht, alle PARTIKEL in einen container zu werfen.
-
hustbaer schrieb:
Um das nochmal klarzustellen: ich habe nicht vorgeschlagen alle Komponenten überhaupt in einen einzigen Container zu packen. Sondern alle Partikel-Komponenten in einen einzigen Partikel-Komponenten-Container. Bzw. vermutlich sogar noch feiner: jeder Partikel-Typ bekommt einen eigenen Container - man wird ja u.U. verschiedene Partikel haben. Aber eben nur einen pro Typ, nicht einen pro Emitter.
Hm.. ja, das ist was anderes. Ich dachte du wolltest alles in den globalen Container stecken. Für jeden Typ einen Container anlegen und die Emitter nur als Positionen nutzen ginge natürlich auch, dann sehen halt nur alle Fackeln genau gleich aus. Aber es wäre wesentlich performanter, das stimmt. Nur würde ich ein Partikel niemals selbst als Entity erstellen, das ist doch viel zu teuer. (Und wie wir uns ja einig sind, sind Partikel auch keine "echten" Objekte, die irgendwie nach Außen kommuniziert werden müssten.)
-
Wieso sollten dann alle Fackeln gleich aussehen?
-
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.das haengt stark davon ab, welche daten und wie oft man uebermitteln moechte. oft wird ein event/message/signal system benutzt, wenn z.b. der player stirbt, wird ein event dafuer getriggert, alle elemente die sich fuer so ein event regestriert haben, bekommen das mit. im einfachsten fall gibt es callback von libsig.
pointer sharen oder desgleichen ist grundsaetzlich nicht der richtige weg, da theoretisch alle systeme unabhaengig laufen koennen, dabei muss es nicht multithreaded sein, es reicht auch dass z.b. in einem RTS die logik fuer die AI componenten time-sliced ist und die updated dann z.b. mit 0.1Hz . bis da jemand mitbekommen wuerde dass ein pointer invalid ist, koennte dieser schonwieder mit was ganz anderem belegt sein.
in einem hypothetisch perfekten system, wuerde jedes entity nur eine ID sein (perfecterweise variable bit length), beim erstellen wuerde es eine object description geben (ich hatte das mal mit xml gebastelt), und dann wird durch alle system die regestriert sind durchgegangen und die jeweiligen descriptoren, fall vorhanden, wuerden in jedem system ihre components, fuer diese entity ID erstellen.
so kannst du wirklich extrem flexibel arbeiten, du kannst z.b. ein system garnicht erst einklinken (z.b. sound-dll loeschen), du kannst zur laufzeit die components eine systems serialisieren (gibt ja keinen pointer drauf ausser im system), das system runterfahren, durch ein anderes ersetzen z.b. ogl->d3d und deserialisieren, das laeuft dann weiter als waere nichts.
du kannst so auch sehr einfach netzwerk kommunikation machen (ich glaube UE3 network code arbeitet zum teil so), du schickst nur events -> die eventqueue repliziert die daten ueber netzwerk (bzw das netzwerk modul ist listener fuer alle events).so perfekt geht das nicht wie man an eurem particle beispiel sieht.
ich wurde schon an code drangesetzt, der so war. ein container wo alle particle sind. entsprechend wurde ich wegen performanceproblemen drangestzt.
- zum einen wurde auf cpu seite pro particle eine virtual function aufgerufen die fuer jeden particle type anders implementiert war, sehr flexibel und ideologisch gut, aber ein vtable lookup und indirekter function call sind schonmal 20mal mehr als die zumeist darin enthaltenen Pos+=Dir;
- auf gpu seite ist es natuerlich visuel auch perfekt, alle particle sind nach entfernung sortiert (kommt natuerlich ein grosser quicksort ueber den ganzen container pro frame hinzu, was anhand der coherenten daten sogar langsammer als ein insert-sort war), pro particle wird die textur, der shader, eine lightquelle und der blendmode gesetzt. selbst mit caching brach das nach spaetestens 3000 particlen total ein (ohne was anderes zu rendern).-loesung 1. ist dann erstmal ein container pro particle system (mit emittern die dorthin spawnen)
cpu seite ist ein grosser switch oder ein vtable call, der sonst pro particle gemacht wurde. war locker 10x schneller in real world. gpu seite hat ein drawcall pro particle system, auch schoen nur ein budget/cap pro system, nachteil ist, dass die ganze welt simuliert wird, was u.U. 99% verschwendung ist (fakeln im ganzen quake level obwohl man nur die in einem raum sieht).
-loesung 2. ist dann pro emitter ein container
cpu seite ist noch relativ fix bei der simulation, culling anhand von AABBs fuers zeichnen und simulieren ist dann einfach, auch collision detection ist relativ gut, da man pro container die paar objekte picken kann die vermutlich kollidieren koennen. gpu seite ist natuerlich auch recht fix und man hat noch die moeglichkeit die emitter zu sortieren.nach der definition von insomniac waere loesung 1 ja noch ein entity system fuer particle. man hat input-transformatin-output. pro system.
particle sind eigentlich die die perfection von component systemen, da man darauf nicht nur an sich transformationen durchfuehrt, sondern ungleich den sonstigen implementierungen wo pro system daten<->logic abhaengigkeit ist, bei particlen einfach nur auf dieselben daten transformationen durchgefuerht werden. z.b. kann man gravitation als ein durchgang machen, collision+reflection als ein pass, sogar incrementeles bubblesort als pass.weil weitere nette links erfragt wurden http://macton.smugmug.com/gallery/8936708_T6zQX#593426709_ZX4pZ
:xmas1:
-
hustbaer schrieb:
Wieso sollten dann alle Fackeln gleich aussehen?
Wenn man für die Fackeln nur ein "Fackel-Feuer" hat, und die Punkte für alle Fackeln in einem Container berechnet werden, sodass man sie nachher rumschieben kann, sehen alle Fackeln genau gleich aus.
-
Ich hätte noch eine Frage zu dem Componenten Basierten Design:
Könnte man nicht auch die GUI somit sinnvoll Realisieren? So das man eine Komponente namens "Interactive" die Ereignisse (Click,Mousover..) realisiert erstellt.
Und dann je nach GUI Element einen ButtonRenderer oder einen InputFieldRenderer einer GUI Entität zuweist.
Somit könnte man die Komponente "Interactive" einen 3D Objekt zuweisen und damit auch mit dem Objekten interagieren. Man müßte nur bei der Ereignesserkennung unterschiedlichen Code verwenden.
Ist dies sinnvoll?
Mit freundlichen Grüßen
Cyphron