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

27.01.2015, 17:36

[Messaging] Kommunikation von Subsystemen

Hi, ich habe das Physik-System für mein Spiel fertig und überlege im Moment wie ich es mit den anderen Subsystemen zusammenarbeiten lasse. Bei meinem Spiel handelt es sich um ein 2D RPG (im Stile Diablo 1/2 etc.). Hier eine Übersicht welche (Sub-)Systeme ich habe bzw. plane:

  • Physik-System für Bewegungsinterpolation und Kollisionserkennung
  • Grafik-System für Animationsinterpolation und Zeichnen der Szene
  • Input-System für das Abbilden der Spieler-Eingabe auf Spielfigur-Aktionen (Bewegen, Angreifen usw.)
  • KI-System zum Erzeugen von Spielfigur-Aktionen der KI-Objekte
  • Game-System ... sicherlich wird das intern aufgeteilt, betrachten wir es hier aber mal als große Blackbox mit der eigentlichen Spiellogik.
  • erstmal kein Audio-System .. das kommt irgendwann später, will ich hier erstmal auslassen
Nun habe ich mir überlegt auf EventQueues zu setzen, um das Messaging zwischen den Systemen möglichst locker zu gestalten. Ein paar Events habe ich mir schon einmal überlegt:
  • InputEvent: z.B. Move(ObjectID, Direction) oder Stop(ObjectID)
  • PhysicsEvent: TileCollision(ObjectID, Pos), ObjectCollision(ObjectID, AnotherObjectID)
Gehen wir mal in das InputSystem rein ... abhängig von der konfigurierten Steuerung würde das System dann ein entsprechendes InputEvent erzeugen und nach außen senden. Je nachdem wer als Empfänger registriert ist bekommt es dann weitergeleitet. Nun stehe ich vor der Frage: Wann prüfe ich z.B. ob eine Bewegung erlaubt ist? (Annahme: Eine Bewegung ist erlaubt wenn die Spielfigur nicht tot oder gelähmt ist, und das Ziel der Bewegung nicht ungültig oder blockiert ist).

Das Physik-System arbeitet im Moment wie folgt: Es bekommt ein InputEvent und versucht beim nächsten update() die angegebene Figur in die angegebene Richtung zu bewegen. Natürlich weiß es nicht viel von der Figur: Nur wo sie steht und ggf. noch Kollisionsparameter. Kommt es zu einer Kollision, erzeugt es ein entsprechendes PhysicsEvent und propagiert es an alle Listener. Kommt es zu keiner Kollision, propagiert das System kein Event.
Nun würde man - zur Überprüfung ob die Figur lebt und nicht gelähmt ist - eine Art "Avatar-System" / "Character-System" / whatever haben.

Natürlich soll das Physik-System erstmal nichts vom Avatar-System wissen .. warum auch ^^ In der Folge müsste das Input-System vor dem Propagieren eines InputEvents das Avatar-System fragen, ob die Figur die entsprechende Aktion aus Sicht des Avatar-Systems (d.h. im Bezug auf Lebensenergie und "ist-gelähmt-oder-nicht") ausführen darf. Dann könnte das Physik-System nach erfolgreicher Realisierung des InputEvents dieses nach außen weiterleiten, so dass wir quasi eine Systemreihenfolge: Input -> Physics -> Avatar bzgl. der Eventweiterleitung haben.

Was haltet ihr von dieser Idee? Welche Alternativen fallen euch ein? Ich will nicht die nächst-beste Idee umsetzen, daher das ganze hier ... :D

LG Glocke

kiba

Alter Hase

Beiträge: 327

Wohnort: NRW

Beruf: Azubi: Fach-Info. Anw.

  • Private Nachricht senden

2

27.01.2015, 18:49

Also so auf die schnelle würde ich sagen...

Reihenfolge der System würde ich jetzt auch sagen.
LevelSystem
AISystem
...
InputSystem
PhysicSystem
MovementSystem

