Wie kann ich diese Implementierung der feindlichen Datenbank verbessern?


7

Ich entwickle ein Rollenspiel und bin an dem Punkt angelangt, an dem ich mit dem Aufbau einer feindlichen Datenbank beginnen muss. Damit sind einige Herausforderungen verbunden und einige Lösungen, über die ich nachgedacht habe.

Folgendes muss ich in meiner feindlichen Datenbank tun:

Ich habe zwei primäre Feindklassen, über die ich Daten darstellen muss:

Eine Basis-Feindklasse, die Folgendes umfasst:

Base Stats
Status Resistance Table
Elemental Resistance Table
Steal Table
Drop Table
Level
Unique ID
Base XP
AI Hook
Name
Display Name

Und eine abgeleitete Klasse, die die Möglichkeit zum Hinzufügen von Ausrüstung hinzufügt und die folgenden Felder hinzufügt:

Main Weapon
Secondary Weapon/Equipment
Armor
Accessories

Ich kann in Zukunft weitere Felder oder zusätzliche Klassen hinzufügen, wenn dies sinnvoll ist. Ich habe zwei mögliche Formate für Datenbank-Feinde in Betracht gezogen.

XML-Dateien

Ich würde es im Grunde so machen:

<?xml version="1.0" encoding="utf-8"?>
<Enemies>
  <Enemy name="Red Dragon" type="BaseEnemy" level="56" displayname="Red Dragon">
    <Stats HP="55000" MP="2500" SP="2500" Strength="212" Vitality="125" Magic="200" Spirit="162" Skill="111" Speed="109" Evasion="100" MgEvasion="100" Accuracy="100" Luck="55"/>
    <StatusResistances>
      <Resistance name="Sleep" value="100" />
      <Resistance name="Stop" value="100" />
    </StatusResistances>
    <ElementResistances>
      <Resistance name="Fire" value="75" />
    </ElementResistances>
    <LootTable>
      <Item name="Elixir" rate="0.03" count="1"/>
    </LootTable>
    <DropTable>
      <Item name="Elixir" rate="0.03" count="1"/>
    </DropTable>
    <AIScript value="BasicBehaviour.py" />
    <BaseXP value="4800"/>
  </Enemy>
  <Enemy name="Gaverick 1" type="HumanoidEnemy" level="33" displayname="Gaverick">
  <!--Same Stuff as above here-->
    <Equipment>
      <Weapon name="Dark Eclipse"/>
      <Armor name="Terra Defense"/>
      <Accessory name="Ribbon"/>
    </Equipment>
  </Enemy>
</Enemies>

Vorteile:

  • Einfach zu erweitern, wenn ich Parameter hinzufügen / neu anordnen muss
  • einfach Standardwerte zuzuweisen
  • Ich habe bereits einen XML-Parser (pugixml) für Konfigurationsdateien, gekachelte Karten und das Laden von Ressourcenbeschreibungen

Nachteile:

  • möglicherweise langsam (meine Datenbank wird wahrscheinlich mehrere hundert Feinde treffen)
  • kann nicht nach beliebigen Feinden abfragen, muss also wahrscheinlich alle Feinde im Speicher behalten.
  • Dies würde bedeuten, dass ich das Spiel neu starten muss, um auch geänderte feindliche Daten zu laden

SQLite

Dazu würde ich im Grunde eine Tabelle mit Spalten erstellen, die alle benötigten Daten darstellen, und die unnötigen Felder leer lassen

Vorteile

  • Durch willkürliches Abfragen können unnötige feindliche Daten nicht mehr gespeichert werden
  • Fühlt sich strukturierter an
  • Kleinere Dateigröße

Nachteile

  • Schwieriger, Parameterreihenfolgen zu erweitern / neu anzuordnen
  • Unnötiger Aufwand für nicht verwendete Felder
  • Muss einen Datenbankschnittstellen-Wrapper für SQLite schreiben

Vor diesem Hintergrund war ich neugierig darauf, Erfahrungen von außen darüber zu sammeln, was andere Leute getan haben. Ich denke vielleicht völlig falsch darüber nach, und wenn ja, schlagen Sie bitte eine Alternative zu den beiden Möglichkeiten vor, die ich hier habe.

