Component Based Game Design - wie sieht das konkret aus?



  • In diesem Thread http://www.c-plusplus.net/forum/325485 ist wieder mal der Begriff "Component Based Game Design" gefallen.
    Scheint ja mittlerweile Standard zu sein dass das empfohlen wird wenn jemand eine Designfrage zum Thema Spieleprogrammierung stellt.

    Ich hab' zum dem Thema auch schon ein paar Beiträge/Blog-Postings/... gelesen, aber so richtig schlau geworden bin ich daraus nicht.

    D.h. ich hatte danach keine konkrete Vorstellung wie man damit ein Spiel umsetzen kann, ohne dass alles komplizierter und/oder langsamer wird.

    Also... wenn ich die Sache richtig verstanden habe, "sollte" man bei Component Based Game Design alle Components eines Typs in einem "kompakten" Array halten. Wenn eine Component eines Typs gelöscht wird, kompaktiert man das Array, indem man z.B. die letzte Component dieses Typs an diese Stelle kopiert - wodurch sich natürlich die Adresse dieser Component ändert.

    Daraus ergibt sich dass nur temporäre Zeiger auf Components erlaubt sind.

    Der Vorteil ist mir klar, nämlich dass man dadurch superschnell über alle Components eines Typs drüberiterieren kann (Cache-freundlich und so).

    Nur erkauft man sich diesen Vorteil dadurch, dass alle Updates von Components die auf Daten von anderen Components der selben Entity angewiesen sind nen Lookup über die Entity-ID in irgendn einen Zentralen Index machen müssen.
    Und ich kann mir irgendwie nicht vorstellen, wie man ein Spiel programmieren kann, ohne dass das *andauernd* vorkommt.

    Also z.B. sollte die Position der grafischen Repräsentation einer Entity mMn. z.B. einer Component "Renderable" gehören. Wenn man jetzt Dinge hat die sich bewegen, z.B. frei fallen, dann erzeugt man für die eine Component "Falling" oder sowas. Die implementiert dann das "Fallen" der Entity. Die braucht dazu aber Zugriff auf die Position. Das selbe gilt natürlich für jede Component die irgend eine Art der Bewegung umsetzt - also ist auch alles was einem Pfad folgt, auf den Spieler zuläuft etc. davon betroffen.
    Und das sind dann bei manchen Spielen mächtig viele Entities. => Mächtig viel Overhead.

    Oder trickst man da irgendwie?
    z.B. indem man bestimmte Daten dann doch wieder direkt im Entity-Objekt abspeichert, abseits der Components, so dass jede Component direkt über nen Zeiger darauf zugerifen kann?
    Oder indem man Spezialregeln aufstellt wie dass jede Entity zu jeder Zeit immer nur eine "Positions-Kontrollierende" Component haben darf, die dann auch direkt die Position abspeichert?
    Oder ...?

    Oder irre ich mich, und die Fälle wo man sowas braucht sind in realen Spielen ausreichend selten, so dass der dadurch erzeugte zusätzliche Overhead nicht ins Gewicht fällt?
    Oder hab' ich da irgendwas grundlegend falsch verstanden?

    Falls jemand von euch dieses Thema wirklich verstanden hat, idealerweise schon selbst eingesetzt, wäre ich froh wenn dieser Jemand mir nen Link posten könnte, oder ne kurze Erklärung schreiben was ich wo falsch verstanden habe (und wie es wirklich gemeint ist). Das wäre cool 🕶



  • Ich weiß nicht ob dir das weiterhilft, aber eine Implementierung die ich selbst mal benutzt habe nutzt auch den inversen Look-Up-Table, so dass man also die Funktion Component -> Entities nachschlagen kann.
    Wenn dann Component A noch Component B benötigt schaust du nur noch bei den Entities nach, welche bei "LookUpEntitiesWith(Component A)" rauskommen.



  • Erstmal danke für deinen Beitrag!
    Ich hatte die letzten Tage recht wenig Zeit (und da ich die Infos nicht braucht um konkret etwas zu implementieren, sondern nur um meine Neugier zu befriedigen, hab ich mir nicht fürher Zeit hierfür genommen) - daher die verspätete Antwort.

    Namenloser324 schrieb:

    Ich weiß nicht ob dir das weiterhilft, aber eine Implementierung die ich selbst mal benutzt habe nutzt auch den inversen Look-Up-Table, so dass man also die Funktion Component -> Entities nachschlagen kann.

    Ich vermute du meinst Component-Type -> Entities.
    Ich erwähne das nicht weil ich klugscheissen will - ich mir bei dem was folgt nur nicht sicher was du genau meinst, ud will sicher gehen dass mein Verständnisfehler nicht schon hier anfängt.

    Namenloser324 schrieb:

    Wenn dann Component A noch Component B benötigt schaust du nur noch bei den Entities nach, welche bei "LookUpEntitiesWith(Component A)" rauskommen.

    Hier wieder LookUpEntitiesWith(Component-Type A), right?
    Und wenn das soweit richtig ist, dann verstehe ich nicht was das bringen soll.
    Ich kann die Liste (konzeptuell, egal ob jetzt linked list oder Vektor oder...) die da zurückkommt ja schwer linear der gewünschten Entity durchsuchen - das würde ja ewig dauern.
    Und wieso Component(-Type) A? Wir suchen ja Component B... 😕

    ----

    Aber mal gucken...
    Jede Entity ist ja im Prinzip nur eine Liste von Components.
    Die Components werden dabei durch einen Array-Index identifiziert -- und wenn eine Component verschoben wird, dann muss halt in der Entity der Index angepasst werden.
    Soweit so gut.

    Wenn jetzt Component A von Entity X die Component B von Entity X sucht, dann wäre die erste Möglichkeit dass Component A direkt einen Zeiger auf ihre Entity hat, dort den Index für Component B nachschlägt, und mit dem dann auf Component B zugreift.
    D.h. wir haben 1x einen Random Access im RAM beim Zugriff auf die Entity.
    Dann muss in der Entity der Index für Component B gesucht werden. Wenn es ausreichend wenig Component-Typen gibt, ist das einfach ein direkter Zugriff in ein Array.
    Wenn es zu viele Component-Typen gibt, dann muss man in einer (vermutlich recht kurzen) Liste bzw. Map nach dem Index für Component B suchen. Das ist vermutlich nochmal min. ein Random Access im RAM (es sei denn man baut Speicher für eine sehr kurze Liste mit in die Entity ein, dann wäre es lineares Lesen im RAM).
    Dann greift man über den Index auf Component B zu, was nochmal ein Random Access im RAM ist.
    Im Idealfall also 2x Random Access im RAM + Scan eines kurzen eingebauten Arrays bzw. Suche in einer kurzen eingebauten Map.
    Sonst 3x Random Access im RAM + Scan des Arrays/Suche in der Map.

    Klingt relativ teuer, wenn man daran denkt dass man das ganze ja hauptsächlich wegen der Cache-Performance macht, also um Random Access im RAM zu minimieren.

    Gibt aber natürlich noch eine andere Möglichkeit.
    Wir könnten jeder Entity auch eine Nummer verpassen. Und zwar so, dass die Nummern von freigegebenen Entities recycled werden bevor neue Nummern vergeben werden.
    D.h. die höchste Entity-Nummer ist dann maximal so gross wie die maximale Anzahl an Entities die irgendwann gleichzeitig existiert haben.
    D.h. wir können es uns vermutlich leisten pro Component Type ein Array mit dieser Grösse zu verwalten, wo man über die Entity-Nummer direkt den Component-Index nachschlagen kann (ist es vielleicht das was du mit "LookUpEntitiesWith" gemeint hast?).
    In der Entity selbst müsste man dann nur noch eine Liste der verwendeten Component-Typen speichern.
    Und dann nochmal zusätzlich ein Array wo man über die Entity-Nummer an die Entity selbst kommt.
    (Bzw. man könnte dann die Entities selbst, genau so wie die Components, auch direkt in dem Array abspeichern -- nur dass das Entity-Array halt nicht kompaktiert wird.)

    Dann sähe der Zugriff wie folgt aus:
    Component A speichert nun direkt die Entity-Nummer, und holt sich aus dem Array für Component B den Component B-Index: 1x Random Access.
    Über den greift man dann schon auf Component B zu: nochmal ein Random Access.
    Macht also in Summe 2x Random Access im RAM, und das Scannen einer Liste/Suche in einer Map fällt ganz weg.

    Klingt schon besser.
    Verglichen mit einer Variante wo man ganz auf Arrays verzichtet, und die Components direkt über Zeiger adressiert ist das nicht viel schlechter.



  • Ist meine Frage bzw. mein Geschreibsel hier so blöd dass sie/es keine Antwort/Erwiderung verdient, oder interessierts nur niemanden ausser Namenloser324? 🙂
    (Oder haben die meisten gar genau so wenig Plan davon wie ich? Oder TL;DR?)



  • Ich mache das nicht wirklich so wie in vielen Tutorials beschrieben wird, sondern habe einfach Abstand davon genommen einen Gottcontainer mit allen Spielobjekten zu benutzen. Wenn ich das erwähne dann eher damit der fragende TE, der in den meisten Fällen ziemlich unerfahren ist, mal eine andere Perspektive darauf bekommt wie man ein Spiel aufbauen kann und dass ein "class game_object" mit Vererbung in den allermeisten Fällen keine sonderlich gute Idee ist.



  • Habs mir jetzt auch ein Weilchen angelesen. Ich werde das Gefühl nicht los, daß ich lokal mit normaler Vererbung und gelegentlichen intrusiven doppelt verketteten Ringen viel besser aufgehoben wäre.
    Für das MMORPG würde ich nicht anders vorgehen, außer daß Zeiger auf Objekte dann immer welteindeutige IDs wären.

    Das Hineinschubsen hat vielleicht den anderen Grund, daß entfernte "Scripter" was reindübeln können, ohne daß man eine LUA-Schnittstelle basteln muss. Also Trennung von Kernentwicklern und Storyentwicklern. Macht das unter Euch aus, wenn Ihr das trennen wollt. Vielleicht löst man da ein Nichtproblem mit fünffachem Aufwand.



  • cooky451 schrieb:

    Ich mache das nicht wirklich so wie in vielen Tutorials beschrieben wird, sondern habe einfach Abstand davon genommen einen Gottcontainer mit allen Spielobjekten zu benutzen.

    Wenn man das "klassische Gottcontainer Design" nimmt, und da den Gottcontainer entfernt, landet man aber immer noch lange nicht bei Component Based Design. Auch nicht bei einer Component Based Design ohne Cache-Optimierungen.
    (Schonmal deswegen weil man den Gottcontainer bei Component Based Design ja trotzdem noch hat. Man verwendet ihn nur für die meisten Vorgänge nicht mehr. ;))

    Wenn ich das erwähne dann eher damit der fragende TE, der in den meisten Fällen ziemlich unerfahren ist, mal eine andere Perspektive darauf bekommt wie man ein Spiel aufbauen kann und dass ein "class game_object" mit Vererbung in den allermeisten Fällen keine sonderlich gute Idee ist.

    Und ich frage mich, ob es Sinn macht einen unerfahrenen Entwickler etwas entgegenzuwerfen, wo ich als doch recht erfahrener Entwickler Schwierigkeiten habe zu verstehen wie es eigentlich gemeint ist.

    Ich halte es durchaus für gut darauf hinzuweisen dass das von vielen Anfängern bevorzugte "IGameObject" Design nicht unbedingt gut ist. Ich denke aber dass jeder, und ganz speziell ein unerfahrener Entwickler, sich leichter tut das anhand von konkreten Beispielen zu verstehen. Und dass ich das was im Netz zum Thema "Component Based Design" so rumfliegt für reichlich wenig konkret halte, sollte ja mittlerweile klar sein.



  • hustbaer schrieb:

    wenig konkret halte, sollte ja mittlerweile klar sein.

    Es ist auch wenig konkret, aber die meisten Artikel beschreieben zumindest schön warum das klassische GameObject-Konzept so problematisch ist. Einen konkreten, guten Artikel zu: "So sollte man ein 3D-Spiel mit beliebigen Objekten und beliebiger Interaktion designen" ist mir leider nicht bekannt. 😉



  • @cooky451
    In den Artikeln die ich bisher gelesen habe, werden grösstenteils zwei Probleme erwähnt:

    1. Dass man in der klassischen Variante hunderte oder tausende Entity-Klassen hat und das ganze dadurch unübersichtlich wird.
      Uff.
      Hunderte oder tausende. Was sind das bitte für Spiele?
      Klar, wenn ich 100 Mob-Typen habe, und stur für jeden Mob-Typ eine eigene Klasse schreibe, dann kann das schon sein. Nur wenn man das so macht, dann gehört man dafür verprügelt. Ich denke doch dass man in den meisten Spielen wo man 100 oder wenige 100 Entity-Typen hat, man die dafür nötige Anzahl an Klassen leicht auf 1/10 der Entity-Typen reduzieren kann.
      100 Schwerter mit 100 verschiedenen Regeln wie der Damage berechnet wird, brauchen keine 100 verschiedenen Klassen, sondern genau eine. Mit nem kleinen "berechne mal eben den Damage" Script oder Funktor pro Schwert. Usw.
      Bleiben also noch Spiele wo man wirklich so viele grundlegend unterschiedliche Entity-Typen hat, dass selbst 1/10 davon nocht zu viel ist um übersichtlich zu bleiben.
      => Uninteressant für jedes Projekt das jemand hier im Forum alleine ober mit ein paar Kumpels umsetzen wird.

    2. Performance.
      Auch hier kommt man lange lange lange mit klassischem Design aus. Klar, man verschenkt dadurch Rechenzeit. So lange es schnell genug läuft, ist es aber eigentlich wurst wie viel schneller es laufen könnte, wenn man es anders machen würde.

    Und das sind beides nicht die Probleme die der typische Anfänger-Entwickler hat.

    Der typische Anfänger-Entwickler baut nämlich keine klassische Game-Architektur, sondern eine klassische Anfänger-Game-Architektur.



  • hustbaer schrieb:

    Auch hier kommt man lange lange lange mit klassischem Design aus.

    Wenn du mit "klassischem Design" meinst dass man von einer object Klasse erbt, nein. Das Problem damit ist ziemlich fundamental und hat eigentlich wenig mit der Menge an Klassen die man hat zu tun. Ganz einfaches Beispiel, hat man schon wenn man alle Objekte zeichnen will die "Drawable" sind. Wie findest du heraus welche Objekte das können?



  • Mich interessiert diese Component Based Game Design schon, nur mal so am Rande. Ich bin zwar weit davon entfernt ein Spiel entwickeln zu können, aber irgendwann will ich so etwas auch mal probieren.

    Vor OOP-Design habe ich großen Respekt. So richtig weiß ich immer gar nicht wie ich was designen soll, wenn man bei mir überhaupt von Designer sprechen kann. Mir sind ein paar OOP-Pattern bekannt, aber so wirklich eigenen ausgefeilte Design fallen mir gar nicht ein. Ich mach halt erstmal das es funktioniert und schaue dann wo ich noch dran schrauben kann oder was ich komplett noch mal neu mache.



  • cooky451 schrieb:

    hustbaer schrieb:

    Auch hier kommt man lange lange lange mit klassischem Design aus.

    Wenn du mit "klassischem Design" meinst dass man von einer object Klasse erbt, nein. Das Problem damit ist ziemlich fundamental und hat eigentlich wenig mit der Menge an Klassen die man hat zu tun. Ganz einfaches Beispiel, hat man schon wenn man alle Objekte zeichnen will die "Drawable" sind. Wie findest du heraus welche Objekte das können?

    Warum sind die Objekte, die nicht drawable sind, überhaupt in der Liste der zu zeichnenden Objekte???



  • Genau das ist ja das Problem bei den Entitäten in Verbindung mit der Spiellogik und/oder Grafikprogrammierung.
    Legt man für jede Funktionaliät eigene Listen an (drawable, hittable, ...) so ist der Aufwand enorm, diese Listen immer aktuell zu halten - und jede weitere Funktionaliät würde dann wieder eigene Listen erfordern. Dieses starre Design beeinträchtigt dann auch viele mögliche Spielerweiterungen (Features), z.B. temporäre Effekte oder Funktionskombinationen.

    Das Component Based Game Design dagegen konzentriert sich eben auf die Komponenten, so daß diese je Entität austauschbar sind. So bewahrt man sich eine große Flexibilität, jedoch kommt es eben dann auch zu den möglichen Problemen bei der Performance - und daher hat hustbaer ja diesen Thread erstellt.

    @volkard, hast du auch nur schon mal im Ansatz überhaupt ein Spiel programmiert?



  • volkard schrieb:

    Warum sind die Objekte, die nicht drawable sind, überhaupt in der Liste der zu zeichnenden Objekte???



  • cooky451 schrieb:

    hustbaer schrieb:

    Auch hier kommt man lange lange lange mit klassischem Design aus.

    Wenn du mit "klassischem Design" meinst dass man von einer object Klasse erbt, nein. Das Problem damit ist ziemlich fundamental und hat eigentlich wenig mit der Menge an Klassen die man hat zu tun. Ganz einfaches Beispiel, hat man schon wenn man alle Objekte zeichnen will die "Drawable" sind. Wie findest du heraus welche Objekte das können?

    Wenn die Frage ernst gemeint ist, dann hast du nichts verstanden.
    Ich meine mit "klassischem Design" ganz normales, vernünftiges, nicht "component based" Softwaredesign.
    Wieso meinst du dass da Gottklassen oder Gottinterfaces vorkommen würden, oder Listen wo haufenweise Objekte drin sind die nicht drin sein müssten?

    cooky451 schrieb:

    volkard schrieb:

    Warum sind die Objekte, die nicht drawable sind, überhaupt in der Liste der zu zeichnenden Objekte???

    Nein, er hat vollkommen Recht.
    Eine Liste wo nur "drawable" Objekte drin stehen hat mit Component Based Design so-gut-wie nichts zu tun.

    -----

    BTW: Die Frage "drawable?" stellt sich normalerweise nicht. Die Trennung Logik/Grafik ist ja wohl quasi-Standard (oder sollte es zumindest sein).

    D.h. man hat eine Struktur (Liste/Scene-Graph/...) in der man seine Entities (Logische Spielobjekte) verwaltet. Und diese besitzen dann "Drawables", die in einem eigenen Scene-Graph verwaltet werden.

    Gezeichnet wird dann nur der "drawable" Scene-Graph. Das ist natürlich der selbe Ansatz wie bei Component Based Game Design. Macht man aber schon lange so. Bei Component Based Game Design geht es aber darum dass *alles* so gemacht wird, nicht nur ein Split zwischen Grafik und Logik.

    Kurzfassung:
    Klassisch = Graph mit Objekten
    Component Based Game Design = Datenbank mit Tables



  • Th69 schrieb:

    Das Component Based Game Design dagegen konzentriert sich eben auf die Komponenten, so daß diese je Entität austauschbar sind.

    Versteh ich nicht. Erst redest du davon, dass das Problem wäre, dass man viele verschiedene Listen brauchen würde (find ich jetzt noch nicht unbedingt problematisch), und viel Aufwand dadurch entsteht, diese Listen aktuell zu halten (das schon eher).
    Und dann kommt plötzlich dieser Satz und da sagst du, dass man die Komponenten pro Entität austauschen kann. Das hat ja mit den ganzen Listen nichts zu tun. Wenn es einen RenderManager gibt und er eine Liste von Renderables braucht, dann wird es ja dadurch nicht besser, dass die Klasse Spieler statt von Renderable abzuleiten in mehrere Komponenten aufteilst.


Anmelden zum Antworten