Was ist eine gute Möglichkeit, Tilemap-Daten zu speichern?


12

Ich entwickle mit einigen Uni-Freunden einen 2D-Plattformer. Wir haben es auf dem XNA Platformer Starter Kit basiert, das TXT-Dateien zum Speichern der Kachelzuordnung verwendet. Dies ist zwar einfach, bietet uns jedoch nicht genügend Kontrolle und Flexibilität beim Level-Design. Einige Beispiele: Für mehrere Ebenen von Inhalten sind mehrere Dateien erforderlich, jedes Objekt ist im Raster fixiert, erlaubt keine Drehung von Objekten, eine begrenzte Anzahl von Zeichen usw. Daher mache ich einige Untersuchungen zum Speichern der Ebenendaten und Kartendatei.

Dies betrifft nur den Dateisystemspeicher der Kachelkarten, nicht die Datenstruktur, die das Spiel während der Ausführung verwenden soll. Die Kachelkarte wird in ein 2D-Array geladen. In dieser Frage geht es darum, von welcher Quelle das Array ausgefüllt werden soll.

Begründung für DB: Aus meiner Sicht sehe ich weniger Redundanz bei der Verwendung einer Datenbank zum Speichern der Kacheldaten. Fliesen in derselben x, y-Position mit denselben Eigenschaften können von Ebene zu Ebene wiederverwendet werden. Es scheint einfach genug zu sein, eine Methode zu schreiben, um alle in einer bestimmten Ebene verwendeten Kacheln aus der Datenbank abzurufen.

Begründung für JSON / XML: Visuell bearbeitbare Dateien, Änderungen können über SVN viel einfacher nachverfolgt werden. Aber es gibt wiederholte Inhalte.

Haben beide Nachteile (Ladezeiten, Zugriffszeiten, Speicher usw.) im Vergleich zu den anderen? Und was wird in der Industrie häufig verwendet?

Derzeit sieht die Datei so aus:

....................
....................
....................
....................
....................
....................
....................
.........GGG........
.........###........
....................
....GGG.......GGG...
....###.......###...
....................
.1................X.
####################

1 - Startpunkt des Spielers, X - Level Exit,. - Leerer Raum, # - Plattform, G - Edelstein


2
Welches existierende "Format" benutzt du? Nur "Textdateien" zu sagen bedeutet nur, dass Sie keine Binärdaten speichern. Wenn Sie sagen "nicht genug Kontrolle und Flexibilität", was genau sind die Probleme, mit denen Sie konfrontiert sind? Warum die Suche zwischen XML und SQLite? Das wird die Antwort bestimmen. blog.stackoverflow.com/2011/08/gorilla-vs-shark
Tetrad

Ich würde mich für JSON entscheiden, da es besser lesbar ist.
Den

3
Warum denken die Leute über die Verwendung von SQLite für diese Dinge nach? Es ist eine relationale Datenbank ; Warum glauben die Leute, dass eine relationale Datenbank ein gutes Dateiformat darstellt?
Nicol Bolas

1
@StephenTierney: Warum wurde diese Frage plötzlich von XML vs. SQLite zu JSON vs. irgendeiner Art von Datenbank? Meine Frage ist insbesondere, warum es nicht einfach ist: "Was ist eine gute Möglichkeit, Tilemap-Daten zu speichern?" Diese X vs. Y-Frage ist willkürlich und bedeutungslos.
Nicol Bolas

1
@ Kylotan: Aber es funktioniert nicht sehr gut. Die Bearbeitung ist unglaublich schwierig, da Sie die Datenbank nur über SQL-Befehle bearbeiten können. Es ist schwer zu lesen, da Tabellen keine effektive Möglichkeit sind, eine Tilemap zu betrachten und zu verstehen, was vor sich geht. Und während es Lookups durchführen kann, ist das Lookup-Verfahren unglaublich überkompliziert. Etwas zum Laufen zu bringen ist wichtig, aber wenn es den Prozess der tatsächlichen Entwicklung des Spiels erheblich erschwert, lohnt es sich nicht, den kurzen Weg zu gehen.
Nicol Bolas

Antworten:


13

Wenn Sie sich also Ihre aktualisierte Frage durchlesen, scheinen Sie sich am meisten um "redundante Daten" auf der Festplatte zu kümmern, mit einigen sekundären Bedenken hinsichtlich der Ladezeiten und der Nutzung durch die Branche.

Zunächst einmal, warum machen Sie sich Sorgen über redundante Daten? Selbst für ein aufgeblähtes Format wie XML wäre es ziemlich trivial, Komprimierung zu implementieren und die Größe Ihrer Ebenen zu verringern. Sie belegen mit größerer Wahrscheinlichkeit Platz mit Texturen oder Sounds als Ihre Level-Daten.

