Wie kann ich objektorientiert mit Inventar umgehen?

7

Ich versuche zu überlegen, wie ich das Spielerinventar nach einem objektorientierten Ansatz am besten handhaben kann.

Zum Beispiel sind Schwert und Axt zwei verschiedene Klassen, die beide von Waffen erben. Waffe und Trank sind ebenfalls verschiedene Klassen, die beide von Gegenständen erben.

Im Idealfall wird das Inventar als Container mit Artikeln implementiert, dies kann jedoch aus mehreren Gründen nicht durchgeführt werden. Es muss sich also um einen Container mit (intelligenten) Zeigern handeln. Da ich meinen Code nicht mit Aufrufen von new und delete lesen möchte, verpacke ich ihn in eine ItemWrapper-Klasse, die auf ctor / dtor Nachrichten / Löschungen enthält.

Der Code sieht also ungefähr so ​​aus

class Sword;
class ItemWrapper;

//Prepare the long sword.
const Sword longSword(...params...);

//Declare the inv
vector<ItemWrapper> inv;

 //Add it to inventory
inv.push_back(longSword);  //compiles if ItemWraper(const Sword&) is defined.

Dieser Ansatz war der beste, den ich in Bezug auf Codeklarheit und einfache Schreibweise finden konnte. Das Problem ist, dass ItemWrapper einen Konstruktor für jede Blattklasse benötigt, die von Item erbt (was sehr viel ist).

Dies ist mein erster Versuch, also würde ich gerne wissen: gehe ich den richtigen Weg? oder ob es einen besseren Weg gibt (vielleicht einen, der so viele Konstruktoren vermeidet)?

Malabarba
quelle
Ich bin wirklich daran interessiert, wie dies am besten umgesetzt wird.
Tili

Antworten:

9

Es gibt etwas, das Sie gerade tun, und das Sie möglicherweise später bereuen, obwohl es logisch erscheint, dies zu tun.

Es ist wahrscheinlich, dass es schwierig ist, jeden Objekttyp zu einer eigenen Klasse zu machen, wenn Sie mehr als zwölf davon haben.

Meine eigenen Bestandsverwaltungssysteme haben keine unterschiedlichen Typen für Artikel. Stattdessen verwende ich eine "Descriptor" -Klasse mit einer Reihe von Eigenschaften (Name-Wert-Paare, die in einem assoziativen Container gespeichert sind [in C ++ würden Sie wahrscheinlich std :: map verwenden]).

Der Property Getter ist eine Vorlagenfunktion, die die Eigenschaft aus der Karte in den entsprechenden Typ umwandelt.

Die Art und Weise, wie ich zwischen Elementtypen unterscheide, besteht darin, eine ItemType-Eigenschaft zu haben, normalerweise eine Art Aufzählung mit Einträgen wie WEAPON, ARMOR usw. Andere Eigenschaften enthalten Schadensbereiche, Verweise auf Bilder, die zum Rendern verwendet werden sollen, Namensbeschreibung usw.

Diese Deskriptoren gelten nicht für Elementinstanzen. Sie beschreiben den Artikeltyp als Ganzes. Elementinstanzen enthalten eine Art Verweis auf den Deskriptor sowie eine ähnliche Zuordnung von Eigenschaften für Werte, die für eine Elementinstanz benötigt werden (z. B. individuelle Haltbarkeit). Auf diese Weise wird vermieden, dass dieselben Informationen immer wieder wiederholt werden. Immerhin ist ein Schwert ungefähr das gleiche wie ein anderes Schwert, es könnte dem Verschleiß näher kommen.

Um Konstruktoraufrufe zu vermeiden, empfehle ich eine Factory-Methode.

PlayDeezGames
quelle
4
Ich denke, Sie könnten ein bisschen weiter sein als ich. Ich denke, es gibt keinen Grund, eine VorpalBladeOfPower-Klasse und eine PunyIronHandaxe-Klasse zu erstellen. Aber wenn Sie getrennte Typen für Waffen, Rüstungen usw. haben, können Sie sich Kummer ersparen. Lassen Sie den Compiler verhindern, dass ein Spieler einen Heiltrank und ein Ruder doppelt ausübt.
Dhasenan
Ich verstehe Ihren Standpunkt zur Zusammenfassung von Elementen in einer Klasse. Ich verstehe nicht wirklich, warum die Eigenschaften in einem assoziativen Container gespeichert werden. Wenn ich es richtig verstehe (und sage, ob ich es falsch verstanden habe), behalten Sie anstelle einer float weightVariablen innerhalb der ItemKlasse die Nummer in einem Container (möglicherweise verbunden mit der Bezeichnung "Gewicht"). Es scheint, als würden Sie einen winzigen Leistungstreffer (Lesen aus dem Container) erzielen und mehr Code schreiben, um ein wenig Speicherplatz zu erhalten.
Malabarba
Ich gehe natürlich davon aus, dass Texturen und Sounds bereits speichergesteuert sind (wie es immer sein sollte). In diesem Fall glaube ich, dass der Vorteil der Speicherverwaltung einiger floats, enums und ints pro Item-Instanz (selbst wenn es Hunderte von Item-Instanzen gibt) nicht sehr bedeutend wäre.
Malabarba
Es wäre ein Leistungseinbruch, wenn Sie Tausende von Eigenschaften in jedem Deskriptor hätten. Im Allgemeinen gibt es in den meisten von ihnen weniger als 20 Eigenschaften.
PlayDeezGames
Entschuldigung, der "Performance-Hit" stand nicht im Mittelpunkt, ich weiß, dass er winzig oder gar nicht vorhanden ist. Mein Punkt war, dass es so aussieht, als würden Sie mehr Code schreiben und 1 oder 2 MB Speicher sparen (unter der Annahme von Hunderten von Elementen).
Malabarba
7