Darüber hinaus sind Vorschläge zur Verbesserung einer dieser Möglichkeiten willkommen. Wirklich, ich möchte nur wissen, ob ich auf dem richtigen Weg bin.

Ich bin offen für die Nutzung jeder kostenlosen Bibliothek und baue bereits Boost ein

Antworten:


12

Ich sehe keine Notwendigkeit für die Komplexität einer vollständig relationalen Datenbank. Es gibt relationale Datenbanken, die komplexe Suchvorgänge vereinfachen und die Suche über große Datenmengen hinweg ermöglichen. Wenn Sie im Spiel nur jedes Mal, wenn ein Feind erscheint, eine davon indizieren, verwenden Sie dazu ein unglaublich überkompliziertes Tool.

Kurz gesagt, SQLite ist übertrieben.

An diesem Punkt ist die Frage nicht SQLite vs. XML. So speichern Sie es auf der Festplatte und möchten die Daten im Spiel darstellen. Wenn die einzige Art von Abfragen, die Sie im Spiel ausführen, darin besteht, nach Namen zu suchen, ist eine einfache Abfrage std::mapausreichend. In der Tat können Sie sie in a stecken std::vectorund nach dem Laden sortieren, um schnell suchen zu können.

Bei der Darstellung auf der Festplatte sollten Sie sich am meisten mit dem Format befassen, das das Bearbeiten und nicht das Lesen erleichtert. Lesen ist etwas, das Sie im Code leicht genug tun können. Bearbeitung muss man einmal pro Feind machen. Wenn Sie neue Feinde entwerfen, müssen Sie deren Daten häufig bearbeiten.

Für mich besteht die Dichotomie zwischen XML und Lua , was als Datenbeschreibungssprache perfekt geeignet ist. Beispielsweise könnte die XML-Beispieldatei in Lua wie folgt dargestellt werden:

return
{
    {
        name = "Red Dragon", type="BaseEnemy", level=56, displayname="Red Dragon",
        stats = {
            HP=55000, MP=2500, SP=2500, Strength=212, Vitality=125,
            Magic=200, Spirit=162, Skill=111, Speed=109,
            Evasion=100, MgEvasion=100, Accuracy=100, Luck=55
        },
        status_resistances = {
            {name="Sleep", value=100},
            {name="Stop", value=100},
        },
        element_resistances = {
            {name="Fire", value=75},
        },
        loot_table = {
            {name="Elixir", rate=0.03, count=1},
        },
        drop_table = {
            {name="Elixir", rate=0.03, count=1},
        },
        script = "BasicBehaviour.py",
        xp = 4800,
    },
    {
        name="Gaveric 1", type="HumanoidEnemy", level=33, displayname="Gaveric",
        --Same Stuff as above here
        equipment = {
            weapon = "Dark Eclipse",
            armor = "Terra Defense",
            accessory = "Ribbon",
        }
    }
}

Der größte Vorteil eines Lua-Skript-basierten Ansatzes ist, dass ... es Lua ist. Während es reine Daten sein kann, ist es nicht haben zu sein. Wenn Sie also einige Wiederholungen von Statistikblöcken haben, können Sie diese Daten problemlos vom Lua-Skript für Sie generieren lassen. All dies ist ein Lua-Skript, das eine Tabelle zurückgibt. Die Tabelle ist das, was Sie in Ihre speicherinternen Datenstrukturen einlesen.

Der Nachteil dieses Ansatzes besteht hauptsächlich darin, dass Sie zum Schreiben dieser Dateien kein Tool verwenden. Wenn Sie ein Tool haben, mit dem Sie diese feindlichen Dateien bearbeiten können, ist der Lua-Ansatz in Ordnung. Wenn Sie sie jedoch von Hand bearbeiten, bedeutet dies, dass Ihr Ladecode, der die Lua-Tabelle durchläuft, die Eingabe überprüfen muss. Es muss überprüft werden, ob die erforderlichen Felder vorhanden sind, ob jeder Zahlenwert eine gültige Zahl ist usw.

