Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

15.01.2015, 09:03

[Entity-Component-System] Daten wo speichern

Hi, ich habe mich in letzter Zeit mit Entity-Component-Design beschäftigt. Im Moment überlege ich wie ich die Daten anordne...

Nehmen wir uns mal einige Beispiel-Objekte:

Fackeln (befinden sich z.B. an Wänden)
  • Position
  • Animations-Daten
  • "Sichtradius" (für Beleuchtung)

Truhen (zum Looten)
  • wieder Position und Animations-Daten
  • Anzeigename ("Stahltruhe" etc.)
  • Inventar (um überhaupt zu Looten)
  • Kollisions-Daten

Gegner-Leiche (auch zum Looten)
  • wieder Position und Animations-Daten (wenn auch unanimiert, aber das ließe sich ja zusammenfassen)
  • Anzeigename ("Goblin")
  • Inventar (klar)
  • aber keine Kollisions-Daten (damit man nicht um tote Gegner herumrennen muss)

Geschosse (z.B. Pfeile oder Zauber)
  • wieder Position und Animations-Daten
  • aber kein Anzeigename
  • dafür wieder Kollisionsdaten (damit das Objekt mit einer Wand oder einem Gegner kollidieren kann)
  • neu sind Blickrichtung/Bewegungsrichtung und Geschwindigkeit
  • div. RPG-Daten (z.B. zur Schadensberechnung beim Aufschlag)

Charaktere (Spieler, Gegner, ggf. NPCs)
  • wieder Position und Animations-Daten
  • auch Anzeigename, Kollisionsdaten, RPG-Daten, Inventar
  • und ein Sichtradius (Spieler: Beleuchtung, Gegner/NPC: für KI)
  • auch hier Blickrichtung/Bewegungsrichtung und Geschwindigkeit
  • ggf. KI-Daten (z.B. "welchen Spieler habe ich im Auge"-Flag)
  • ggf. Keybindings (für Spielereingabe)
  • Aktions-Flag (Idle, Move, Attack etc.)
  • Aktions-Sounds

Den Anzeigenamen würde ich prinzipiell zu den Standarddaten zählen (muss ja bei Geschossen nicht angezeigt werden), die ich in der Entity geben würde. Aus dieser Sicht würde es sich also anbieten die Animations-Daten in der Entity zu speichern, weil ja jeder sie verwendet (teilweise eingeschränkt aber egal).. Allerdings steht das (imho) schonmal mit dem Entity-Component-Modell und der Entkopplung entgegen - was mir persönlich missfällt (und auch noch das Single-Responsibility-Principle verletzt).

Ebenfalls frage ich mich - wenn ich in Komponenten zerlege (und eine Render-Komponente habe): zählt Sichtradius zu den Render-Daten (wegen Beleuchtung), zu den KI-Daten (wegen Sichtweite für Berechnungen) oder doch zu den Standard-Daten weil die Verwendung mehrdeutig ist... Ich würde denken letzteres. Wie seht ihr das?

Um das ganze abzukürzen: mein bisheriger Ansatz war:
  • Entity = {Position, Anzeigename, Sichtradius}
  • Render-Component = {Animationsdaten}
  • Physics-Component = {Kollisionsdaten, Blickrichtung, Geschwindigkeit}
  • Avatar-Component = {Inventar, div. RPG-Daten, Aktions-Flag, Aktions-Sounds}
  • Logic-Component = entweder {Keybindings für Spieler} oder {KI-Daten und -Algorithmen für Gegner/NPCs}

Das führt nur unweigerlich dazu, dass die Avatar-Component (= RPG-Modell einer Entity) unweigerlich sehr groß wird. Schön ist allerdings, dass Rendering und Physics von einander gelöst sind - genauso wie die Logic (Abstraktion von Input und KI). Aber so 100%-ig zufrieden bin ich damit auch noch nicht :( Was mich beunruhigt ist, dass die Avatar-Component eben sehr groß werden kann, da "div. RPG-Daten" ein weites Feld ist ^^

Zu Fackeln, Truhe, Gegner-Leichen, Geschossen, Charakteren und der Physics-Componente: Da war die Idee, dass ich verschiedene Physics-Implementierungen habe: AmbiencePhysics (keine Kollision, Geschwindigkeit oder Blickrichtung), EnvironmentPhysics (Kollision aber keine Geschwindigkeit oder Blickrichtung), DefaultPhysics (alle Daten, da von Geschossen und Charakteren verwendet).

Wie denkt ihr darüber?

LG Glocke

/EDIT: Was mir noch einfällt: ggf könnte ich die AvatarComponent mittels Komposition wieder zerlegen: Inventar-Objekt, Stat-Objekt (z.B. zur Schadensberechnung), usw. :search: )