Zum Beispiel sind Schwert und Axt zwei verschiedene Klassen, die beide von Waffen erben. Waffe und Trank sind ebenfalls verschiedene Klassen, die beide von Gegenständen erben.

Ich würde die Dinge nicht so machen; Ein Schwert und eine Axt unterscheiden sich hauptsächlich in ihren Daten, nicht in ihrem grundlegenden Verhalten als Waffen. Ebenso bei Gegenständen. Ich würde diese übermäßige Verwendung von Vererbung vermeiden, indem ich mich zunächst auf nur Weaponund beschränke Item(Sie könnten diese sogar zu einer einzigen Klasse zusammenfassen, aber das ist eine andere Diskussion). Wenn nichts anderes, wird dies die Konstruktorexplosion lindern, in der Sie sehen ItemWrapper.

Es muss sich also um einen Container mit (intelligenten) Zeigern handeln. Da ich meinen Code nicht mit Aufrufen von new und delete lesen möchte, verpacke ich ihn in eine ItemWrapper-Klasse, die auf ctor / dtor Nachrichten / Löschungen enthält.

Betrachten Sie die intelligenten Boost- Zeiger, anstatt Ihre eigenen zu rollen. Sie sind ziemlich isoliert vom Rest von Boost (so dass Sie nicht viele Abhängigkeiten haben, wenn Sie aus irgendeinem Grund den Rest von Boosts ordentlichem Zeug nicht ausnutzen möchten) und viele von ihnen Sie wurden als Teil der neuen Standardbibliothek in C ++ 11 übernommen, sodass Sie keine Probleme beim Upgrade haben sollten.

Dies ist mein erster Versuch, also würde ich gerne wissen: gehe ich den richtigen Weg? oder ob es einen besseren Weg gibt (vielleicht einen, der so viele Konstruktoren vermeidet)?

Wie oben würde ich mich bemühen, eine Vererbung zu vermeiden und etablierte Lösungen von Drittanbietern zu verwenden, anstatt Ihre eigenen zu rollen. Diese Schritte allein sollten einen Großteil der Wartungskomplexität beseitigen, die Sie entdecken (oder in Kürze entdecken werden).

Ein anderer Ansatz besteht darin, es so zu betrachten: Es ItemWrapperist ein Pflaster für ein Implementierungsdetailproblem. Es selbst repräsentiert kein reales "Objekt" im typischen Sinne. Überlegen Sie sich also, was eine bessere Analogie sein könnte, die auch Ihr Problem löst, einen Platz für die Automatik newund deleteAnrufe zu haben.

In den meisten Lagerbeständen können (einige) Gegenstände gestapelt werden, nicht wahr?

Die "Stapelbarkeit" eines Elements ist eine Eigenschaft dieses Elements, aber das Element selbst sollte nicht für die Wartung des Stapels verantwortlich sein. Wenn Sie eine Klasse haben, die eine ItemStackmit Methoden zum Abfragen der Größe des Stapels, zum Teilen des Stapels usw. darstellt, können Sie die Lebensdauer des Elementzeigers verwalten (was, wie ich beachten sollte, bei Stapeln etwas komplizierter werden kann Dies liefert ein weiteres Argument für die Verwendung von Boost in der Stack-Klasse zusammen mit der Implementierung des restlichen Verhaltens. Elemente, die nicht gestapelt werden können, werden durch einen Stapel dargestellt, der immer einen einzelnen Artikel enthält, und Ihr Inventar selbst behandelt immer noch homogene Instanzen von ItemStack.


quelle
+1 Kombinieren Weaponund Itemwäre der letzte Nagel im Sarg für ItemWrapper. Weaponkönnte erweitern Itemoder sie könnten beide eine gemeinsame Basisklasse erweitern. Dann könnten Sie einfach so etwas habenvector<shared_ptr<Item> > inv;
Gyan aka Gary Buyn
Die Verwendung eines ItemStack scheint eine gute Idee zu sein.
Malabarba