Inventar-System für ein Textbasiertes RPG
-
Moin Leute,
zuerst hoffe ich, dass ich im richtigen Forum dafür bin.
Ich versuche ein Textbasirtes RPG zu schreiben.
Nun bin ich an dem Punkt wo ich gerne ein Inventar implementieren würde, da komme ich jedoch nicht wirklich weiter.Also meine Idee
Ich erstelle ein Klasse "C_Item" diese hat die für das Inventar wichtigen parameter (Größe/Gewicht).
Von dieser Klasse leite ich nun weitere ab (C_Waffen, C_Rüstung etc.)
Von diesen Klassen leite ich unter umständen nochmals welche ab, z.B. für legendäre Waffen die im Kampf besondere Möglichkeiten bieten.
Diese neuen Klassen haben logischer Weise mehr Parameter und Funktionen als die Basisklasse "C_Item"Meine Fragen zu dieser Idee sind:
1. wie "Speichere" ich das am besten ins Inventar? Mit nem Vektor, ner Map/Multimap, oder nem Array und die Items mit nem Code versehen?
2. kann ich zum Beispiel eine Multimap für "C_Item" erstellen und dort die anderen Klassen mit reinspeichern?
Oder ist mein Ansatz für ein Inventar-System nicht gut?
Falls wer ein gutes Beispiel für ein solches Inventar hat, gerne hier mit Posten. Ich selber habe leider keins gefunden, welches ich nachvollziehen konnte
-
Erster Tipp: Benenne deine Typen vernünftig. Englisch und ohne "cleveren" Präfix:
Item
,Weapon
, ...Sipps schrieb:
1. wie "Speichere" ich das am besten ins Inventar? Mit nem Vektor, ner Map/Multimap, oder nem Array und die Items mit nem Code versehen?
Du musst dein eigenes Problem verstehen. Was ist ein Item? Ist das eine Beschreibung für eine Art von Gegenstand oder ist das schon die Instanz? Hat eine Item-Instanz einen Zustand oder sind alle Instanzen einer Art identisch? Kann man identische Item-Instanzen stapeln? Haben die Item-Instanzen im Inventar eine feste Reihenfolge oder kann man die als Spieler ändern?
Wenn das alles klar ist, kann sich Gedanken über geeignete Datenstrukturen machen.
-
Du gibst nicht die richtigen Informationen. Was für Voraussetzungen/Ansprüche hast du an das Inventarsystem? Soll es gut skalieren? Können Gegenstände stapelbar sein? Kann man sie anlegen, und sollen sie dann immer noch stapelbar sein? Gibt es Attribute ("geschliffen", "abgenutzt", "verzaubert"), die eigene Stapel erzeugen würden? Wie viel Speicher soll/kann es verbrauchen? Werden oft Gegenstände hinzugefügt/entfernt? Wie viele Gegenstände gibt es überhaupt in deiner Spielwelt? Gibt es NPCs, und sollen diese auch das gleiche Inventarsystem benutzen?
-
Items die ins Inventar kommen sollen als Instanz gespeichert werden. (Item = Instanz)
Gleiche Instanzen sollen starpelbar sein
Ja, es gibt zustände ("geschliffen", "abgenutzt", etc.) die einen neuen Starpel brauchen, da hier ja etwas in der Instanz geändert wird
Ob die Item-Instanzen eine feste Reihenfolge haben oder der Spieler die ändern kann ist mir eigentlich egal, ich würde nur erst mal gerne ein Inventar hinbekommen
Da ich noch relativ neu im programmieren bin mache ich mir zum Thema "Speicher" nicht ganz so viele Gedanken, ich versuche nur den benötigten Speicher so gering wie möglich zu halten (Ist glaub ich bei nem Text-RPG auch nicht das wichtigste)
Wie oft Gegenstände hinzugefügt/entfernt werden ist noch lange nicht raus
Die Anzahl der Gegenstände ist noch nach oben offen
Und ich denke die NPCs (Wenn ich den welche mache) brauchen das nicht. Im Notfall mache ich ein Neues...
Ich hoffe jetzt reicht das in etwa...
und mal unabhängig davon was ich machen will, kann mir wer ein Beispiel zeigen wie man so ein Inventar aufzieht? wie gesagt, ich finde irgendwie keine Beispiele...
-
Sipps schrieb:
Da ich noch relativ neu im programmieren bin mache ich mir zum Thema "Speicher" nicht ganz so viele Gedanken, ich versuche nur den benötigten Speicher so gering wie möglich zu halten (Ist glaub ich bei nem Text-RPG auch nicht das wichtigste)
Nee, nee, nee, das ist schon wichtig. Auch große Spieleschmieden bekommen das nicht ordentlich auf die Kette (siehe Bethesda, das Inventar von Skyrim - jetzt nicht vom Design, sondern auch, was technisch dahinter steht - ist mal absoluter Müll, skaliert überhaupt nicht gut, das macht den Eindruck, als waren da Programmierer am Werk, die sich über Skalierung einfach überhaupt keine Gedanken gemacht haben), deswegen sind solche Überlegungen selten verfrüht.
Ich frage deswegen, weil die einfachste Möglichkeit, ein Inventar zu bauen, einfach darin besteht, einen großen Speicherblock anzufordern. Und in diesen Block hat jeder Gegenstand im Spiel seine feste Position, zuzusagen ein Stapel. Ein Gegenstand wird angezeigt, sobald er im Stapel > 0 erreicht hat. Ein bisschen gewöhnungsbedürftig, verbraucht abhängig von allen Items im Spiel und von der Größe der Items Speicher, ist aber superschnell abzuspeichern, zu laden, neue Gegenstände können durch inkrement hinzugefügt werden, Gegenstände können durch dekrement entfernt werden, man muss keine Vektoren oder Listen durchgehen, die Positionen sind ja fix. Probleme könnten bei der Anzeige des Inventars aufkommen, wenn später eine Reihenfolge implementiert werden soll, dann könntest du mit einer Struktur arbeiten, in der du über fixe Indizes auf dynamische Zeiger zugreifst, in der sich die Items befinden. Das kostet dann zwar noch mal Speicher, aber skaliert wunderbar. *
Du kannst auch versuchen, ein Kategoriensystem einzubauen, dann mit Vektoren. Wenn dann irgendwann neuer Speicher angefordert werden muss, wird nicht das komplette Inventar neu gebaut, sondern nur die Kategorie.
Aber um dir eine definitive Antwort geben zu können, musst du schon wissen, was du willst. Und wenn ich das lese:
Sipps schrieb:
Wie oft Gegenstände hinzugefügt/entfernt werden ist noch lange nicht raus
, dann sehe ich, dass du noch nicht soweit bist, dir darüber Gedanken zu machen. Bekomme erst mal raus, was du WILLST, und dann kannst du dich um die Implementierung kümmern. Die Chancen stehen sogar gut, dass du's eh neumachen musst, weil mittendrin noch was hinzugefügt werden muss - morgen bist du immer schlauer als heute.
Sipps schrieb:
und mal unabhängig davon was ich machen will, kann mir wer ein Beispiel zeigen wie man so ein Inventar aufzieht? wie gesagt, ich finde irgendwie keine Beispiele...
Ich habe mich mal 'ne Weile mit der Modifizierung von Gothic 1 und 2 beschäftigt, wenn ich mich recht erinnere, hat die Engine das Inventar über eine Liste verwaltet. Anders ging es nicht. Jeder NPC hatte sein eigenes Inventar, in dem Gegenstände rein und rauskamen. In Gothic 2 bekamen dann Händler noch ein Händlerinventar, welches sich vom ersten Inventar unterschied.
Aber ehrlich, werd' dir erst mal darüber klar, was du willst.
EDIT: * hat aber den Nachteil, dass trotzdem jedes Mal, wenn das Inventar durchgegangen werden muss, geprüft werden muss, ob die Anzahl der Items > 0 ist. Und das für alle Items im Spiel. Wenn das viele sind, ist das auch wieder blöd.
-
**Für die Gegenstände
**Basisklasse "Item"
daraus werden z.B.
"Waffen"
"Rüstungen"
"Tränke"aus Waffen
"Schwert"
"Kurzschwert"
"Axt"aus Rüstungen
"Lederrüstung"
"Kettenhemd"
"Schuppenpanzer"aus Tränke
"Kleiner Heiltrank"
"Kleiner Manatrank"
"Kleiner Ausdauertrank"So das heist ich hätte jetzt 9 Verschiedene Items die jede eine Klasse für sich sind (Bei den Waffen und Rüstungen müsste das hier im Beispiel nicht sein da sie alle die gleichen Parameter und Funktionen haben, könnte bei Legendären Waffen/Rüstungen aber anders sein). Die Tränke brauchen ja je nach Wirkungsweise unterschiedliche Funktionen.
Es sollen später sehr viel Gegenstände im Spiel enthalten sein.
Das Inventar
Jedes Item hat den Parameter "Volumen", das Inventar soll mit "maxVolumen" begrenzt werden. Dies kann man vor dem Erhalten eines Items über eine Funktion prüfen.
Ich würde das Inventar gerne nach den "Themenklassen" sortieren und innerhalb dieser "Themenklassen" nach beliebigen Parametern (z.B. Bei Waffen sollen diese nach dem DMG-Wert)
Das Inventar soll über eine Menü-Struktur verwaltet werden.
z.B.
`(1) Waffen
(2) Rüstungen
(3) Tränke
Eingabe: 1
Waffen(1) 1x Kurzschwert
(2) 1x Axt
(0) Neu Sortieren`
usw.Also, wenn man nun einen Goblin besiegt bekommt man ein Kurzschwert dann möchte ich, dass ein Kurzschwert in das Spielerinventar aufgenommen wird.
Wenn der Spieler bereits ein Kurzschwert hat so soll es gestackt werden.also soll dann im Inventar stehen:
`
Waffen(1) 2x Kurzschwert
(2) 1x Axt
(0) Neu Sortieren`
NPCs werden eine anderes Inventar-System bekommen
Kernfrage?
Nach meinem Verständnis und meine Vorstellung fürs programmieren ist die Kernfrage:
Wie speichere ich am besten Instanzen verschiedener Klassen in eine Klasse die diese Instanzen verwalten kann?
Ich hoffe das ist überlegt genug
@Dachschaden Ich glaube ich hab dich ein paar mal nicht richtig verstanden, tut mir leid ich hoffe jetzt ist das bisschen besser
-
Ich habe mal die Aussagen nach ihrer Relevanz umsortiert.
Sipps schrieb:
NPCs werden eine anderes Inventar-System bekommen
OK, das ist ein Punkt FÜR das "Wir packen alles in den gleichen Speicherblock"-Konzept.
Sipps schrieb:
Es sollen später sehr viel Gegenstände im Spiel enthalten sein.
Das ist ein Punkt GEGEN das "Wir packen alles in den gleichen Speicherblock"-Konzept. Außer du meinst "Es soll viele Instanzen der Items geben" statt "Es soll viele verschiedene Items geben".
Sipps schrieb:
So das heist ich hätte jetzt 9 Verschiedene Items die jede eine Klasse für sich sind (Bei den Waffen und Rüstungen müsste das hier im Beispiel nicht sein da sie alle die gleichen Parameter und Funktionen haben, könnte bei Legendären Waffen/Rüstungen aber anders sein). Die Tränke brauchen ja je nach Wirkungsweise unterschiedliche Funktionen.
Hm. Ich gehe davon aus, dass du C++ verwendest? Dann würde ich pro Kategorie einen Vektor mit nur für diese Kategorie reservierten Items erstellen. Damit sparst du dir generische Basisklassenverwendung und kannst direkt mit den Objekten arbeiten. Und du speicherst auch nur das, was im Inventar ist, und nicht in einer großen Liste über eine Mengenangabe, was du hast.
Damit ist dann auch diese Frage:
Sipps schrieb:
Wie speichere ich am besten Instanzen verschiedener Klassen in eine Klasse die diese Instanzen verwalten kann?
beantwortet - du erstellst eine Klasse pro Kategorie, einen Vektor pro Klasse für dein Inventar, und implementierst dann die einzelnen Logiken.
-
ok, danke für die hilfe
-
eine frage habe ich doch noch, wenn ich das in Vektoren speichere, wie kann ich dann die Ausgabe sortieren?
-
Ich kenne mich nicht gut genug mit modernen C++-Features aus, um einen idealen Weg aufzuzeigen, ich mache in der Hauptsache C. Mein naiver Weg wäre, einen zweiten Vektor mit den Items der Kategorie zu bauen und die dann zu sortieren für die Ausgabe.
Alternativ kannst du auch jedes Mal, wenn ein neues Item eingefügt werden soll, die Items sortieren anhand deiner Kriterien.
-
Sipps schrieb:
eine frage habe ich doch noch, wenn ich das in Vektoren speichere, wie kann ich dann die Ausgabe sortieren?
Inkludiere den Header "algorithm" der STL und benutze einen der gegebenen Sortieralgorithmen.
http://www.cplusplus.com/reference/algorithm/Gegebenenfalls musst du dir einen eigenen Funktor schreiben um
deine Items zu vergleichen.
http://stackoverflow.com/questions/356950/c-functors-and-their-uses
http://www.cprogramming.com/tutorial/functors-function-objects-in-c++.htmlWenn du dich beim Design des Inventarsystems dafür entscheidest die Vererbungsmethode zu benutzen, sprich eine Basisklasse von der alle Items erben, dann beachte beim auslegen des Designs, dass du nicht auf das Diamantproblem stößt.
https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problemUm alle Items in einem Container speichern zu können, benötigst du eine generische Basisklasse, z.B. ItemInterface, von der alle Items erben. Dann erstellst du eine Instanz des Items und speicherst einen Zeiger vom Typ ItemInterface* in deinem Container. Das hat den Nachteil, dass wenn du dich zum Beispiel für einen std::vector entscheidest, zwar die Zeiger im Speicher kontinuierlich gespeichert sind, die Objekte selbst aber irgendwo im Speicher herumfliegen, was das Iterieren des Containers verlangsamt. ( Das sollte bei einem kleinem Spiel allerdings keinen großen Einfluss haben. ) Wie du schnell merken wirst, kannst du beim Zugreifen auf Items aus dem Vektor nur die Funktionen der Basisklasse aufrufen. Um dies zu umgehen kannst du entweder (pure) virtuelle Methoden in der Basisklasse definieren und von den erbenden Klassen überschreiben lassen oder du castest(?de) den Basisklassenzeiger zu einem Zeiger auf die richtige erbende Klasse ( Item* ).
http://www.cplusplus.com/doc/tutorial/typecasting/Bitte vergiss dabei nicht den Destruktor der Basisklasse als virtuell zu deklarieren.
( https://www.c-plusplus.net/forum/96988-full )Anschließend kann ich noch dazu raten intelligente Zeiger ( z.B. std::unique_ptr<ItemInteface> ) anstelle von Rohzeigern ( ItemInterface* ) zu verwenden. Diese löschen das Objekt automatisch sobald es aus dem Anwendungsbereich fällt. ( Verhindert Speicherlecks )
http://www.cplusplus.com/reference/memory/
https://de.wikibooks.org/wiki/C%2B%2B-Programmierung/_Speicherverwaltung/_Smart_PointerDie Deklaration des Containers würden dann zum Beispiel so aussehen:
std::vector<std::unique_ptr<ItemInterface>> items_;
Items hinzufügen kannst du dann auf die zum Beispiel auf folgende Art und Weise:
#include <iostream> #include <memory> #include <vector> class ItemInterface { public: virtual ~ItemInterface() {}; // virtueller Destruktor virtual void ichBinEinItem() = 0; // pure/abstrakte virtuelle Funktion protected: ItemInterface(std::size_t gewicht) : gewicht_{ gewicht } {}; std::size_t gewicht_; // Sobald du eine pure virtuelle Funktion deklarierst, kannst du keine Instanz der Klasse mehr erstellen, da die Funktion überschrieben werden muss. Diese Klassen heißen abstrakte Klassen. }; class Item1 : public ItemInterface { public: Item1(std::size_t gewicht) : ItemInterface(gewicht) {}; virtual void ichBinEinItem() override // "override" spezifiziert, dass diese Funktion eine andere Funktion überschreibt. Falls dies nicht der Fall ist, wird die ein Compilerfehler angezeigt ( C++11 Feature ) { std::cout << "Ich bin Item Nummer 1. Mein Gewicht ist: " << gewicht_ << " kg.\n"; // benutze "\n" anstelle von std::endl, wenn du viel Text ausgeben lässt ist dies im Optimalfall wesentlich schneller. } }; class Item2 : public ItemInterface { public: Item2(std::size_t gewicht) : ItemInterface(gewicht) {}; virtual void ichBinEinItem() override { std::cout << "Ich bin Item Nummer 2. Mein Gewicht ist: " << gewicht_ << " kg.\n"; } }; int main() { std::vector< std::unique_ptr<ItemInterface> > items; items.emplace_back(std::make_unique<Item1>(10)); items.emplace_back(std::make_unique<Item2>(20)); for (auto& item : items) { // range-based for loop ( DE? ) item->ichBinEinItem(); } return 0; }
Dieser Code generiert den folgenden Text in der Konsole:
Ich bin Item Nummer 1. Mein Gewicht ist: 10 kg.
Ich bin Item Nummer 2. Mein Gewicht ist: 20 kg.Das Ganze hier ist relativ schnell zusammengewürfelt, wenn also jemand einen Fehler findet - BITTE korrigieren ! Für die deutschen Übersetzungen gibt es übrigens keine Gewähr
EDIT : Hier ist noch ein englischer Spickzettel, den du benutzen kannst, um den richtigen Container für deine Anwendung zu finden.
http://i.stack.imgur.com/G70oT.png