Zweitens wird ein Binärformat mehr als wahrscheinlich schneller geladen als ein textbasiertes Format, das Sie analysieren müssen. Doppelt so, wenn Sie es gerade in Gedächtnis fallen lassen und Ihre Datenstrukturen gerade dort haben können. Dies hat jedoch einige Nachteile. Zum einen sind Binärdateien nahezu unmöglich zu debuggen (insbesondere für Personen, die Inhalte erstellen). Sie können sie nicht unterscheiden oder versionieren. Sie werden jedoch kleiner und schneller geladen.

Was einige Motoren tun (und was ich für eine ideale Situation halte), sind zwei Ladepfade. Für die Entwicklung verwenden Sie ein textbasiertes Format. Es spielt keine Rolle, welches Format Sie verwenden, solange Sie eine solide Bibliothek verwenden. Für die Freigabe wechseln Sie zum Laden einer Binärversion (kleiner, schnelleres Laden, möglicherweise ohne Debug-Entitäten). Ihre Werkzeuge zum Bearbeiten der Ebenen spucken beide aus. Es ist viel mehr Arbeit als nur ein Dateiformat, aber Sie können sozusagen das Beste aus beiden Welten herausholen.

Abgesehen davon denke ich, dass Sie ein bisschen die Waffe springen.

Schritt eins zu diesen Problemen ist immer, das Problem, auf das Sie stoßen, gründlich zu beschreiben. Wenn Sie wissen, welche Daten Sie benötigen, wissen Sie, welche Daten Sie speichern müssen. Von dort ist es eine überprüfbare Optimierungsfrage.

Wenn Sie einen Anwendungsfall zum Testen haben, können Sie verschiedene Methoden zum Speichern / Laden von Daten testen, um zu messen, was Sie für wichtig halten (Ladezeit, Speichernutzung, Festplattengröße usw.). Ohne etwas davon zu wissen, ist es schwierig, genauer zu sein.


8
  • XML: Leicht von Hand zu bearbeiten, aber sobald Sie viele Daten laden, verlangsamt es sich.
  • SQLite: Dies ist möglicherweise besser, wenn Sie viele Daten auf einmal oder nur kleine Teile abrufen möchten. Wenn Sie dies jedoch nicht an anderer Stelle in Ihrem Spiel verwenden, ist es für Ihre Karten meines Erachtens übertrieben (und wahrscheinlich zu kompliziert).

Meine Empfehlung ist, ein benutzerdefiniertes Binärdateiformat zu verwenden. Bei meinem Spiel habe ich nur eine SaveMapMethode, die jedes Feld mit a durchläuft und speichert BinaryWriter. Auf diese Weise können Sie bei Bedarf auch die Komprimierung auswählen und die Dateigröße besser steuern. Dh Speichern Sie a shortanstelle von a, intwenn Sie wissen, dass es nicht größer als 32767 sein wird. In einer großen Schleife kann das Speichern von etwas shortanstelle von a inteine viel kleinere Dateigröße bedeuten.

Wenn Sie diesen Weg gehen, empfehle ich, dass Ihre erste Variable in der Datei eine Versionsnummer ist.

Betrachten Sie zum Beispiel eine Kartenklasse (sehr vereinfacht):

class Map {
    private const short MapVersion = 1;
    public string Name { get; set; }

    public void SaveMap(string filename) {
        //set up binary writer
        bw.Write(MapVersion);
        bw.Write(Name);
        //close/dispose binary writer
    }
    public void LoadMap(string filename) {
        //set up binary reader
        short mapVersion = br.ReadInt16();
        Name = br.ReadString();
        //close/dispose binary reader
    }
}

Angenommen, Sie möchten Ihrer Karte eine neue Eigenschaft hinzufügen, z. B. eine Listvon PlatformObjekten. Ich habe das gewählt, weil es ein bisschen komplizierter ist.

Zunächst erhöhen Sie die MapVersionund fügen Folgendes hinzu List:

private const short MapVersion = 2;
public string Name { get; set; }
public List<Platform> Platforms { get; set; }

Aktualisieren Sie dann die Speichermethode:

public void SaveMap(string filename) {
    //set up binary writer
    bw.Write(MapVersion);
    bw.Write(Name);
    //Save the count for loading later
    bw.Write(Platforms.Count);
    foreach(Platform plat in Platforms) {
        //For simplicities sake, I assume Platform has it's own
        // method to write itself to a file.
        plat.Write(bw);
    }
    //close/dispose binary writer
}

Dann, und hier sehen Sie wirklich den Vorteil, aktualisieren Sie die Lademethode:

