Ich werde mit allen leicht anderer Meinung sein und sagen, dass der relationale Ansatz hier vernünftig ist. Interessant ist hier, dass Elemente mehrere Rollen haben können. Das Hauptproblem wird sein, dass sich die Zuordnung zwischen diesem relationalen Layout und einem OO-Layout im Code nicht "natürlich" anfühlt, aber ich denke, auf der Datenbankseite können mehrere Rollen sauber ausgedrückt werden (ohne seltsame Codierungen oder Redundanz, nur Verknüpfungen). .
Als Erstes müssen Sie entscheiden, wie viele Daten objektspezifisch sind und wie viele von allen Elementen eines bestimmten Typs gemeinsam genutzt werden.
Folgendes würde ich tun, wenn alle Daten artikelspezifisch sind:
// ITEMS table: attributes common to all items
item_id | name | owner | location | sprite_id | ...
1 | Light Saber | 14 (Tchalvek) | 381 (Tchalvek house) | 5663 | ...
// WEAPONS table: attributes for items that are weapons
item_id | damage | damage_type | durability | ...
1 | 5 | sharp | 13 | ...
// LIGHTING table: attributes for items that serve as lights
item_id | radius | brightness | duration | ...
1 | 3 meters | 50 | 8 hours | ...
In diesem Design befindet sich jedes Element in der Elementtabelle, zusammen mit Attributen, die alle (oder die meisten) Elemente haben. Jede zusätzliche Rolle, die ein Gegenstand spielen kann, ist eine separate Tabelle.
Wenn Sie es als Waffe verwenden möchten, schlagen Sie es in der Waffentabelle nach. Wenn es da ist, kann es als Waffe verwendet werden. Wenn es nicht da ist, kann es nicht als Waffe verwendet werden. Die Existenz der Aufzeichnung sagt Ihnen, ob es sich um eine Waffe handelt. Und wenn es da ist, werden alle seine waffenspezifischen Attribute dort gespeichert. Da diese Attribute direkt anstatt in einer codierten Form gespeichert werden, können Sie Abfragen / Filter mit ihnen ausführen. (Für die Metrikseite Ihres Spiels möchten Sie beispielsweise Spieler nach Waffenschadenart aggregieren, und dies können Sie mit einigen Verknüpfungen und einem Gruppentyp nach Schadenstyp tun.)
Ein Gegenstand kann mehrere Rollen haben und in mehr als einer rollenspezifischen Tabelle vorhanden sein (in diesem Beispiel sowohl Waffe als auch Beleuchtung).
Wenn es nur ein Boolescher Wert wie "Ist das haltbar?" Ist, würde ich ihn in die Tabelle "Elemente" einfügen. Es kann sich lohnen, "Ist dies eine Waffe" usw. zwischenzuspeichern, damit Sie die Waffen und andere Rollentabellen nicht nachschlagen müssen. Es fügt jedoch Redundanz hinzu, sodass Sie darauf achten müssen, dass es synchron bleibt.
Die Empfehlung von Ari, eine zusätzliche Tabelle pro Typ zu erstellen, kann auch bei diesem Ansatz verwendet werden, wenn einige Daten nicht pro Element variieren. Wenn beispielsweise der Waffenschaden nicht pro Gegenstand variiert, die Rollen jedoch immer noch pro Gegenstand variieren, können Sie gemeinsam genutzte Waffenattribute in einer Tabelle zusammenfassen:
// WEAPONS table: attributes for items that are weapons
item_id | durability | weapon_type
1 | 13 | light_saber
// WEAPONTYPES table: attributes for classes of weapons
weapon_type_id | damage | damage_type
light_saber | 5 | energy
Ein anderer Ansatz wäre, wenn die Rollen, die von Elementen gespielt werden, nicht nach Element, sondern nur nach Elementtyp variieren. In diesem Fall würden Sie den item_type in die Items-Tabelle einfügen und die Eigenschaften wie "Ist es eine Waffe?" Und "Ist es haltbar?" Und "Ist es ein Licht?" In einer ItemTypes-Tabelle speichern. In diesem Beispiel mache ich auch, dass Artikelnamen nicht pro Artikel variieren:
// ITEMS table: attributes per item
item_id | item_type | owner | location
1 | light_saber | 14 (Tchalvek) | 381 (Tchalvek house)
// ITEMTYPES table: attributes shared by all items of a type
item_type | name | sprite_id | is_holdable | is_weapon | is_light
light_saber | Light Saber | 5663 | true | true | true
// WEAPONTYPES table: attributes for item types that are also weapons
item_type | damage | damage_type
light_saber | 5 | energy
Es ist wahrscheinlich, dass sich Gegenstandstypen und Waffentypen während des Spiels nicht ändern. Sie können diese Tabellen also nur einmal in den Speicher laden und diese Attribute in einer Hash-Tabelle anstatt mit einem Datenbank-Join nachschlagen.
Leider sind in solchen Situationen relationale Datenbanken (wie SQL) nicht ausreichend und nicht relationale Datenbanken (wie MongoDB ) zeichnen sich aus. Abgesehen davon ist es nicht unmöglich, die Daten in einer relationalen Datenbank zu modellieren. Da Ihre Codebasis anscheinend bereits von SQL abhängig ist, ist hier das Modell, mit dem ich gehen würde:
Anstatt ein Element zu erstellen und eine Tabelle zu verwenden, erstellen Sie für jeden Elementtyp eine Tabelle und eine Referenztabelle "[item] _type".
Hier einige Beispiele:
Diese Lösung bietet Ihnen viel langfristige Flexibilität und Skalierbarkeit, reduziert (wenn nicht eliminiert) verschwendeten Speicherplatz und ist selbstdokumentierend (im Gegensatz zu "item_use_data"). Es erfordert etwas mehr Setup beim Entwickler, aber meiner Meinung nach ist es die eleganteste verfügbare Lösung für Situationen, in denen Sie eine relationale Datenbank zum Speichern Ihrer Daten verwenden müssen. Im Allgemeinen sind nicht relationale Datenbanken für die Spieleentwicklung viel besser geeignet, da sie flexibler Daten modellieren und gleichzeitig leistungsfähiger sind als SQL-basierte Datenbanken. Dies macht sie zu einer "Win-Win" -Wahl.
Bearbeiten 1: Ein Fehler mit dem Feld potion_type_id wurde behoben
Bearbeiten 2: Es wurden weitere Details zu nicht relationalen und relationalen Datenbanken hinzugefügt, um eine zusätzliche Perspektive bereitzustellen
quelle
Speichern Sie zunächst den objektorientierten Vererbungsansatz und entscheiden Sie sich für ein komponentenbasiertes System .
Sobald Sie dies getan haben, wird das SQL-Layout plötzlich viel einfacher. Sie haben eine Tabelle für jeden Komponententyp mit einer gemeinsamen ID-Nummer. Wenn Sie Artikel Nr. 17 möchten, suchen Sie in jeder Tabelle nach "Artikel-ID 17". Jede Tabelle mit einem Schlüssel wird mit einer Komponente versehen.
Ihre Gegenstandstabelle enthält alle erforderlichen Daten für Gegenstände im Allgemeinen (Name, Verkaufspreis, Gewicht, Größe, alles andere, was von allen Gegenständen geteilt wird). Ihre Waffentabelle enthält alle geeigneten Daten für Waffen, Ihre Trank-Tabelle enthält alle geeigneten Daten Für Tränke enthält Ihre Rüstungstabelle alle entsprechenden Daten für die Rüstung. Willst du einen Brustpanzer, das ist ein Eintrag in Item und ein Eintrag in Armor. Willst du einen Schwerthelm, den du trinken kannst, stellst du einfach einen Eintrag in jeden Tisch und bam, du bist fertig.
Denken Sie daran, dass dasselbe Muster nicht besonders objektspezifisch ist - Sie können es für Kreaturen, Zonen und alles andere verwenden, was Sie möchten. Es ist überraschend vielseitig.
quelle
Die Verwendung von SQL war Ihr schwerwiegender Fehler. Es ist absolut NICHT zum Speichern statischer Spieldesigndaten geeignet.
Wenn Sie sich nicht von SQL entfernen können, würde ich in Betracht ziehen, Elemente in einer serialisierten Datei zu speichern. Dh
Natürlich, das ist hässlich und wirft alle SQL „Nettigkeiten“ aus dem Fenster, aber Sie wirklich brauchen sie? Höchstwahrscheinlich lesen Sie alle Ihre Artikeldaten zu Beginn des Spiels trotzdem und niemals
SELECT
durch etwas anderes alsitem_id
.quelle
Abhängig davon, wie viele Merkmale Sie wahrscheinlich benötigen, können Sie eine einfache Bitmaske für Objekte verwenden, deren unterschiedliche Bits unterschiedlichen Merkmalen entsprechen:
Anschließend können Sie einfache Bittests durchführen, um festzustellen, ob ein Objekt für eine bestimmte Funktion verwendet werden kann.
"Glasflasche" kann also einen Wert von 101111 haben, was bedeutet, dass sie haltbar ist, als Waffe verwendet werden kann, leicht zerbricht, man sie werfen kann und Flüssigkeiten enthalten kann.
Jeder Editor, den Sie für Elemente erstellen, kann dann über einfache Kontrollkästchen verfügen, um Merkmale für ein Objekt zu aktivieren / deaktivieren.
quelle
In unserem Projekt haben wir item_attributes für die verschiedenen "zusätzlichen Daten", die ein Artikel haben kann. Es ist ungefähr so angelegt:
Dann haben wir eine Attributtabelle, die so aussieht:
Ari Patrick hat jedoch Recht, letztendlich sind relationale DBs nicht dafür gemacht. Der Nachteil ist, dass wir einige ziemlich komplexe Verfahren haben, um neue Elemente zu generieren (über ein externes Tool - was ich sehr empfehle. Versuchen Sie nicht, diese manuell hinzuzufügen, Sie werden sich nur verwirren).
Die andere Option, die Sie haben, ist die Verwendung einer Skriptsprache zum Erstellen der Elementvorlagen. Anschließend können Sie diese einfach analysieren und zum Erstellen neuer Elemente verwenden. Natürlich müssen Sie die Artikeldaten immer noch in der Datenbank speichern, aber an diesem Punkt müssen Sie sich nicht mehr um die Besonderheiten beim Erstellen neuer Artikel kümmern. Sie können so ziemlich nur eine alte Skriptdatei kopieren, einige Informationen ändern und Sie sind gut gehen.
Ehrlich gesagt, wenn wir die Erstellung neuer statischer Elemente wiederholen würden, würden wir wahrscheinlich einen viel einfacheren Ansatz mit Skriptelementvorlagen wählen.
quelle
Dies ist eine typische Viele-zu-Viele-Beziehung, die für eine leistungsfähige relationale Datenbank nicht allzu esoterisch ist. Sie haben viele Merkmale für ein Objekt, und jedes Merkmal kann von einem oder mehreren verschiedenen Objekttypen verwendet werden. Modellieren Sie drei Relationen (Tabellen), von denen eine eine Assoziationsrelation ist, und Sie sind fertig. Durch eine ordnungsgemäße Indizierung können Sie schnell Daten lesen.
Überlagern Sie Ihren Code mit einem ORM, und es sollten nur sehr wenige Probleme zwischen DB und Middleware auftreten. Viele ORMs können die Klassen auch selbst automatisch generieren und werden so noch "unsichtbarer".
Für die NoSQL-Datenbanken gibt es keinen Grund, warum Sie dies nicht tun können. Es ist derzeit in Mode, für diese Technologie in Handelslappen und Blogs zu jubeln, aber sie haben eine Reihe eigener Probleme: relativ unreife Plattformen, ideal für einfache, selten geänderte Lesevorgänge (wie ein bis viele Twits in einem Profil), aber Schlecht für komplexe dynamische Lese- oder Aktualisierungsvorgänge, schlechte unterstützende Toolchain, redundante Daten und die damit verbundenen Integritätsprobleme usw. Sie bieten jedoch den Vorteil einer besseren Skalierbarkeit, da sie in unterschiedlichem Maße Transaktionsfunktionen und deren Overhead vermeiden und Modelle für Verteilung / Replikation einfacher sind .
Der übliche Hobgoblin zwischen relationalen Datenbanken und NoSQL-Datenbanken ist die Leistung. Verschiedene RDMBS haben unterschiedliche Overhead-Grade, wodurch sie für die Skalierung auf die Ebenen von Facebook oder Twitter weniger bevorzugt werden. Es ist jedoch sehr unwahrscheinlich, dass Sie mit diesen Problemen konfrontiert werden. Selbst dann können einfache SSD-basierte Serversysteme die Leistungsdebatte unbrauchbar machen.
Lassen Sie uns klar sein: Die meisten NoSQL-Datenbanken sind grundsätzlich verteilte Hash-Tabellen und beschränken Sie auf die gleiche Weise wie eine Hash-Tabelle in Ihrem Code, d. H. Nicht alle Daten passen gut in dieses Modell. Das relationale Modell ist viel leistungsfähiger für die Modellierung von Beziehungen zwischen Daten. (Der verwirrende Faktor ist, dass die meisten RDBMS Legacy-Systeme sind, die schlecht auf die Anforderungen der beliebten 0,0001% des Webs abgestimmt sind, nämlich Facebook et al.)
quelle
Hier finde ich modernere OP-Mapper wirklich nützlich, wie Entity Framework 4 und seine (CTP) Code-First-Funktion . Sie schreiben einfach die Klassen und ihre Beziehungen in regulären Code und ohne sie zu dekorieren oder manuell auszuführen, generiert EF den Backing Store im SQL-Format für Sie, mit den erforderlichen Verknüpfungstabellen und allem ... es setzt wirklich Kreativität frei ^^
quelle
Folgendes überlege ich mir jetzt:
Da jedes "Merkmal" ohnehin Änderungen im Code erfordert, habe ich beschlossen, die Merkmale (und alle erforderlichen Standarddaten) (zumindest für den Moment) im Code selbst zu belassen.
Z.B
$traits = array('holdable'=>1, 'weapon'=>1, 'sword'=>array('min_dam'=>1, 'max_dam'=>500));
Anschließend erhalten Elemente ein Feld "trait_data" in der Datenbank, das die
json_encode()
Funktion verwendet, um im JSON-Format gespeichert zu werden.Es ist geplant, dass Elemente alle Standardstatistiken von ihren übergeordneten Merkmalen erben und nur Override-Merkmale aufweisen, die Unterschiede zu den als Standard festgelegten Merkmalen angeben.
Der Nachteil des Merkmalsfeldes ist, dass ich den JSON-Teil zwar von Hand bearbeiten kann, aber es mit Daten in der Datenbank nicht wirklich einfach oder sicher ist.
quelle
Das ist ein bisschen hackig, und ich kann nicht garantieren, dass es ein gutes DB-Design ist. Meine DB-Klassen sind schon eine Weile her und fallen mir nicht so schnell ein. Wenn jedoch garantiert wird, dass Elemente weniger als 10 Elemente enthalten, können Sie jedem Element ein Attribut1-Feld und ein Attribut2-Feld usw. bis zu Attribut10 zuweisen. Es würde Ihre Notwendigkeit für die Viele-zu-Viele-Elementattributtabelle auf Kosten der Begrenzung der Anzahl von Attributen beseitigen.
Wenn ich zurückblicke, bin ich mir ziemlich sicher, dass es ein schreckliches Design ist, aber es bedeutet, dass Sie bei einer komfortablen relationalen Datenbank bleiben können und nicht Neuland betreten müssen. Es liegt an Ihnen, zu entscheiden, ob sich der Kompromiss lohnt.
quelle
Nevermind gab eine gute Antwort, aber ich frage mich, brauchen Sie dafür überhaupt eine Datenbank? Das Speichern aller Daten in einer einfachen Datei und das Laden in das Programm beim Start scheint die intelligenteste Methode zu sein.
Bearbeiten:
Richtig, in einer zustandslosen anforderungsgesteuerten Umgebung sollten Sie die Daten besser in einer Datenbank aufbewahren. Schreiben Sie Ihre Daten in eine Datei und schreiben Sie einen Code, um daraus eine Datenbank des vom Typ Nevermind vorgeschlagenen Typs zu machen.
Wenn die Anzahl der Objekte nicht zu groß ist, kann es die schnellste Methode sein, das Los buchstäblich in einer Codedatei zu deklarieren.
quelle