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)?
Antworten:
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.
quelle
float weight
Variablen innerhalb derItem
Klasse 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.float
s,enum
s undint
s pro Item-Instanz (selbst wenn es Hunderte von Item-Instanzen gibt) nicht sehr bedeutend wäre.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
Weapon
und beschränkeItem
(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 sehenItemWrapper
.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.
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
ItemWrapper
ist 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 Automatiknew
unddelete
Anrufe 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
ItemStack
mit 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 vonItemStack
.quelle
Weapon
undItem
wäre der letzte Nagel im Sarg fürItemWrapper
.Weapon
könnte erweiternItem
oder sie könnten beide eine gemeinsame Basisklasse erweitern. Dann könnten Sie einfach so etwas habenvector<shared_ptr<Item> > inv;