Komposition in C++ für ein Game



  • Ist das von oben sinnvoll?



  • Was spricht gegen Strategy? Damit bist du flexibler, wenn z.B. plötzlich State dazukommen soll.



  • Das ist sowas ähnliches wie Strategy.

    class Mover { virtual void Move(Movable& m); };
    class FastMove : public Mover {...};
    class PlayerMove : public Mover {...};
    
    class Spirit : public Movable
    {
      Move(Mover& m)
      {
        m.Move(*this);
      }
    };
    


  • brotbernd schrieb:

    Das ist sowas ähnliches wie Strategy.

    class Mover { virtual void Move(Movable& m); };
    class FastMove : public Mover {...};
    class PlayerMove : public Mover {...};
    
    class Spirit : public Movable
    {
      Move(Mover& m)
      {
        m.Move(*this);
      }
    };
    

    Es ist aber weniger flexibel. Mit Strategy könnte man das Verhalten zur Laufzeit verändern, was insbesondere dann interessant wäre, wenn man z.B. einen Spieler hat der sowohl gehen als auch rennen kann.



  • #include <iostream>
    
    class IMovement
    {
    	public:
    		virtual ~IMovement() { }
    
    		virtual void Move() = 0;
    };
    
    class MovementPlayerWalk : public IMovement
    {
    	public:
    		virtual void Move()
    		{
    			std::cout << "Gehen." << std::endl;
    		}
    };
    
    class MovementPlayerRun : public IMovement
    {
    	public:
    		virtual void Move()
    		{
    			std::cout << "Laufen." << std::endl;
    		}
    };
    
    class Spieler
    {
    	private:
    		IMovement *myMovement;
    
    	public:
    		Spieler(IMovement *movement) : myMovement(movement) { }
    
    		void SetMovement(IMovement *movement)
    		{
    			myMovement = movement;
    		}
    
    		void Move()
    		{
    			myMovement->Move();
    		}
    };
    
    int main()
    {
    	MovementPlayerWalk walk;
    	Spieler x(&walk);
    
    	x.Move();
    
    	MovementPlayerRun run;
    	x.SetMovement(&run);
    
    	x.Move();
    
    	return 0;
    }
    

    Irgendwie so...



  • Danke vielmals für eure Antworten.

    Der Beispielcode ist sehr hilfreich. Das wäre dann Strategy oder? Ich werd mir das mal genauer ansehen.



  • .... schrieb:

    Es ist aber weniger flexibel. Mit Strategy könnte man das Verhalten zur Laufzeit verändern, was insbesondere dann interessant wäre, wenn man z.B. einen Spieler hat der sowohl gehen als auch rennen kann.

    Bei seinem Ansatz mit Funktionszeigern kann man das Verhalten auch zur Laufzeit ändern. Der Spieler bekommt einen Zeiger auf Moveable::player_move oder Moveable::fast_move usw. Es ist blos umständlicher eine Instanz an einen Methodenzeiger zu binden und das zu übergeben, als einfach ein Strategieobjet zu übergeben, wie bei Dir IMovement oder bei mir Mover
    Außerdem muss man bei neuen Bewegungen seine Klasse

    class Moveable{ player_move(...); fast_move(...); };
    

    ändern. Bei unseren Lösungen wird die Strategiehierachie erweitert (open for extension/closed for modification).



  • brotbernd schrieb:

    Das ist sowas ähnliches wie Strategy.

    ... und heisst Factory Pattern.



  • DocShoe schrieb:

    ... und heisst Factory Pattern.

    Hm, das musst Du mir jetzt aber erklären.
    Ich hatte mich auf seinen Ansatz bezogen:

    icarus2 schrieb:

    class Moveable{ player_move(...); fast_move(...); };
    class Sprite
    {
    private:
    pointer_to_Moveable_fct
    
    public:
    void move(); // In dieser Methode wird dann die entsprechende move Methode aufgerufen.
    
    set_move_fct(...)
    }
    


  • Wenn ich das alles richtig verstanden habe gibt es ein Movement Objekt, das in diesem Beispiel 2 move Methoden hat: player_move und fast_move. Ich habe die beiden Methoden move_player und move_sprite genannt.

    // Interface für Mover Klassen. Die Speicherverwaltung lasse ich aussen vor, 
    // da kann man mit Sicherheit was Hübsches mit smart pointern basteln.
    // Das Mover Objekt ist global, dass kann man sicher auch noch besser 
    // unterbringen.
    
    // Abstrakte Mover Klasse. Ein Mover Objekt enthält ein Set von 
    // Methoden zum Bewegen von verschiedenen Objekten (hier: Player und Sprite).
    struct IAbstractMover
    {
       virtual ~IAbstractMover();
    
       virtual void move_player( Player& op ) = 0;
       virtual void move_sprite( Sprite& op ) = 0;
    };
    
    // konkrete Implementaion des Move Interface: Typ 1 Mover
    struct CrawlSlowlyMover : public IAbstractMover
    {
       void move_player( Player& op );
       void move_sprite( Sprite& op );
    };
    
    // konkrete Implementaion des Move Interface: Typ 2 Mover
    struct RunReallyFastMover : public IAbstractMover
    {
       void move_player( Player& op );
       void move_sprite( Sprite& op );
    };
    
    IAbstractMover* GlobalMover = new RunReallyFastMover ();
    
    void f()
    {
      Player p;
      Sprite s;
    
       // Player bewegen
       GlobalMover->move_player( p );
    
       // Sprite bewegen
       GlobalMover->move_fast( s );
    }
    

    So, und nachdem ich das alles hier geschrieben habe glaube ich, dass es vollkommener Murks ist. Es sei denn, OP möchte das Bewegungsverhalten aller Objekte global ändern, ansonsten ist das Strategy Pattern wohl die richtig Lösung :p Obwohl... wenn ich mir Beitrag #3 ansehe könnte er fast sowas haben wollen.

    Normalerweise würde man für die Erzeugung des konkreten Mover Objektes eine Fabrik (IAbstractMoverFactory/CrawlSlowlyMoverFactory/RunReallyFastMoverFactory) benutzen, daher der Name Factory Pattern.

    Edit: Nach 30 Sekunden Nachdenken ist mir aufgefallen, dass das alles großer Käse ist. Die Factory sollte eigentlich Player und Sprites mit entsprechenden Eigenschaften erzeugen, statt das Bewegungsverhalten zu bestimmen. Das konkrete Bewegungsverhalten kann man durch das Strategiemuster umsetzen. Es gäbe also verschiedene Factories, die zum Erstellen von Player/Sprite Objekten benutzt wird und je nach Factory Typ die Player/Sprite Objekte mit unterschiedlichen Move Eigenschaften ausstattet (gern per Strategy Pattern).
    Ich lass den Blödsinn trotzdem mal stehen... hatte etwas wenig Schlaf letzte nacht.



  • DocShoe schrieb:

    Edit: Nach 30 Sekunden Nachdenken ist mir aufgefallen, dass das alles großer Käse ist.

    Ja also Dein Code ist weder Factory noch Strategy. Es ist... weiß nicht. kennich nicht 🙂
    Im entferntesten hat es Ähnlichkeit mit Visitor. Dazu müssten Player und Sprite von Basexyz erben und der Mover würde etwas wie

    struct CrawlSlowlyMover 
    {
     void Move(Basexyz& x)
     {
       x.Accept(*this);
     }
     void Visit(Player& p)
     {
       MovePlayer(p);
     }
     void Visit(Sprite& s)
     {
       MoveSprite(s);
     }
    ...
    

    Das würde aber auch nicht mehr Sinn machen 😃

    Vorrausgesetzt man kann das Bewegen aller Objekte in ein Strategieobjekt auslagern, ist das klassiche Strategiemuster wie schon gezeigt wurde ein ordentlicher Ansatz.



  • Können wir uns auf sowas einigen? 😉

    // forward declaration
    class IAbstractMoveable;
    
    struct IAbstractMover
    {
       virtual~ IAbstractMover() 
       {
       };
    
       virtual void move( IAbstractMoveAble& Object ) = 0;
    };
    
    struct SlowMover : IAbstractMover
    {
       void move( IAbstractMoveAble& Object )
       {
          // Objekt bewegt sich langsam
       }
    };
    
    struct NormalMover : IAbstractMover
    {
       void move( IAbstractMoveable& Object )
       {
          // Objekt bewegt sich normal
       }
    };
    
    struct FastMover : IAbstractMover
    {
       void move( IAbstractMoveAble& Object )
       {
          // Objekt bewegt sich schnell
       }
    };
    
    class IAbstractMoveable
    {
       // Strategy Pattern: konkreter Mover übernimmt die Bewegung des Objekts
       IAbstractMover* Mover_;
    
    public:
       IAbstractMoveable() : Mover_( 0 )
       {
       }
    
       virtual ~IAbstractMoveable() 
       {
       }
    
       void set_mover( IAbstractMover* Mover )
       {
          // neues Mover Objekt setzen
          Mover_ = Mover;
       }
    
       void move()
       {
          // Strategy Pattern: Mover Objekt ist für 
          // die tatsächliche Bewegung verantwortlich
         if( Mover_ ) Mover_->move( *this );
       }	
    };
    
    struct Player : public IAbstractMoveable
    {
    };
    
    struct Spriter : public IAbstractMoveable
    {
    };
    
    sruct IAbstractObjectFactory
    {
       virtual ~IAbstractObjectFactory()
       {
       }
    
       virtual Player* create_player() = 0;
       virtual Sprite* create_sprite() = 0;
    };  
    
    struct EasyObjectFactory : IAbstractObjectFactory
    {
       Player* create_player() 
       {
          Player* p = new Player(); 
          p->set_mover( new NormalMover() ); 
          return p;
       }
    
       Sprite* create_sprite()
       {
          Sprite* s = new Sprite();
    
          // Easy Mode: Sprites bewegen sich langsamer als der Spieler
          s->set_mover( new SlowMover() );
          return s;
       }
    };
    
    struct EasyObjectFactory : IAbstractObjectFactory
    {
       Player* create_player() 
       {
          Player* p = new Player();
          p->set_mover( new NormalMover() );
          return p;
       }
    
       Sprite* create_sprite()
       {
          Sprite* s = new Sprite();
    
          // Normal Mode: Sprites bewegen sich genauso schnell wie der Spieler
          s->set_mover( new NormalMover() );
          return s;
       }
    };
    
    struct HardObjectFactory : IAbstractObjectFactory
    {
       Player* create_player() 
       {
           Player* p = new Player();
           p->set_mover( new NormalMover() );
           return p;
       }
    
       Sprite* create_sprite()
       {
          Sprite* s = new Sprite();
    
          // Hard Mode: Sprites bewegen sich schneller als der Spieler
          s->set_mover( new FastMover() );
          return s;
       }
    };
    

    Das ist eigentlich das, worauf ich hinaus wollte. In Abhängigkeit des Schwierigkeitsgrades wird eine Objektfabrik erzeugt, die für die Erzeugung von Spielobjekten verantwortlich ist. Jedes erzeugte Spielobjekt erhält Defaulteigenschaften, die von der Objektfabrik abhängen, aber zu Laufzeit noch geändert werden können (z.B. Geschwindigkeitsbonus/-malus).

    Ich bin vorhin davon ausgegangen, dass der OP für alle bestehenden Objekte das Bewegungsverhalten global ändern wollte, statt es individuell für einzelne Objekte zu ändern. Daher das Movement Objekt, das für die Bewegung aller Objekte eines bestimmten Typs verantwortlich ist und für jeden Objekttyp eine eigene move Methode anbietet. Mit Visitor Pattern hat das nichts zu tun, ist einfach nur eine Überladung für jeden bewegbaren Objekttypen. Das ist natürlich längst nicht so flexibel, da es die Bewegung für alle Objekte eines bestimmten Typs festlegt und keine individuelle Behandlung zulässt.

    PS:
    Resourcenmanagement habe ich aus Platzgründen ignoriert



  • Den Strategieteil hatten wir ja schon auf der 1.Seite. Aber jetzt sehe ich auch eine Fabrik 🙂 (Allerdings nenn ich das Muster in dieser Form "Abstract Factory". "Factory" ist für mich anders). Ob diese Fabrik hier Sinn macht weiß ich allerdings nicht. Aber das kann ja der OP entscheiden. Auf jeden Fall noch ein weiterer guter Tipp.

    Resourcenmanagement habe ich aus Platzgründen ignorier

    Schreib doch statt

    Player* p = new Player();
    
    PlayerPtr p(new Player());
    

    und behaupte alle xPtr sind eine typedef std::shared_ptr<x>...

    Sonst kopiert das der ein oder andere Einsteiger und ignoriert auch einfach das Resourcenmanagement 😉



  • Gibt es gute Bücher wo diese verschiedenen Entwicklungsmuster beschrieben werden (Java oder C++). Ich kenne das Buch Entwicklungsmuster von Kopf bis Fuss (oder so ähnlich) und das fand ich gut (hatte es aber nur ausgeliehen und nur die ersten 2-3 Kapitel gelesen). Ist halt einfach für Java aber ich nehme an die grundlegenden Konzepte sind genau die gleichen oder?



  • Von dem Buch habe ich viel positives gehört. Ist also wahrscheinlich nicht schlecht.

    Andernfalls gibt es ein Standardwerk von der "Gang of Four" über Design Patterns:
    Design Patterns | ISBN: 9780201633610



  • Danke schon mal 🙂

    Wie wird das eigentlich an der ETH gemacht, Drako. Wird da auch ein Buch für angeschafft, in den Vorlesungen behandelt oder wie ist das da?



  • icarus2 schrieb:

    Danke schon mal 🙂

    Wie wird das eigentlich an der ETH gemacht, Drako. Wird da auch ein Buch für angeschafft, in den Vorlesungen behandelt oder wie ist das da?

    Drakon bitteschön. 😉

    Also kaufen musst du dort grundsätzlich keinen Schulstoff. Vieles wird dir als Skript angeboten (wenn du sie bereits gedruckt und gebunden haben möchtest, dann kostet dich das ca 10.- ) oder die Bücher auch als .pdf. Man kann gewisse Bücher allerdings günstiger kaufen. Du kannst also auch gut alle Bücher und Skripts am PC nachlesen (was ich nicht unbedingt überall empfehle).
    Wenn du willst kann ich dir ein paar Tipps für das erste Semester geben, was das Material anbelangt.



  • Wups, kannte mal einen, der sich Drako nannte. Alte Gewohnheit 😉

    Ja, par Tipps zum Stoff wären wirklich gut. Ich fange ja im Herbst an. In welchem Semester bist du?



  • Ich bin im Moment im zweiten Semester.

    Also gut. Ist zwar ein wenig OT, aber vielleicht auch für jemand anderen Interessant. (ich gehe mal davon aus, dass du Inf. studierst. - Das einzig wahre, gell Nexus. *duck* :))

    Eprog (Einführung in die Programmierung): Wird kein Problem für dich sein und daher musst du das Buch (Englisch) von Meyer nicht unbedingt kaufen. Ich habs gemacht und durchgelsen. Wird viel _sehr_ grundlegendes erklärt, aber hin und wieder etwas interessantes, aber für den Stoff, den du können musst reicht viel weniger (die Vorlesung + Übungen + ein wenig was selber machen).
    Mitschreiben der Vorlesung ist nicht notwendig, da es die Slides + Vorlesungsvideos/Audios gibt. (Von der trivialität des meisten, was er sagt mal abgesehen).

    LinAlg (Lineare Algebra): Da gibts ein Skript (Deutsch), dass zu empfehlen ist. Es gibt afaik nicht einmal eine Onlineversion.
    Hier lohnt sich das Mitschreiben, allerdings mit Ausnahmen. Wenn Dr. Kressner anfängt irgendwelche Algorithmen in Pseudocode an die Tafel zu schreiben oder versucht irgendwelche Verfahren hochmathematisch korrekt an die Tafel zu kritzeln, dann lehn dich zurück und hör auf das probieren abzuschreiben es bringt nichts. Die Verfahren werden in der Übung nochmal erklärt und sind nach ein paar mal sehr intuitiv zu machen (Gauss, Cholesky, Gram-Schmidt usw.), wenn man es ein paar mal gemacht hat. Das irgendwie auf Blatt mit Formel zu heften ist imo weitestgehend sinnlos. Ich kann die Algorithmen-Dinger im Skript jetzt auch nur Nachvollziehen, weil ich weiss wies geht sonst siehst du da nur i's, j's und Indexe.

    DiskMat (Diskrete Mathematik): Gutes Skript (Englisch), unbedingt nehmen!
    Mitschreiben lohnt sich auch hier. Prof. Maurer schreibt nicht so viel auf die Tafel und man kommt also sehr gut mit und kann angenehm zuhören und noch Zusatznotizen machen.

    Analysis I: Hier scheiden sich die Geister was das Skript (Deutsch) anbelangt. Wenn man es sieht, dann ist man erstmal völlig erschlagen, denn es sind praktisch nur Formel drin (obwohl es ab nächstem Semester auch ein paar Grafiken drin hat). Dazu kommt noch, dass es auch sehr viele Beweise für Sachen drin hat, die wir (Informatiker) so gar nicht direkt beherrschen müssen und werden dann in einem völlig erschlagendem Tempo durchgemacht (Picard-Lindelöf mit Vorgeplänkel innerhalb 1h). Hier gilt dann auch eher, dass man sich entspannen sollte. Die Übungen sind dann viel angewandter.
    Viele (wirklich sehr viele) sind irgendwann gar nicht mehr in die Vorlesung gekommen, sondern haben nur die Übungsstunden besucht eben weil die Vorlesung teilweise (oft?) nicht wirklich das wiederspiegelt, was du können musst. Ich bin eigl. immer gegangen, aber gelernt habe ich schon mehr ausserhalb der Vorlesung. Nicht zuletzt begeistert der Prof. mit seinem Engagement extrem und die raren, aber wirklich köstlichen (Nerd-)Jokes von ihm sind auch immer mal wieder ein Spass. 🙂
    Versuch gar nicht erst mit Prof. Struwe mithalten zu wollen und alles von der Tafel abzuschreiben. Es ist schlichtweg nicht möglich. Zum Glück ist es auch nicht nötig, denn er schreibt praktisch das Skript 1:1 an die Tafel (ja wirklich und das auswendig ⚠ ) und man hat so genug Zeit zuzuhören und sich weitere Notizen zu machen.

    Falls du andere Professoren hast, dann kann es natürlich auch anders sein!
    Uhh. Ich habe gerade nachgeschaut. Du hast 3 andere Profs, als wir hatten.. DiskMat, LinAlg und Analysis werden von einem anderen Prof. gehalten.. Das macht das ganze sehr relativ. 🙄
    Naja. Was du auf jeden Fall davon mitnehmen kannst ist, dass du nicht um biegen und brechen alles mitschreiben musst, sondern dass du das sehr individuell betrachten musst und deinen Stil ggf. nach ein paar Wochen anpassen, damit du das beste aus der Vorlesung holen kannst.



  • Vielen Dank für die Infos, war sehr interessant zu lesen.

    Was mich noch interessieren würde:

    - Programmiert man wirklich in Eiffel? ^^

    - Wie schwierig und wie schnell gehts in der Mathematik voran? Ich hatte nicht Schwerpunktfach Mathemathik. Komplexe Zahlen und Differentialgleichungen lösen sagt mir deswegen nichts. Sollte man sich schon vorher damit beschäftigen oder hat man da im Studium noch genug Zeit?


Anmelden zum Antworten