Ich würde dann noch so was wie ein MoveComponenet haben mit isCollosionMovable( oder so) und der velocity.
Die AI oder InputSystem setzt dann den Player in bewegung in dem es die velocity setzt/erhöht (0 = Stillstand).
Das PhysicSystem kann dann eine "probe" Position berechnen mit der dann die Collision ermittelt wird, dann ein isCollosionMovable-Flag setzen.
Im MovementSystem kommt dann alles zusammen, es wird überprüft ob man sich bewegten kann (isCollosionMovable, isPlayerDead, ...) und dann endgültig die neue Position gesetzt und ggf. die velocity wieder zurückgesetzt.

3

27.01.2015, 19:58

Das PhysicSystem kann dann eine "probe" Position berechnen mit der dann die Collision ermittelt wird, dann ein isCollosionMovable-Flag setzen.
Im MovementSystem kommt dann alles zusammen, es wird überprüft ob man sich bewegten kann (isCollosionMovable, isPlayerDead, ...) und dann endgültig die neue Position gesetzt und ggf. die velocity wieder zurückgesetzt.

In Anlehnung an deinen Vorschlag: Ich könnte (da meinen Physik-System Bewegung und Kollision in einem Aufwasch macht, das Behandeln von InputEvents aber vom eigentlichen Update-Loop losgelöst ist) mir folgende Reihenfolge vorstellen:
  1. Input- und KI-System updaten (d.h. InputEvents erzeugen und an Physik-System propagieren)
  2. Input-Events vom Physik-System behandeln lassen. Dabei wird der Kollisionstest durchgeführt und ein Movement-Flag (dass sich das Objekt bewegen will) gesetzt. Nachdem das erfolgreich war, leitet das Physik-System die jeweilgen Input-Ents an das Avatar-System weiter.
  3. Dann werden die Input-Events vom Avatar-System behandelt und quasi Phase 2 der Input-Verifizierung durchgeführt, d.h. der Test ob sich das Objekt bewegen darf (isDead, isStunned etc.). Wenn das Objekt die Aktion nicht durchführen darf, wird das Movement-Flag vom Avatar-System zurückgetzt... "nein, darfst du nicht" ^^
  4. Erst dann würde das Physik-System aufgefordert seine Komponenten zu aktualisieren (und ggf. zu bewegen, sofern das entsprechende Flag noch gesetzt ist).
  5. Und weiter im Text
Da ich bei der Bewegung an Sich auch weiterhin die Kollisionsprüfung brauche, sehe ich keinen wirklichen Vorteil darin, Kollision und Bewegung in zwei Systeme aufzuteilen. Ich will versuchen die Abhängigkeiten zwischen den Systemen so gering wie möglich zu halten :)

LG Glocke

kiba

Alter Hase

Beiträge: 327

Wohnort: NRW

Beruf: Azubi: Fach-Info. Anw.

  • Private Nachricht senden

4

28.01.2015, 16:14

Hmmm...mir gefällt das weiterleiten des Events nicht so.
Du musst dann ja quasi sicher stellen das es auch durch alle Systeme durch geht und wenn du mal vergisst weiterzuleiten dann kriegen die anderen es vll nicht.
So wie du dein Movement-Flag beschreiben hast schaft du dir Abhängigkeiten in den Systemen, denn einige Systeme müssen dann schauen "Lohnt es sich den Spieler zu bewegen wenn das flag schon false ist?, Wurde das Flag schon von den anderen System gesetzt?, oldvalue &= newvalue"

Ich hab auch zur Zeit das Problem, dass einige meiner Systeme immer wieder mal die selbe Funktion/Methode benötigen, ich versuch es zur Zeit mit ein paar Utitilty-Klasse welche dann einige Hilfsmethoden beinhalten (keine Data)
und überlege noch wie ich das System noch weiter aufteilen könnte, damit es auch wirklich nur eine Aufgabe macht.
Du kannst ja das Event von mehrere Systeme verarbeiten lassen.
Das PhysikSystem setzt dann z.b. isCollision-Flag und das AvatarSystem dann den isDead-Flag.
Zum Schluss entscheidet das MovementSystem ob sich das Object weiter bewegen darf.