Und obwohl es nicht gerade schwierig ist, Code zu schreiben, ist er äußerst langweilig. Der Vorteil von XML besteht darin, dass Sie es mit einem RelaxNG- oder WXS-Schema validieren können. Es gibt sogar XML-Editoren mit schemagesteuerter Bearbeitung, sodass es unmöglich wird, eine ungültige XML-Datei zu schreiben .

Wenn Sie ein neues Feld hinzufügen müssen, passen Sie einfach Ihr Schema an, und schon geht es Ihnen gut. Wenn Sie die Dateistruktur erneut ändern, passen Sie Ihr Schema an und validieren Sie die Dateien erneut, um Fehler dort zu korrigieren, wo sie auftreten. Sie können sogar ein XSLT-Tool schreiben, um Dateien automatisch von einer Formatversion in eine andere zu konvertieren.

Natürlich müssen Sie wissen, wie man Schemas schreibt, und Sie müssen ein schemagesteuertes XML-Format haben. Andernfalls wird das Lua-Skript nicht schlechter gestellt, da Sie die Daten in beiden Fällen validieren müssen. Und das Lua-Skript ist wohl einfacher zu analysieren, da Sie die Daten direkt abfragen können. Bei einer Lua-Tabelle, die eine feindliche Definition enthält, können Sie bestimmte "Elemente" nach Namen abfragen. Bei einem XML-Parser müssen Sie die Elemente so verarbeiten, wie sie zu Ihnen kommen. Dies ist im Allgemeinen etwas komplexer zu schreiben.

kann nicht nach beliebigen Feinden abfragen, muss also wahrscheinlich alle Feinde im Speicher behalten.