TrommlBomml

Community-Fossil

Beiträge: 2 117

Wohnort: Berlin

Beruf: Software-Entwickler

  • Private Nachricht senden

2

15.01.2015, 09:27

Vielleicht solltest du auch bei Architektur bedenken, dass neben Trennen der Daten aus Handhabung von Abhängigkeiten gehören. Das bedeutet, dass nunmal Komponenten voneinander abhängig sein können, da hilft auch das penibelste auftrennen nichts. Die Kür ist dann meiner Meinung nach, diese Abhängigkeiten Sauber auszudrücken.

Dementsprechend ist es vielleicht gar nicht so wichtig, wem du was zuordnest in Bezug auf Abhängigkeiten sondern Bezug auf logische Zusammenhänge. Das finde ich persönlich viel wichtiger, als die Abhängigkeiten möglichst klein zu halten.

Zum Beispiel gehören für mich zur Render-Komponente keine Animationsdaten. Man kann auch etwas animiertes Rendern, ohne die Animation zu aktualiseren. Der Anzeigename gehört für mich eher zum Avatar, wenn dann hat das Entity eine Id und Tag.

Aber mir gefällt dein Vorgehen. Selten lese ich jemanden der sich so gründlich Gedanken um Architektur macht, da muss ich dich sehr loben!

kiba

Alter Hase

Beiträge: 327

Wohnort: NRW

Beruf: Azubi: Fach-Info. Anw.

  • Private Nachricht senden

3

15.01.2015, 18:46

Ich hab auch seit einiger Zeit mit ECS angefangen und hab auch so meine Probleme.

Also ich würde das ganze noch in Map und RPG Components auf teilen.
Und so paar Grunddaten wie Name.
Die Systeme können sich dann ja die notwendigen Components raus nehmen.
Map(Position, Sicht) + Graohics(Sprite, Anmation) fürs Rendern.
Map + Body(Kollision, Matrix) für PhysiksSystem.

Zudem kommt noch das später die System sich mit Events etwas untereinandern austauschen können.
z.B. Trigggert das (Map)PhysikSystem dem AnimationsSystem der Spieler wurde entdeckt und lässt etwas Aufblinken oder dem KI/BattleSystem der Gegner ist alamiert.
PhysikSystem kann dem BattleSystem triggern der Spieler wurde getroffen
Mithilfe der Events kann man Infos wie EntityIDs mit senden um z.b. Angreifer zu bestimmen fürs Damage berechnen.
Wenn die Systeme und die Entitys sich in ein Manager befinden das sollte das funktionieren.

Wo ich noch Probleme hab ist was darf oder SOLL alles in ein Component.
premetives, (c++)string, vector-array (list, set, map vermeiden), stl und boost Sachen wie chrono, date_time?
Wie sieht es aus mit Component IN Component?, POD in Componente?
Ich hab immer das Gefühl vieles wieder verwenden zu wollen (Position, HP, MP, ...) oder das vieles gleich aussieht und nur ein anderen Bedeutung/Nutzen hat (klingt alles sehr nach OOP).
Und wie sieht es aus mit Hilfsfunktionen, Setter(umwandeln), Gettern(umwandeln), Default-ctor (um int, float, ... auf 0 zu setzen)?
"Komposition an Stelle von Vererbung", PlayerComponent und EnemyCompoenent haben beide ein "BaseActorComponent"?

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Component? POD?
struct TimerData {
    int start;
    int end;
};

// extends ?
struct SpecTimer {
   TimerData base;
}

// Compoent ? Entity ?
struct Actor {
     TimerData timer1; // für balbal ....
     TimerData timer2; // für ...
}

Entity player;
player.assign(Actor());
player.assign(SpezTimer());
player.assign(PlayerRPG());

4

16.01.2015, 10:13

Moin, erstmal danke für euren Antworten! :)

Zum Beispiel gehören für mich zur Render-Komponente keine Animationsdaten. Man kann auch etwas animiertes Rendern, ohne die Animation zu aktualiseren. Der Anzeigename gehört für mich eher zum Avatar

Guter Punkt! Ich verwende eine fertige Animator-Implementierung für FrameAnimations, die ich noch etwas zu einem Behavior-Manager erweitert habe (konkret: bestimmtes Verhalten loopt bis es neu gesetzt wird - wie Walk, anderes geht am Ende in einen anderen Zustand über - z.B. nach Attack kommt Stand, und nochmal andere verbleiben im letzten Frame ihrer Animation - z.B. Die). Dieses Behavior dann in der Entity anzusiedeln erscheint mir am sinnvollsten: Ich halte diese Daten somit zentral - macht Sinn, da sie auch von vielen Komponenten verwendet werden.

