Du bist nicht angemeldet.

Werbeanzeige

1

02.10.2015, 17:40

ECS + Eventsystem vs. Übersicht behalten

Hi,

für meinen 2D Dungeoncrawler habe ich ein Entity-Component-System implementiert und lasse die Systeme über Events miteinander kommunizieren. So sendet das InputSystem ein InputEvent (ObjektID, Bewegungsrichtung) an das ActionSystem. Das ActionSystem prüft ob die Bewegung möglich ist (ggf. ist der Spieler gelähmt, tot oder schlimmeres ^^). Dann geht das Event weiter an's MovementSystem, wo die eigentliche Bewegung initiiert wird. Da geht ein MoveEvent raus und wird vom CollisionSystem weiterverarbeitet. Das prüft ob es zu einer Kollision kommt. Wenn ja sendet es (an verschiedene Systeme) ein CollisionEvent. Dann weiß das MovementSystem z.B. dass es das Objekt zurücksetzen und die Bewegung beenden soll. Ansonsten geht vom CollisionSystem ein MoveEvent (im Stile "Tile was Left") raus um verschiedene andere Sachen darüber zu informieren.
Ähnlich laufen bei mir Kampfberechnungen: Wenn der Spieler die Attack-Taste drückt geht ein ActionEvent (ObjektID, AktionsID -- d.h. Attack) vom Input- ans ActionSystem. Darf der Spieler angreifen wird ein AnimationEvent ausgelöst und das ActionEvent verzögert an das CombatSystem weitergeleitet (damit der Schaden berechnet wird wenn die Animation weit genug fortgeschritten ist). Ich denke wie es weiterläuft könnt ihr erahnen ... da gibt es noch DamageEvents, ExperienceEvents etc. pp.

Inzwischen bin ich an einer Stelle angelangt wo ich die verschiedenen Systeme in eine großen Topf werfe und der Gameloop seine Arbeit vollbringt. Dazu muss ich jedoch die Systeme miteinander verbinden.

Jedes System dass ein Event Foo sendet, erbt von einem entsprechenden Listener (analog für das Senden). D.h. ich kann z.B. das MovementSystem als InputListener (Bewegungseingabe), CollisionListener (weil es auf CollisionEvents vom CollisionSystem hört) und als MoveSender (klar^^) auffassen. FooListener und FooSender können miteinander verbunden werden, so dass der Sender an den Listener sendet. Allerdings wird das Verbinden ziemlich laang und groß. Hier ein Ausschnitt:

C-/C++-Quelltext

1
2
3
4
5
animation.bind<AnimationEvent>(action); // ActionSystem blockiert Ausführung von Aktionen bis zum nächsten Idle
action.bind<AnimationEvent>(animation); // ActionSystem löst die FrameAnimations aus
item.bind<AnimationEvent>(animation); // (*)
perk.bind<AnimationEvent>(animation); // (*)
animation.bind<AnimationEvent>(ai); // KI benachrichtigen --> onIdle()

(*) Items und Perks (verwende ich als Oberbegriff für Zauber und Fertigkeiten) lösen Animationen aus, aber erst wenn sie durch das Item- bzw. PerkSystem "durchgingen". So wird z.B. bei NotEnoughMana keine Zauberanimation ausgelöst - oder ein nicht vorhandener Trank löst keine Trink-Animation aus etc.

Hier noch ein Beispiel:

C-/C++-Quelltext

1
2
3
4
collision.bind<CollisionEvent>(movement); // Unterbrechen der Bewegung
collision.bind<CollisionEvent>(action); // (**)
collision.bind<CollisionEvent>(projectile); // (***)
collision.bind<CollisionEvent>(ai); // KI benachrichten --> onCollision() --> z.B. neuen Pfad suchen

(**) Das ActionSystem löst auch Bewegungsanimationen aus. Im Falle einer Kollision wird es informiert, dass die Bewegung abbricht.
(***) Kollisionserkennung (CollisionSystem) und -behandlung (ProjectileSystem, lässt Geschosse explodieren und den Kampf auslösen) sind getrennt.

Ich denke ihr erahnt wie das insgesamt aussieht .. übersichtlich ist anders! Zwecks Übersichtlichkeit habe ich schon versucht das ganze in Diagrammform zu packen. Aber da sind zu viele Kanten (Eventverbindungen zwischen Systemen) drinnen, als dass das übersichtlich wird.

Hat jemand Erfahrungen wie ich hier die Übersicht behalte? Ich habe zu jedem System dokumentiert welche Events woher kommen bzw. wohin gehen sollten und was sie ausdrücken.. aber dennoch fühle ich mich .. nicht verirrt, aber viel fehlt nicht :D

LG Glocke

Schorsch

Supermoderator

Beiträge: 5 206

Wohnort: Wickede

Beruf: Student

  • Private Nachricht senden

2

02.10.2015, 17:53

Meine Erfahrungen mit ECS sind da nicht groß anders. Man verliert schnell die Übersicht und neigt dazu die Ebenen nicht besonders schön zu trennen. Versuch deine logischen Ebenen vielleicht etwas besser zu definieren und kümmere dich um eine Verbesserung der Schnittstellen dazwischen. Dann abstrahierst du automatisch viel Logik weg die dich nicht mehr zu interessieren braucht.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

3

02.10.2015, 18:05