... damit? Die Datenstrukturen, die aus Ihrem Beispiel-XML entnommen wurden, würden verbraucht (ohne Komprimierungsversuche:

  • 14 Statistiken * 4 Bytes pro = 56 Bytes.
  • Interner Name: 32-Byte-Zeichenfolge.
  • Anzeigename: 64-Byte-Zeichenfolge.
  • Typ: Teil der Datenstruktur; muss nicht gespeichert werden.

Jede Waffe kann auch eine 32-Byte-Zeichenfolge sein. Also eine Gesamtsumme im schlimmsten Fall von ... 248 Bytes. Sie können mehr als 3500 davon in einem halben MB RAM speichern. Ich würde mir darüber keine Sorgen machen.

Dies würde bedeuten, dass ich das Spiel neu starten muss, um auch geänderte feindliche Daten zu laden

Warum? Das liegt an dir. Es gibt nichts, was besagt, dass Sie die feindlichen Daten nicht mitten im Spiel ablegen und neu laden können. Wenn Sie das nicht können, liegt das nur daran, dass Sie Ihren Code nicht richtig strukturiert haben, um dies zu ermöglichen.


+1 für Lua. Vergessen Sie auch nicht YAML.
michael.bartnett

6

Ich habe zwei primäre Feindklassen, über die ich Daten darstellen muss. Eine Basis-Feindklasse ... und eine abgeleitete Klasse ...

Ich bin nicht davon überzeugt, dass Sie dafür zwei Klassen benötigen. ein einziger sollte ausreichen. Sie können dies genauso gut mit Komposition und nicht mit Vererbung modellieren . Moderne Software-Design-Techniken tendieren weg von tiefen Klassenhierarchien, weil sie dazu neigen, spröde und schwer zu pflegen zu sein. Ihre vorgeschlagene Hierarchie ist zwar nicht sehr tief, aber unnötig (was ein erster Schritt in Richtung "tief" ist). Ihre abgeleitete Klasse fügt nur Eigenschaften hinzu, die genauso gut in der Basisklasse verbleiben können, aber leer bleiben.

Auf diese Weise können Sie nicht nur das Verhalten und die Eigenschaften Ihrer einzelnen Feinde später besser iterieren und optimieren, sondern Ihren Code auch einfacher gestalten, da Sie nur mit einer einzigen öffentlichen API und deren Verarbeitung arbeiten.

Ich habe zwei mögliche Formate für Datenbank-Feinde in Betracht gezogen.

Von diesen beiden würde ich XML wählen. Im Allgemeinen sollten Sie sich JSON ansehen . Ich denke nicht, dass die Verwendung einer tatsächlichen relationalen Datenbank hier gerechtfertigt ist. In dieser Frage finden Sie eine Diskussion darüber, warum: Die Kurzversion ist komplex und weniger direkt zugänglich oder bearbeitbar. Da Sie keinen der Vorteile benötigen, die eine relationale Datenbank bietet, sollten Sie sich besser an etwas Ähnliches halten.

Sowohl XML als auch JSON haben den großen Vorteil, dass sie trivial von Menschen gelesen und bearbeitet werden können, ohne dass spezielle Tools erforderlich sind, die Ihre Iterationszeiten erheblich verlängern können. Mit XML können Sie XSLT verwenden , um Ihre Daten in großen Mengen zu transformieren, falls Sie das Schema jemals drastisch ändern oder eine andere Art der Migration in großem Maßstab durchführen müssen.

Speziell für Ihre drei "Vorteile" für SQLite:

  • Sie können die Daten genauso leicht aus dem Speicher halten, wenn sie sich in einer Datei auf einer Festplatte befinden (möglicherweise abhängig von Ihrem XML-Reader), aber Sie möchten dies wahrscheinlich nicht tun, da die Festplatte "langsam" ist und Sie dies wahrscheinlich nicht tun. Es sind nicht genügend Daten vorhanden, um wirklich viel Speicherdruck zu verursachen.
  • Es ist nur so strukturiert, wie Sie es erstellen, und das Entwerfen wirklich guter, normalisierter Datenbankschemata ist nicht immer eine triviale Aufgabe. Sie erhalten im Allgemeinen nur dann die maximalen Gewinne aus einer Datenbank, wenn Sie über ein ordnungsgemäßes Schemadesign verfügen. Es ist auch viel schwieriger, das Schema zu ändern.
  • Sie können jederzeit zu einem binären Dateiformat wechseln, um Ihr Spiel zu versenden, das den inhärenten Aufwand eines ausführlicheren, textbasierten Formats wie XML oder JSON verringert oder beseitigt. Das ist ganz einfach.

3

Hier ist eine andere Möglichkeit, es zu betrachten, eine Datenstruktur zu erstellen, sie mit Ihren Monsterwerten zu füllen, dann die Datenstruktur in einer Binärdatei zu speichern und diese in eine Pak-Datei zu legen, wie:

ork_footsoldier.npcdat
ork_chief.npcdat
guard.npcdat
 Into..
NPCData.pak

Laden Sie dann das Paket in den Speicher, analysieren Sie die npcdat-Dateien und analysieren Sie auf Anfrage die darin enthaltenen Daten. Es ist schneller als XML und SQLLite und nimmt viel weniger Platz ein.


@Skeith Ich stimme zu. Eine getrennte Nur-Text-Datei erledigt den Job, den der Benutzer127817 beschreibt, ohne allzu große Probleme.
Christopher

@Christopher: Außer dass du einen Parser dafür schreiben müsstest. Und etwas zum Laden der Packdateien. Und viele Dinge, die Sie nicht nur mit dem Laden einer XML-Datei tun müssen. "Deliminierter Klartext" ist auch schwieriger zu bearbeiten, da es für sie keine geführten Bearbeitungswerkzeuge gibt. Sie können das Format leicht falsch verstehen und werden es erst wissen, wenn Sie versuchen, es zu laden.
Nicol Bolas

Das Schreiben eines Parsers für Binärdaten ist viel schneller und einfacher als ein Textparser. Außerdem ist es in einem Produktionsspiel viel schwieriger, die Werte zu ändern, um Cheats zu speichern.
Matt Jensen

0

Der einzige Vorteil von SQLite, den ich mir vorstellen kann, ist die Reduzierung von Duplikaten, dh wenn Sie in den Vorteilsbereich "Fühlt sich strukturierter an" fallen.

Sie können XML jedoch mit XPath abfragen, wenn Sie dies wünschen. Aber ich denke, für Hunderte (und sogar Tausende) ist das Speichern aller Daten im Speicher nicht kritisch. Bei dieser Datenmenge ist die Größe der Datenbank- oder XML-Datei nicht kritisch.

Ich stimme für XML.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.