Konsolenspiel mit verschiedenen Helden
-
Hi!
Folgendes: ich wollte mich gerade an ein Konsolenspiel ranmachen, aber bin bei der Überlegung selbst auf ein kleines Problem gestoßen.
Das Spiel soll verschiedene Helden bieten die man auswählen kann; jeder Held hat die gleiche Anzahl an Fähigkeiten und verschiedene passive Fähigkeiten.
Ich hatte es mir so überlegt: ich mache eine "allgemeine" Hero-Klasse, mit protected-Membern für jene Attribute die jeder Held haben wird (HP, Basis Schaden, ...). Ebenso ein paar rein virtuelle Methoden (Abilty1, Abilty2, ... - ich will die einzelnen Fähigkeiten sehr unterschiedlich gestalten).
Dann die einzelnen Heldenklassen, welche von der Hero-Klasse erben. Ebenso kann man noch zusätzliche passive Fähigkeiten auswählen nachdem man einen Helden ausgewählt hat, um noch mehr Abwechslung rein zu bringen.Mein Problem:
In meinem Design stellt sich mir die Frage, wie ich dann im "Kampf" selbst den ausgewählten Helden anspreche. Je nachdem welchen Helden man wählt, wählt man (indirekt) ja immer eine andere Klasse. Ich kann dann doch nicht im Game-Loop selbst schreibenif(hero == Jim) Jim hero; if(hero == John) John hero; // [...]
Das sieht ja nur mehr bescheuert und dämlich aus. Wie aber dann?
-
Mal einfach und schnell zusammengeschrieben wie ichs vor hatte:
// parent class for every hero class Hero { protected: int _hp; int _baseDmg; int _armor; virtual void ability1() = 0; virtual void ability2() = 0; virtual void ability3() = 0; virtual void ability4() = 0; }; // 1st hero class Jim : protected Hero { public: // ctor Jim() { _hp = 1000; _baseDmg = 250; _armor = 220; } void ability1() { /* cool stuff! */ } void ability2() { /* cool stuff! */ } void ability3() { /* cool stuff! */ } void ability4() { /* cool stuff! */ } }; // 2nd hero class John : protected Hero { // ctor John() { _hp = 1000; _baseDmg = 250; _armor = 220; } void ability1() { /* cool stuff! */ } void ability2() { /* cool stuff! */ } void ability3() { /* cool stuff! */ } void ability4() { /* cool stuff! */ } };
-
Du hast doch schon die Basisklasse. Leite von der public und nicht protected ab, dann machst du dir irgendwo in deiner Spielklasse eine Membervariable Hero * und je nachdem welchen man ausgewählt hat, weist du ihr die entsprechende Instanz zu.
-
Vielleicht stehe ich einfach nur dumm auf der Leitung, aber genau bei
und je nachdem welchen man ausgewählt hat, weist du ihr die entsprechende Instanz zu.
weiß ich nicht weiter
-
Hero * hero = nullptr; if (selectedHero == "Jim") hero = new Jim(); else if (selectedHero == "John") hero = new Joh(); hero->foo();
-
Achso, geht das nicht anders? Weil das wollte ich ja umgehen.
Wenn ich jetzt 50 Helden habe, sieht das ja nur noch unschön aus.
Oder ist es ein allgemeiner Designfehler?
-
Was meinst du genau, bzw. was ist dann eigentlich dein Problem? Ich hab nur deinen ursprünglichen etwas umgebaut, damit es funktioniert.
-
Wenn ich nun nicht 2 Helden habe, sondern z.B. 100, kann ich dann 100 mal in Folge
if(selectedHero == "x") hero = new x
schreiben?
-
Ok, das war aus deiner ursprünglichen Frage nicht rauszulesen. Du brauchst so ein if-else Konstrukt natürlich nicht. Wo kommt der ausgewählte Held überhaupt her? Du könntest z.B. eine map Name->Instanz aufbauen, z.B. so:
std::map<std::string, Hero*> heroes; heroes["Jim"] = new Jim(); heroes["John"] = new John();
Oder du machst eine Liste und nimmst das Element an der Position, die im Menü gewählt wurde. Dieselbe Liste verwendest du dann natürlich auch, um das Menü erst aufzubauen.
-
Ja genau, ich will so ein riesiges if-else-Konstrukt vermeiden.
Für die Helden gebe ich einfach eine Liste auf der Konsole aus:
1. John: Ist toll
2. Jim: Ist auch toll
3: Peter: Ist ganz tollUnd je nachdem welche Nummer man wählt, der Held wird dann auch gewählt.
Lade ich bei dieser map-Varianten nicht immer alle Helden gleichzeitig, obwohl ich nur einen brauche? Zwar kann ich dann einen spezifisch ansprechen, aber in der Map existieren doch initialisierte Objekte zu ALLEN Helden?
Was mir aber gerade beim Rumspielen mit den Maps aufgefallen ist:
Laut http://www.cplusplus.com/reference/map/map/operator[]/ kann ich mir einen bestimmten Eintrag aus der Map wie folgt holen:std::map<std::string, int> m; m["John"] = 1; m["Jim"] = 2; // --- std::cout << m["John"];
Sollte, zumindest laut der Cpp-Referenz funktionieren. Mein Visual Studio aber findet das weniger knorke, weil es meint, dass der << Operator für sowas ungeeignet ist. Ich krieg den entsprechenden Wert wenn ich über die Methode data() rangehe (also m["John"].data()) - aber über diese Methode finde ich nichts, daher weiß ich nicht, ob es die richtige Wahl ist.
-
Wenn du nicht für alle Helder initialisierte Instanzen vorhalten willst, obwohl du nur einen brauchst (ist auch sinnvoll so), brauchst du irgendeine Art Factory. Das einfachste wär hier wieder so ein switch-case Konstrukt. Eine andere Möglichkeit wäre hier noch Factory Klassen zu schreiben, die deine Helden erzeugen. Könnte man z.B. auch mit einer Map und Type Erasure lösen. Da könntest du dir vielleicht boost::factory anschauen.
Dein Compiler gibt eindeutige Fehlermeldungen aus. Wenn er was nicht mag, dann machst du was falsch. Das sollte schon gehen, was du geschrieben hast, schau dir die genaue Fehlermeldung an.
-
Danke für den Tipp, werd ich mir ansehen.
Wegen der Fehlermeldung; der Quellcode (sollte, meiner Meinung nach, eigentlich passen):
#include <iostream> #include <map> int main() { std::map<int, std::string> m; m[1] = "John"; m[2] = "Foo"; std::cout << m[2]; }
Aber die Fehlermeldung:
error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'std::basic_string<_Elem,_Traits,_Ax>' (or there is no acceptable conversion)
-
#include <string>
-
Oh. Vielen Dank.
-
Nur mal so generell: Wie unterscheiden sich die Helden denn? Ich tippe mal auf: Anderes Model, andere Fertigkeiten.. ? Das sind aber nur andere Belegungen für die selben Attribute. Hier bietet sich Vererbung eigentlich nicht an.
-
Richtig.
Ich vermute mal, die schönste Variante wäre die ganzen Heldenattribute in einer Datenbank / Datei zu speichern und so auszulesen? XML würde sich hierfür ja eignen:
<?xml version="1.0"?> <heroes> <hero> <name>Jim</name> <hp>1000</hp> <basedmg>250</basedmg> <armor>500</armor> <ability1>9128</ability1> <ability2>1928</ability1> </hero> </heroes>
Wobei ich die verschiedenen Abilities einfach über eine ID speichere. Diese finden sich dann in einer Map, welche aus den IDs und Pointern zu den entsprechenden Funktionen besteht, oder?
-
Du könntest die Attribute schon als Member deiner Heldenklasse halten. (Du würdest bei der Instanziierung dann die Werte aus deiner XML da reinkonstruieren.) Vielleicht ändern die sich ja wenn man irgendwelche Gegenstände einsammelt.
-
Nuki schrieb:
Wobei ich die verschiedenen Abilities einfach über eine ID speichere. Diese finden sich dann in einer Map, welche aus den IDs und Pointern zu den entsprechenden Funktionen besteht, oder?
Nein, find ich auf jeden Fall nicht gut. Schon gar nicht irgendwelche Zahlen als IDs. Ich hab mich auch schon gefragt, warum dir für deine Helden nicht eine Klasse reicht, die unterschiedlich intialisiert wird. Aber da du geschrieben hast, du willst das Verhalten teilweise gravierend abändern hab ich mir gedacht, wird schon seinen Sinn haben.
Ist das Verhalten wirklich so unterschiedlich, oder läuft es auf denselben Algorithmus hinaus, den man durch verschiedene Parameter beeinflussen kann?
Wenn du wirklich verschiedene Algorithmen bauen willst, würd ich vielleicht sowas wie Strategies einsetzen, und den Helden dann aus Strategie-Komponenten aufbauen. Die kannst du dann über einen Namen oder so in der XML angeben und dann wieder über eine Art Factory registrieren.
-
In dem Zusammenhang wäre vielleicht das Stichwort Dependency-Injection angebracht.
-
Mechanics schrieb:
std::map<std::string, Hero*> heroes; heroes["Jim"] = new Jim(); heroes["John"] = new John();
Hundert Instanzen erstellen, wobei man sicher ist, dass man eh nur eine braucht? Eher nicht
Ich sehe aktuell und auch sonst keine Notwendigkeit, das über Ableitung zu machen. Das wäre sogar hinderlich. Mach es wie du schon selbst gemacht hast über eine separate Konfiguration, womit dein Held dann "gefüttert" wird (muss nicht zwingend XML sein). Gerade wenn du grad am testen/balancen bist und ein paar Fähigkeiten des Helden austauschen willst, seine Werte verändern willst, oder auch schnell mal einen neuen Helden basteln willst, willst du nicht ständig dein Programm neu kompilieren müssen. Einfach Datei umschreiben oder neu anlegen, im Programm den Helden laden, fertig.
Da die Fähigkeiten eh wieder Objekte sein sollten, kannst du sie dann trotzdem individuell gestalten, ohne an deinem Helden dafür was ändern zu müssen
-edit-
das würde es an sich sogar vereinfachen, später einen Spielstand zu erstellen. Einfach eine solche Konfigurationsdatei im Savegame ablegen und beim späteren Laden deinen Helden damit wieder initialisieren