Habe noch nicht ganz Verstanden worum es dir geht?

Möchtest du dein System/Code Verbessern oder möchtest du sicher gehen das alle Listner mit den Richtigen Sendern Verbunden sind.

Für 2tes würde vielleicht eine kleine Datenbank helfen hier alle Beziehungen zwischen Sender und Listner Tabelarisch darzustellen.
Diese Daten könnte man dann auch noch Grafisch etwas aufbereiten, mit ListnerXYZ ist Verbunden mit X Sendern oder anders herum.

Falls es wirklich um Code geht, da kann ich leider nicht wirklich weiterhelfen.
Wer aufhört besser werden zu wollen hört auf gut zu sein!

aktuelles Projekt:Rickety Racquet

4

02.10.2015, 18:07

Das klingt ein bisschen nach übergeordneten Systemen ... was mir da spontan einfällt:

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
struct PhysicsSystems
    : EventListener<InputEvent>
    , EventSender<MoveEvent, CollisionEvent, ExplosionEvent> {
    
    MovementSystem movement;
    CollisionSystem collision;
    ProjectileSystem projectile;
    
    PhysicsSystems(...)
        : EventListener<InputEvent>{}
        , EventSender<MoveEvent, CollisionEvent, ExplosionEvent>{}
        , movement{...}
        , collision{...}
        , projectile{...} {
        // eingehenden, ausgehenden und internen Eventfluss erzeugen
        bind<InputEvent>(movement);
        movement.bind<MoveEvent>(collision);
        collision.bind<CollisionEvent>(movement);
        collision.bind<CollisionEvent>(projectile);
        collision.bind<CollisionEvent>(*this);
        collision.bind<MoveEvent>(*this);
        projectile.bind<ExplosionEvent>(*this);
    }
    
    void update(Time delta) {
        forwardAllIncommingEventsToInternSystems();

        movement.update(delta);
        collision.update(delta);
        projectile.update(delta);
        
        forwardInternSystemsOutgoingEventsToOutside();
    }
};


Und dann

C-/C++-Quelltext

1
2
3
4
5
6
7
8
PhysicsSystems physics{...};
BehaviorSystems behaviors{...}; // z.B. ActionSystem, AiSystem
GameplaySystems gameplay{...}; // z.B. ItemSystem, CombatSystem

physics.bind<MoveEvent>(behaviors);
physics.bind<CollisionEvent>(behaviors);
physics.bind<ExplosionEvent>(gameplay);
// etc.


Das würde zumindest Hierarchie reinbringen und die Internas verbergen (z.B. dass Movement und Collision getrennt sind).

@Koschi: Primär bzgl. Code, aber die andere Betrachtung spielt auch mit rein.

buggypixels

Treue Seele

Beiträge: 125

Wohnort: Meerbusch

Beruf: Programmierer

  • Private Nachricht senden

5

03.10.2015, 10:16

Generell finde ich es unglücklich mit Events zu arbeiten. Man springt dann immer wahllos im Code hin und her. Ein anderer Ansatz wäre es, dass jedes System alle Events
in einen Buffer schreibt und dann ein übergeordnetes System solch einen Buffer an ein oder mehrere andere System weiterreicht. Dadurch ist auch gewährleistet, dass
alle System in der vorgesehenen Reihenfolge arbeiten. Die Verarbeitung wird dann auch wesentlich effizienter, wenn z.B. das AnimationsSystem direkt einen Buffer
mit z.B. 10 Events bekommt. Außerdem kannst Du so die Zusammenhänge der einzelnen Systeme steuern. Durch Events springst Du kreuz und quer durch Deine Systeme
und auch das Debugging wird dann wirklich schwer. Zusätzlich sparst Du extrem viel Code, wenn Du nicht für jedes mögliche Event alle Listener verknüpfen mußt.
Ich würde auch nicht versuchen, dass übergeordnete System zu generisch zu machen. Im ersten Schritt einfach ein System komplett ausführen und dann den EventBuffer
an andere System senden von denen Du weißt, dass sie sich für solche Events interessieren. Oder einen Listener auf dieser Ebene einführen.

6

03.10.2015, 10:50

Generell finde ich es unglücklich mit Events zu arbeiten. Man springt dann immer wahllos im Code hin und her.

Events sind nicht gleich Events ;)

Ein anderer Ansatz wäre es, dass jedes System alle Events
in einen Buffer schreibt und dann ein übergeordnetes System solch einen Buffer an ein oder mehrere andere System weiterreicht. Dadurch ist auch gewährleistet, dass
alle System in der vorgesehenen Reihenfolge arbeiten.

Genau so läuft es bei mir auch. Die Systeme sind nacheinander dran, bearbeiten Events die an sie gesendet wurden und senden Events an andere Systeme. Dann ist das nächste System dran (und dann werden erst die Events, die an dieses System gerichtet sind abgearbeitet). :)

Durch Events springst Du kreuz und quer durch Deine Systeme
und auch das Debugging wird dann wirklich schwer.

Jetzt muss ich mal fragen: Was verstehst du unter Events? Direct dispatch?

Zusätzlich sparst Du extrem viel Code, wenn Du nicht für jedes mögliche Event alle Listener verknüpfen mußt.

Ich verknüpfe einmal alle relevanten Listener und dann läuft das Ding.

Werbeanzeige