Aber mir gefällt dein Vorgehen. Selten lese ich jemanden der sich so gründlich Gedanken um Architektur macht, da muss ich dich sehr loben!

Dankeschön :)

Also ich würde das ganze noch in Map und RPG Components auf teilen.
Und so paar Grunddaten wie Name.
Die Systeme können sich dann ja die notwendigen Components raus nehmen.
Map(Position, Sicht) + Graohics(Sprite, Anmation) fürs Rendern.
Map + Body(Kollision, Matrix) für PhysiksSystem.

Map-Komponenten sind bei dir Entity-Komponenten, die einen Bezug zur Map / Scene / (in meinem Fall) Dungeon haben? Vielleicht verstehe ich dich falsch - die Arbeit meiner Komponenten hatte ich mir wie folgt vorgestellt: [...] (Ich erstelle nachher noch ein paar UML Diagramme dazu und hänge sie hier an.)

Zudem kommt noch das später die System sich mit Events etwas untereinandern austauschen können.
z.B. Trigggert das (Map)PhysikSystem dem AnimationsSystem der Spieler wurde entdeckt und lässt etwas Aufblinken oder dem KI/BattleSystem der Gegner ist alamiert.
PhysikSystem kann dem BattleSystem triggern der Spieler wurde getroffen
Mithilfe der Events kann man Infos wie EntityIDs mit senden um z.b. Angreifer zu bestimmen fürs Damage berechnen.
Wenn die Systeme und die Entitys sich in ein Manager befinden das sollte das funktionieren.

Zum Thema Messaging hatte ich mir schon erste Gedanken gemacht... die ich aber noch nicht voll strukturiert habe - weil ich mit dem Komponentensystem noch nicht zufrieden bin. Sobald ich da einen Ansatz habe der mich zufrieden stellt, überlege ich mir ein Messaging-System (vermutlich auf Basis von Direct Dispatch und Broadcasts).

RenderComponent, der sowohl Animationen als auch statische Bilder anzeigen kann.
PhysicComponent der auf Box2D zugreift und Rect, Circle oder Polygone als BoundingBox verwenden kann
LightComponent für Box2dLights, sämtliche Lichter
InteractionComponent für klickbare Objekte, mit Bereichstrigger und anbinung an das Dialogsystem
ControllableComponent für Spielercharakter

Kannst du zur InteractionComponent und dem Dialogsystem noch mehr erzählen? Der Punkt hat mein Interesse geweckt^^ Ich hatte (in einer früheren Implementierung, die Overdesign und dadurch extrem verwinkelt war) eine Hud-Implementierung. Das ECS hatte z.B. Kampfevents (Damage received, Enemy Killed etc.) an alle gebroadcastet, so dass diese auch im Hud ankamen, wo dann "Floating Damage Labels" (wie man sie aus eigentlich fast jedem RPG kennt) und ein Kill-Counter angezeigt wurden.

LG Glocke

eXpl0it3r

Treue Seele

Beiträge: 386

Wohnort: Schweiz

Beruf: Professional Software Engineer

  • Private Nachricht senden

5

16.01.2015, 11:10

Blog: https://dev.my-gate.net/
—————————————————————————
SFML: https://www.sfml-dev.org/
Thor: http://www.bromeon.ch/libraries/thor/
SFGUI: https://github.com/TankOs/SFGUI/

6

16.01.2015, 11:15

Ah ok, danke LetsGo.

Ich hatte oben erwähnt, dass ich noch UML-Diagramm(e) anfertigen wollte um mein Konzept klarer zum Ausdruck zu bringen. Das Klassen-Diagramm ist soweit fertig. Bei Bedarf kann ich zu einzelnen Aspekten noch ein Sequenzdiagramm anfertigen um den Kontrollfluss zu verdeutlichen. Das ganze ist natürlich unvollständig und nicht final :)