public void LoadMap(string filename) {
    //set up binary reader
    short mapVersion = br.ReadInt16();
    Name = br.ReadString();
    //Create our platforms list
    Platforms = new List<Platform>();
    if (mapVersion >= 2) {
        //Version is OK, let's load the Platforms
        int mapCount = br.ReadInt32();
        for (int i = 0; i < mapCount; i++) {
            //Again, I'm going to assume platform has a static Read
            //  method that returns a Platform object
            Platforms.Add(Platform.Read(br));
        }
    } else {
        //If it's an older version, give it a default value
        Platforms.Add(new Platform());
    }
    //close/dispose binary reader
}

Wie Sie sehen, kann die LoadMapsMethode nicht nur die neueste Version der Karte laden, sondern auch ältere Versionen! Wenn ältere Karten geladen werden, haben Sie die Kontrolle über die verwendeten Standardwerte.


8

Kurzgeschichte

Eine gute Alternative zu diesem Problem ist das Speichern Ihrer Ebenen in einer Bitmap, einem Pixel pro Kachel. Mit RGBA können auf einfache Weise vier verschiedene Dimensionen (Ebenen, IDs, Drehung, Farbton usw.) auf einem einzelnen Bild gespeichert werden.

Lange Geschichte

Diese Frage erinnerte mich daran, als Notch vor ein paar Monaten seinen Ludum-Dare-Eintrag im Livestream veröffentlichte, und ich möchte mitteilen, falls Sie nicht wissen, was er getan hat. Ich fand es sehr interessant.

Grundsätzlich verwendete er Bitmaps, um seine Level zu speichern. Jedes Pixel in der Bitmap entsprach einem "Plättchen" auf der Welt (in seinem Fall nicht wirklich ein Plättchen, da es ein Raycast-Spiel war, aber nah genug). Ein Beispiel für eine seiner Ebenen:

Bildbeschreibung hier eingeben

Wenn Sie also RGBA (8 Bit pro Kanal) verwenden, können Sie jeden Kanal als unterschiedliche Ebene verwenden und bis zu 256 Kacheltypen für jeden von ihnen. Wäre das genug? Zum Beispiel könnte einer der Kanäle die Drehung für die Kachel enthalten, wie Sie es erwähnt haben. Außerdem sollte das Erstellen eines Level-Editors für die Arbeit mit diesem Format auch ziemlich trivial sein.

Und das Beste von allem ist, dass Sie nicht einmal einen benutzerdefinierten Inhaltsprozessor benötigen, da Sie ihn wie jeden anderen Texture2D-Prozessor in XNA laden und die Pixel in einer Schleife zurücklesen können.

Im IIRC verwendete er einen roten Punkt auf der Karte, um einen Feind zu signalisieren. Abhängig vom Wert des Alpha-Kanals für dieses Pixel würde dies die Art des Feindes bestimmen (z. B. könnte ein Alpha-Wert von 255 eine Fledermaus sein, während 254 eine Fledermaus wäre) ein Zombie oder etwas anderes).

Eine andere Idee wäre, Ihre Spielobjekte in solche zu unterteilen, die in einem Raster fixiert sind, und solche, die sich "fein" zwischen Kacheln bewegen können. Behalten Sie die festen Kacheln in der Bitmap und die dynamischen Objekte in einer Liste.

Es könnte sogar eine Möglichkeit geben, noch mehr Informationen in diese 4 Kanäle zu multiplexen. Wenn jemand eine Idee dazu hat, lass es mich wissen. :)


3

Sie könnten wahrscheinlich mit fast allem davonkommen. Leider sind moderne Computer so schnell, dass diese vermutlich relativ kleine Datenmenge keinen nennenswerten Zeitunterschied darstellt, selbst wenn sie in einem aufgeblähten und schlecht aufgebauten Datenformat gespeichert sind, das eine enorme Menge an zu lesender Verarbeitung erfordert.

Wenn Sie das "Richtige" tun wollen, machen Sie ein Binärformat wie Drackir vorgeschlagen, aber wenn Sie aus irgendeinem albernen Grund eine andere Wahl treffen, wird es wahrscheinlich nicht zurückkommen und Sie beißen (zumindest nicht in Bezug auf die Leistung).


1
Ja, das hängt definitiv von der Größe ab. Ich habe eine Karte, die ungefähr 161 000 Kacheln enthält. Jedes davon hat 6 Schichten. Ich hatte es ursprünglich in XML, aber es war 82 MB und dauerte ewig, um zu laden. Jetzt speichere ich es in einem Binärformat und es ist ungefähr 5 MB vor der Komprimierung und lädt in ungefähr 200 ms. :)
Richard Marskell - Drackir

2

