Komposition in C++ für ein Game
-
Ahoi
Auch wenn es im Endeffekt für ein Spiel gedacht ist habe ich trotzdem das Gefühl, dass das hier reingehört. Falls nicht bitte verschieben.
Ich möchte mal wieder ein kleines Spiel programmieren und da gibt es Objekte, die sich bewegen können. Um starkes Binding zu verhindern möchte ich mit dem Kompositon-Modell arbeiten.
Ich hatte folgende Idee:
Ich erstelle eine Klasse Move, die verschiedene Memberfunktionen entählt, wie z.B. player_move, alien_move usw.In der abstrakten Klasse Sprite möchte ich im Anschluss für das Bewegen der Objekte eine Instanz-Variable haben, die auf eine Funktion der Klasse Move zeigt. Damit möchte ich dann die verschiedenen Move-Methoden aufrufen und diese im Verlauf des Spiels dann auch ändern können.
Ist dieser Ansatz sinvoll, bwz. ist das das korrekte Vorgehen bei Kompositions-Modell?
-
Ich versteh es nicht ganz, klingt aber eher nach Strategie.
Composite ist eher was wieclass Movable { virtual void Move() = 0; } class Alien : public Movable {...}; class Player : public Movable {...}; class Ship : public Movable { Container<Movable> Passengers_; void Move() { for_each_passenger(Move); } }; ... for_each_movable_object (Move);Naja, oder sowas ähnliches. Ob das für dein Spiel Sinn macht, kann man ohne Infos nicht sagen.
-
Ich hatte mehr so etwas gemeint:
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(...) }
-
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_moveoderMoveable::fast_moveusw. Es ist blos umständlicher eine Instanz an einen Methodenzeiger zu binden und das zu übergeben, als einfach ein Strategieobjet zu übergeben, wie bei DirIMovementoder bei mirMover
Außerdem muss man bei neuen Bewegungen seine Klasseclass 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 wiestruct 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?