Ein Beispiel:
  • Der Mainloop kennt die ObjectInput-Komponenten aller Spieler und informiert mittels objInput.pushEvent(myEvent) über alle auftretenden Eingaben.
    • ObjectInput verarbeitet das Event entsprechend seiner Implementierung.
  • Der Mainloop kennt außerdem alle Dungeons, die sich momentan im Spiel befinden und informiert mittels dungeon.update(frameTime) darüber, dass ein neuer Frame durchlaufen wird.
    • Ein Dungeon kennt seine Entities (oben GameObjects genannt) und informiert mittels object.update(frameTime).
      • Ein GameObject kennt seine Components und informiert mittels component.update(frameTime).
        • Dieser Call kann z.B. in einer ObjectInput ankommen, so dass die Komponente z.B. schaut, welches Behavior durch ein oder mehrere Events (Space, Shift+K etc.) ausgelöst wurde.
        • Da jede Komponente sein "parent"-GameObject kennt, kann es von diesem mit parent.getAvatar() die Avatar-Komponente abrufen (die in diesem Design den BahviorManager besitzt).
        • Schließlich kann der Avatar gefragt werden, ob das Setzen des neuen Behaviors gültig ist und dieses schließlich ggf. durchführen.
          • Dadurch würde setBehavior von ObjectAvatar aufgerufen, welches das Behavior zunächst setzt.
          • Anschließend würde es sein parent-GameObject über die Änderung informieren, diese dann bestimmte oder alle Komponenten und fertig. (Die API dafür fehlt wie oben angesprochen bisher noch).
Sieht bisschen verschachtelt aus .. :D

LG Glocke

/EDIT:

Gerade überflogen, sieht interessant aus! Danke, werde ich mir bei Gelegenheit zu Gemüte führen :)

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Glocke« (16.01.2015, 11:23)


7

18.01.2015, 18:53

Hi, nachdem ich mich die letzten Tage mit Data-oriented Design (allgemein und im Bezug auf Entity-Systeme) beschäftigt habe, habe ich das Design aus dem vorherigen Post massiv umgekrämpelt :D Zusätzlich habe ich mir zum Messaging Gedanken gemacht und eine kleine EventQueue auf Basis von std::vector geschrieben (absichtlich nicht mit std::queue da ich alle pro Update alle Events in einem Loop verarbeite).

Nun bin ich wieder in meiner Ausgangssituation - wenn auch modifiziert: Ich überlege meine GameObject-Klasse (aka Entity-Klasse) gegen einen GUID auszutauschen. Meine GameObjects sind im Moment reine Komponenten-Container... Im Grunde möchte ich dieses Entity-has-Component-Beziehung in die Subsysteme auslagern, d.h. das PhysicsSystem weiß welche Komponente zum Objekt #13 gehört etc. Zusätzlich will ich versuchen Datenabhängigkeiten zu vermeiden - die einzige Alternative wären aus meiner Sicht aber nur Datenredundanzen. Oder konkret:

  • Die Position wird vom Physics- und vom Render-System verwendet (Bewegung, Kollision; Zeichenposition).
  • Da ich meine GameObjects "entsorgen" will, kann ich die Position nicht mehr zentral verwalten.
  • Möglichkeiten:
    1. Position nur im Physics-System hinterlegen. Macht Sinn, da das Subsystem die Position auch verändert. Das Render-System müsste dann die Position beim Physics-System erfragen. :zombie:
    2. Position in allen Subsystemen speichern, die sie brauchen. Wird sie geändert (idealerweise nur von einem System, die anderen lesen nur), würde die Änderung an alle anderen Systeme propagiert werden. :golly:
  • Beide Varianten überzeugen mich nicht so

Fall 1: Datenabhängigkeit. Wenn das Render-System jedesmal bei der Physik nachfragen muss wo das Objekt steht, habe ich (bei Iterieren im Render-System) zusätzliche Indirections - mal abgesehen von der Abhängigkeit von einem anderen System.

Fall 2: Datenredundanz. Der Gedanke, dass ich eigentlich identische Daten in verschiedenen Subsystemen habe und sie (im Falle der Position teilweise jeden Frame) aktualisieren und propagieren muss, erscheint mir mehr Aufwand zu sein als sinnvoll ist.

Nun ist die Position (wie eingangs beschrieben) kein Einzelfall... je länger ich über das Gesamtsystem nachdenke, desto mehr Daten werden von anderen Subsystemen verwendet...
  • Input-System müsste zu jedem Objekt (hier konkret: Spieler-Objekt, da alles andere nicht auf Spielerinput direkt reagieren braucht) das Verhalten der Spielfigur (Idle, Move, Attack etc.) abgefragt werden .. denn befindet die Figur sich in einem längeren Cast-Zustand soll sie sich u.U. nicht wegbewegen dürfen. Auch wenn das Beispiel etwas konstruiert wirkt, könnt ihr sicher trotdzem erahnen was ich grob meine :) Zusätzlich müsste es sich (wenn sich das Objekt schon bewegt hat und nur die Richtung ändert) auch die bisherige Richtung merken (die eigentlich Teil der Physik ist).
  • Das Animations-System braucht z.B. die Blickrichtung, um dem Sprite den richtigen Frame zu geben.
  • Das KI-System braucht diverse Angaben verschiedener System - Entscheidungen der Gegnerwahl könnten z.B. auf Basis von Entfernung zu einem selbst, der verbleibenden Lebensenergie oder sonstigem sein.