Ich weis ja nicht was in ein PhysicSystem noch so alles drin steck aber vll könntest du es noch mehr aufteilen z.b. in eine CollisionsSystem, GravitationsSystem, BulletSystem, ...
Das MovementSystem soll dann auch wirklich NUR prüfen ob es ich bewegen darf bzw. die neue Position setzen.
Alles andere wie Kollisionsprüfung macht dann z.b. das CollisionsSystem.

5

28.01.2015, 20:48

Die Reihenfolge, die ich in meinem letzten Post beschrieben hatte, habe ich aus einigen Gründen nochmal überarbeitet. Das eigentlich einzige InputEvent was "Zustimmung" von Avatar- und Physik-System braucht, ist Movement. Alles andere kann alleine vom Avatar-System entschieden und umgesetzt werden.

Betrachten wir mal den Fall dieser Bewegungs-Events... Verhindert werden kann eine Bewegung durch Kollision mit dem Terrain / einem anderen Objekt (Erkennung ist Aufgabe meines Physik-Systems) oder durch Bewegungsunfähigkeit der Spielfigur (Lähmung, Tod etc.). Andere Events (Angreifen, Zaubern, Item benutzen, eine Truhe looten etc.) bedürfen im Grunde nur noch der "Zustimmung" des Avatar-Systems (nach wie vor als Subsummierung der "eigentlichen" Spiel-Logik). Also ist es prinzipiell möglich das InputEvent zunächst der Physik zu übergeben. Daher ist meine Überlegung folgende:

  1. Das Input- bzw. KI-System erzeugen gemäß Eingabe bzw. KI-Logik die neuen InputEvents und leitet sie an das Avatar-System weiter.
  2. Das Avatar-System prüft nun, ob die gegebenen InputEvents erlaubt sind (z.B. ob gelähmt oder tot, ob genug Manavorrat etc.). Sind die Events erlaubt, wird das Behavior state des Objekts entsprechen manipuliert (Move, Attack, Cast, etc.) und die Events nun an das Physik-System weitergeleitet.
    Alle Events die nicht zugelassen wurden (z.B. weil die Figur gelähmt ist) werden "verschluckt". Das Input-System erhält keine Aussage darüber. Ggf. könnte das Audio-System informiert werden, um einen "Not enough Mana"-Sound abzuspielen ^^
    Mehr passiert im Avatar-System an dieser Stelle noch nicht, d.h. erstmal nur das Reihe behandeln der InputEvents.
  3. Weiter geht es im Physik-System: Hier sind inzwischen die vom Avatar-System abgesegneten InputEvents eingegangen. Für die Bewegungen unter den Events prüft das System auf Kollision. Kommt es zu einer Kollision, wird ein Physik-Event mit näheren Details an das Avatar-System gesendet. Ggf. könnte es auch an das Audio-System geschickt werden, um einen Kollisions-Sound (irgendwo-dagegenrenn-Sfx ^^ ) abzuspielen.
    Alle InputEvents die vom Physik-System abgesegnet wurden, werden an das Grafik-System weitergeleitet.
    Im Anschluss werden die eigentlichen Bewegungsinterpolationen und ggf. weitere Kollisionstests durchgeführt (und damit ggf. weitere Physik-Events generiert).
  4. Nun kommt das Avatar-System zum "richtigen" Einsatz: Erstmal werden die angesammelten Physik-Events abgearbeitet, d.h. Figuren die bisher das Behavior Move hatten werden wieder auf Idle zurückgesetzt. Sollte ein Bewegungs-InputEvent vom Avatar-System zuvor akzeptiert worden, vom Physik-System aber aufgrund Kollision abgelehnt worden sein, ist das Gesamtbild ab dieser Stelle wieder stimmig.
    Danach werden diverse spiellogische Sachen erledigt: Schadensberechnung, Looting, Skill-Cooldowns etc. durchführen.
  5. Danach kommt schließlich das Grafik-System zum Zuge: Hier sind inzwischen InputEvents eingegangen die dazu verwendet werden neue Animationen abzuspielen. So weiß das Grafik-System z.B., dass sich eine bestimmte Figur X nun in Richtung Y bewegt und kann die entsprechende Animation abspielen.
    Zum Schluss wird die Szene dann noch gezeichnet :)