Siehe diese Frage: 'Binäres XML' für Spieldaten? Ich würde mich auch für JSON oder YAML oder sogar XML entscheiden, aber das hat ziemlich viel Overhead. Was SQLite betrifft, würde ich das niemals zum Speichern von Ebenen verwenden. Eine relationale Datenbank ist praktisch, wenn Sie viele Daten speichern möchten (z. B. relationale Links / Fremdschlüssel) und wenn Sie eine große Anzahl unterschiedlicher Abfragen erwarten, ist das Speichern von Kacheln anscheinend nicht geeignet Dinge und würde unnötige Komplexität hinzufügen.


2

Das grundsätzliche Problem bei dieser Frage ist, dass es zwei Begriffe zusammenführt, die nichts miteinander zu tun haben:

  1. Paradigma der Dateispeicherung
  2. Speicherinterne Darstellung der Daten

Sie können Ihre Dateien als Nur - Text in einem beliebigen Format speichern, JSON, Lua - Script, XML, ein beliebige binäres Format, etc. Und nichts davon wird verlangen , dass Ihre in Speicherrepräsentation dieser Daten zu einer bestimmten Form.

Es ist die Aufgabe Ihres Level-Loading-Codes, das Dateispeicher-Paradigma in Ihre In-Memory-Darstellung zu konvertieren. Beispiel: "Ich sehe weniger Redundanz bei der Verwendung einer Datenbank zum Speichern der Kacheldaten." Wenn Sie weniger Redundanz wünschen, sollte Ihr Level-Loading-Code dies berücksichtigen. Das sollte Ihre In-Memory-Darstellung können.

Es ist nicht etwas, mit dem sich Ihr Dateiformat befassen muss. Und hier ist der Grund: Diese Dateien müssen von irgendwoher kommen.

Entweder werden Sie diese von Hand schreiben oder Sie werden eine Art Werkzeug verwenden, das Ebenendaten erstellt und bearbeitet. Wenn Sie sie von Hand schreiben, ist das wichtigste, was Sie benötigen, ein Format, das leicht zu lesen und zu ändern ist. Datenredundanz ist nicht etwas , Ihr Format auch berücksichtigen muss, weil Sie die meisten verbringen werden Ihre Zeit , um die Dateien zu bearbeiten. Möchten Sie tatsächlich die von Ihnen entwickelten Mechanismen manuell verwenden müssen, um Datenredundanz bereitzustellen? Wäre es nicht eine bessere Verwendung Ihrer Zeit, wenn Sie nur Ihren Levellader damit umgehen lassen würden?

Wenn Sie ein Tool haben, das sie erstellt, ist das tatsächliche Format (bestenfalls) aus Gründen der Lesbarkeit von Bedeutung. Sie können alles in diese Dateien einfügen. Wenn Sie redundante Daten in der Datei auslassen möchten, entwerfen Sie einfach ein Format, das dies ermöglicht, und sorgen Sie dafür, dass Ihr Tool diese Funktion ordnungsgemäß verwendet. Ihr Tilemap-Format kann RLE-Komprimierung (Run-Length Encoding), Duplizierungskomprimierung und beliebige Komprimierungstechniken enthalten, da es Ihr Tilemap-Format ist.

Problem gelöst.

Relationale Datenbanken (RDB) sind vorhanden, um das Problem der Durchführung komplexer Suchvorgänge in großen Datenmengen zu lösen, die viele Datenfelder enthalten. Die einzige Art der Suche, die Sie jemals in einer Kachelkarte ausführen, ist "Kachel an Position X, Y abrufen". Jedes Array kann damit umgehen. Die Verwendung einer relationalen Datenbank zum Speichern und Verwalten von Tilemap-Daten wäre ein extremer Overkill und eigentlich nichts wert.

Selbst wenn Sie Ihre In-Memory-Darstellung etwas komprimieren, können Sie RDBs in Bezug auf Leistung und Speicherbedarf problemlos übertreffen. Ja, Sie müssen diese Komprimierung tatsächlich implementieren. Wenn die Datengröße im Arbeitsspeicher jedoch so kritisch ist, dass Sie eine RDB in Betracht ziehen, möchten Sie wahrscheinlich tatsächlich bestimmte Arten der Komprimierung implementieren. Sie sind schneller als eine RDB und haben gleichzeitig weniger Arbeitsspeicher.


1
  • XML: sehr einfach zu verwenden, aber der Overhead wird ärgerlich, wenn die Struktur tief wird.
  • SQLite: bequemer für größere Strukturen.

Für wirklich einfache Daten würde ich wahrscheinlich einfach die XML-Route wählen, wenn Sie bereits mit dem Laden und Parsen von XML vertraut sind.

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.