Gerade durch die KI (aber auch durch die anderen Subsysteme) habe ich ziemlich viele (teilweise komplexe) Abhängigkeiten. Habt ihr einen Rat wie ich diese Abhängigkeiten in den Griff bekomme? "Datenredundanz vs. -abhängigkeit" scheint im Moment der einzige Weg .. :dash: .. oder (um Xardas aus Gothic 3 zu zitieren)

Zitat

Aber vielleicht gibt es noch einen Weg, einen dritten, unabhängig von den offensichtlichen


LG Glocke :)

DeKugelschieber

Community-Fossil

Beiträge: 2 641

Wohnort: Rheda-Wiedenbrück

Beruf: Software-Entwickler

  • Private Nachricht senden

8

18.01.2015, 19:31

Ich habe inzwischen sehr viele Systeme und Möglichkeiten in diesen durchprobiert. Im Endeffekt finde ich kann man einfach Code schreiben und keine supa-dupa Allzweckwaffe entwickeln. Ganz einfach, wir wollen Objekte haben, sei es virtuelle oder materielle (sichtbare, Modelle z.B.) die sich über ihre Daten definieren. Diese wollen wir in einem oder mehreren loops updaten. Die Bedingung dafür ist nur dass alle benötigten Komponenten in einem System verfügbar sind.

Also, super einfach und es trifft trotzdem die Idee des ganzen (finde ich):

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Position{
  public:
    float x, y;
    // ...
}

class Mesh{
  // ...
}

class Texture{
  // ...
}

// Basisklasse für alle Entites
class Entity{}

class WorldEntity:public Entity{
  public:
    // Komponenten
    Position position;
    Mesh mesh;
    Texture texture;
    // ...
}

// Basisklasse für alle Systeme
class System{
  private:
    std::vector<Entity*> entities;
  public:
    // Methoden zum hinzufügen, entfernen usw. von Entities
    // evt. mehr Logik, die sich auf das System bezieht
    // und natürlich unsere loop Methode, die überschrieben werden muss:
    virtual void update(const float deltaTime) = 0;
}

class WorldEntityCollisionSystem{ // k.A. ist nur ein Beispiel...
  public:
    void update(const float deltaTime){
      // for und was sonst noch so nötig ist
    }
}


Wenn Komponenten entfernt werden sollen kann man diese auf 0/nullptr setzten und prüft dies einfach im System. Ob Komponenten jetzt noch eine Basisklasse brauchen weiß ich nicht, kann aber Sinn machen wenn man Objekte aus einem Pool bereitstellen möchte oder ähnliches.

9

18.01.2015, 21:20

Naja den Ansatz mit Subsystemen (Komponente mit Daten + Manager/System mit Logik) möchte ich eigentlich schon beibehalten. Das hat weniger was mit Allzweckwaffen und mehr mit der Trennung verschiedener Subsysteme zu tun. Eine vollkommen monolithische Architektur möchte ich vermeiden.

kiba

Alter Hase

Beiträge: 327

Wohnort: NRW

Beruf: Azubi: Fach-Info. Anw.

  • Private Nachricht senden

10

19.01.2015, 04:08

Also wenn du alle Entitiys in ein Manager handelst dann hast du das Problem mit den redundanten Daten nicht.
Der Manager beinhaltet dann noch die ganzen Systeme und die Events, welchen das Problem der abhänigkleit nimmt.
Jedes System holt sich von den entitys immer nur die Componente es die gerade benötigt und kann wenn es nötig ist Events triggern.
Durch die Events können dann die anderen System darauf reagieren.
Das Animations-System braucht z.B. die Blickrichtung, kein Problem dann holt die die Componente die dafür zuständig sind, unabhängig davon ob das RenderSystem auch die gleich Componente verwendet.
In den Systeme kannst du auch den EntityManager mi tübergeben (durch eine reference), damit kannst du dir dann die Entitys per ID holen um z.b. etwas an den Componente zu verändern. (z.B. ausgelöst durch ein Event).
Das KI-System kann auch die PhysikComponente verwenden um Entfernungen zu ermitteln, oder das PhysikSystem triggert einfach ein Event(SichtweiseEvent) welchen das KISystem fängt.


(Link)


Bedenke das die Entity-Klasse NUR eine ID hat wimit die Componente im Manager verwaltet.
Die Entity create-Methoden erstellen dann Componente welche der neuen Entity(ID) zugewiesen werden.

Werbeanzeige