Oder etwas verknappt (aus Sicht der Events):
  1. InputEvents von Input-/KI-System an Avatar-System senden
  2. InputEvents im Avatar-System prüfen, ggf. an Physik-System weiterleiten
  3. InputEvents im Physik-System prüfen, ggf. umsetzen und an das Grafik-System weiterleiten oder PhysicsEvents an das Avatar-System senden
  4. PhysikEvents im Avatar-System auswerten
  5. InputEvents im Grafik-System auswerten.

Hmmm...mir gefällt das weiterleiten des Events nicht so.
Du musst dann ja quasi sicher stellen das es auch durch alle Systeme durch geht und wenn du mal vergisst weiterzuleiten dann kriegen die anderen es vll nicht.

Naja, ich habe mir Utility-Klassen geschrieben, hier mal die Auszüge aus der Headerfile dazu:

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
template <typename E>
class EventListener {
    private:
        std::vector<E> in;
        
    public:
        /// Push an event to the listener
        void pushEvent(E const & event);
        
        /// Dispatch all using a concrete listener
        template <typename Listener>
        void dispatch(Listener& listener);
};

template <typename E>
class EventSender {
    private:
        std::vector<E> out;
        std::vector<EventListener<E>*> target;
        
    public:
        /// Enqueue event for sending
        void propagate(E const & event);
        /// Register listener
        void addListener(EventListener<E>& listener);
        /// Unregister listener
        void removeListener(EventListener<E>& listener);
        /// Send all enqueued events
        void broadcast();
};

Ich denke wenn ich die addListener-Aufrufe beim Erzeugen der System-Instanzen nicht vergesse, dürfte das ganze relativ glatt laufen :)

Das PhysikSystem setzt dann z.b. isCollision-Flag und das AvatarSystem dann den isDead-Flag.
Zum Schluss entscheidet das MovementSystem ob sich das Object weiter bewegen darf.

Ich weis ja nicht was in ein PhysicSystem noch so alles drin steck aber vll könntest du es noch mehr aufteilen z.b. in eine CollisionsSystem, GravitationsSystem, BulletSystem, ...
Das MovementSystem soll dann auch wirklich NUR prüfen ob es ich bewegen darf bzw. die neue Position setzen.
Alles andere wie Kollisionsprüfung macht dann z.b. das CollisionsSystem.

Ich persönlich mag eine "exzessive" Aufteilung in Subsysteme nicht so sehr .. für mich sollte ein System in sich "rund" sein, so dass ich es als Black-Box verwenden kann: Es erwartet gewisse eingehende Events, manipuliert die Daten entsprechend bzw. liefert es bestimmte Events zurück. Wenn ich z.B. das Physik-System in Kollisions- und Bewegungssystem zerlege - oder das Grafik-System in Animations- und Render-System, habe ich zwischen den Physik- bzw. Grafik-Sub-Systemen ziemlich viele Abhängigkeiten:
  • Das Kollisionssystem muss wissen wohin sich ein Objekt bewegt .. und damit nicht nur Position + Richtung, sondern auch dass es sich bewegt.
  • Das Bewegungssystem braucht die Angaben auch, um die Bewegung ausführen zu können.
  • Das Animationssystem (dabei beziehe ich mich mal konkret auf die Thor-Library und die dort enthaltene Animator-Implementierung!) muss das zu animierende Objekt kennen (Sprite etc.).
  • Das Rendersystem braucht diesen Sprite aber auch.
Dabei stellt sich dann die Frage: Wem "gehört" der Sprite: Animations- oder Rendersystem? Wem "gehören" Position, Richtung und "isMoving"-Flag: Kollisions- oder Bewegungssystem? Auch aus diesen Gründen habe ich die Systeme zusammengefasst.

Um das ganze abzurunden: Ich will dir die Aufteilung in kleinere Subsystem nicht zerreden :D Sicherlich gibt es auch gute Gründe dafür, wie z.B. die aus der geringen Größe und Komplexität der Logik resultierende Übersichtlichkeit :)

LG Glocke

PS: An diesem Post habe ich jetzt ewig geschrieben XD Irgendwie habe ich den gesamten Text mind. 2x komplett gelöscht, meine Gedanken umstrukturiert und neu formuliert... das ganze ist gar nicht so trivial wie es vllt. scheint :fie:

6

29.01.2015, 13:13

Servus :) Ich habe noch einmal in Ruhe über meinen Ansatz nachgedacht und festgestellt, dass ich im Grunde in meinem InputSystem eine Möglichkeit suche, ein Event vom AvatarSystem prüfen zu lassen (ob aus dessen Sicht realisierbar oder nicht). Da ich beide System aber von einander entkoppelt haben möchte, ist die Lösung etwas subtiler ^^ . Sie lautet: C++11 Lambdas.

Die Idee: Das InputSystem erzeugt ja InputEvents gemäß seiner Vorgaben (d.h. der konfigurierten Steuerung). Bevor es die Event propagiert, könnte es ein Lambda fragen, ob das Event überhaupt "erlaubt" ist. Das Lambda leitet die Frage an das AvatarSystem weiter - ohne dass beide Systeme voneinander wissen müssen.

Ein minimales, kompilierbares Beispiel:

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
#include <functional>
#include <iostream>

struct InputEvent {
    // details ...
};

using Verifier = std::function<bool(InputEvent const & event)>;

class InputSystem {
    private:
        Verifier verify;
        
    public:
        InputSystem(Verifier verify) : verify{verify} {}
        
        void update() {
            // poll event from somewhere ...
            InputEvent event;
            if (verify(event)) {
                // process event
                std::cout << "verification passed" << std::endl;
            }
        }
};

class AvatarSystem {
    public:
        bool verify(InputEvent const & event) const {
            // test event and return whether allowed or not
            return true;
        }
};

int main() {
    AvatarSystem avatar;
    
    InputSystem input([&avatar](InputEvent const & event) {
        return avatar.verify(event);
    });
    
    input.update();
}


Demnach habe ich eine vereinfachte Abfolge der Systeme:
  1. Input-System aktualisieren. Dann leitet es (vom AvatarSystem genehmigte) InputEvents an das Physik-System weiter.
  2. Physik-System aktualisieren. Es behandelt die InputEvents: entweder bewegt es die Figur entsprechend und leitet das InputEvent an das Avatar- und Grafik-System weiter, oder es erkennt eine Kollision und leitet diese an das Avatar-System weiter.
    Zusätzlich werden die sonstigen Bewegungsinterpolationen durchgeführt.
  3. Das Avatar-System kommt dran, behandelt die eingehenden Events (z.B. führt es den Angriff aus und berechnet den Schaden) usw.
  4. Das Grafik-System bekam auch die InputEvents und weiß dadurch ob eine neue Animation abgespielt werden darf.

Was haltet ihr davon?

LG Glocke

/EDIT: Prinzipiell könnte man da auch noch den Kollisionstest mit einbauen, d.h. eine verify()-Methode vom Physik-System bereitstellen und beides im Lambda prüfen. Dann könnte man das Weiterleiten der Events sparen und das InputSystem dann direkt das verifizierte InputEvent an alle relevanten Systeme (Avatar, Physik und Grafik) propagieren lassen... das ergibt eine schöne Event-Topologie :)

/EDIT2: Grafische Darstellung angehangen.
»Glocke« hat folgendes Bild angehängt:
  • event_design.png

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Glocke« (29.01.2015, 13:28)


Werbeanzeige