Benutzerdefiniertes erweiterbares Dateiformat für 2D-gekachelte Karten

8

Ich habe gerade einen Großteil meiner Spielelogik implementiert, erstelle aber trotzdem meine Karten mit fiesen For-Loops im laufenden Betrieb, um mit etwas arbeiten zu können. Jetzt wollte ich weitermachen und nachforschen, wie diese Daten (un) serialisiert werden können. (Ich suche keinen Karteneditor - ich spreche von der Kartendatei selbst)

Im Moment suche ich nach Vorschlägen und Ressourcen, wie ich ein benutzerdefiniertes Dateiformat für meine Karten implementieren kann, das die folgenden Funktionen (basierend auf der MoSCoW-Methode) bieten soll:

  • Haben müssen
    • Erweiterbarkeit und Abwärtskompatibilität
    • Handhabung verschiedener Schichten
    • Metadaten darüber, ob eine Kachel fest ist oder durchlaufen werden kann
    • Spezielle Serialisierung von Entitäten / Triggern mit zugehörigen Eigenschaften / Metadaten
  • Könnte haben
    • Eine Art Einbeziehung des Kachelsatzes, um zu verhindern, dass Dateien / Kachelsätze verstreut sind

Ich entwickle mit C ++ (mit SDL) und ziele nur auf Windows ab. Jede nützliche Hilfe, Tipps, Vorschläge, ... wäre dankbar!


Ergebnis der folgenden Diskussion

Ich habe in den letzten Stunden mein Kartendateiformat entworfen und mir ein Skelett ausgedacht (enthält vorerst nur Ebenen - den Rest überlasse ich jedem, der sein eigenes Format entwirft), das ich allen Interessierten mitteilen wollte - wenn Sie haben die gleichen Absichten, wie Sie sich inspirieren lassen können. Der Screenshot in voller Größe kann bei Imgur heruntergeladen werden .

Kore Map-Dateiformat

Christian Ivicevic
quelle
2
XML ist zunächst ein hervorragendes Format. Es ist erweiterbar und abwärtskompatibel. Sie können beliebige Informationen und Metadaten hinzufügen. Das Lesen, Erstellen und Bearbeiten von XML wird umfassend unterstützt. Die Serialisierung von und nach XML wird für nahezu jede Sprache unterstützt. Der Nachteil ist, dass der Speicherplatz ziemlich ineffizient ist. Dies kann durch Verwendung einer Binärversion oder durch Verwendung der Zip-Komprimierung für die Datei selbst verbessert werden.
Daniel Carlsson
@ DanielCarlsson: Wie in der akzeptierten Antwort unten erwähnt, werde ich mich am Anfang an XML halten, um etwas zum Debuggen und Arbeiten zur Hand zu haben. Später werde ich zu einem benutzerdefinierten Binärformat übergehen. Trotzdem positiv bewertet, da XML in Kombination mit den RapidXML-Bibliotheken in C ++ fantastisch ist.
Christian Ivicevic

Antworten:

6

Persönlich bin ich eher ein Fan von Binärformaten mit Abschnitten (wie Windows PE, nur viel einfacher). Sie sind auch einfacher zu analysieren (aber das ist nur meine Meinung ... Ich habe genug mit XML gearbeitet, um mir Kopfschmerzen zu bereiten und zu überprüfen, ob getElementByName einen einzelnen Wert oder eine Liste von Werten zurückgegeben hat ... ugh). Wenn ich du wäre, würde ich es so machen:

".MMF\0" // magic value at the start, null-terminated string. stands for My Map Format :)
    char header_length // useful when parsing. char is a byte, of course, an unsigned one
    char version // version of the map file. (you don't really need ints here, because you probably won't be needing more than 255 versions for example, but you can also use them)
    char* map_name // null terminated string describing the name of the level/map
    char* author_name // if you are going to have a map editor for the general public, it would be nice to credit the person who made the map
    int width // it's probably wise to plan ahead and expect an int here when you're parsing the file
    int height
    ".layer\0" // we begin another subsection
        char header_length
        char type // type of the layer. for example, you can put 1 there if you want this to be a layer describing different tiles/block in a Terraria like game
        ".data\0" // yet another subsection. this will hold the data for the tiles
                  // in a hypothetical terraria 2d game, you would lay down tiles from
                  // the top-right corner (0,0) and then begin writing row after row
                  // write(1,0); write(2,0); write(3,0); ... then write(0,1); write(1,1);
                  // write(2,1); write(3,1); and so on..
            char t1 // tile at (0,0). for example, value 0 is empty, or passable tile
            char t2 // tile at (1,0). this might be a dirt block - value 1
            char t3 // tile at (2,0). a rock, perhaps? value 3
            (...)
            char tn // tile at (width-1, height-1) or the bottom-left tile
    ".layer\0" // another layer.
        char header_length    
        char type // let this on be of value 2, and let it describe portals.
                  // putting portals in a game makes it instantly 20% cooler
        ".data\0"
            char t1  // 0, no portal here at tile (0,0)
            char t2  // still nothing
            char t3  // nope, try again
            (...)
            char t47 // at some location, you made a red portal. let's put 1 here so we can read it in our engine
            (...)
            char t86 // looke here, another 1! you can exit here from location corresponding to t47
            (...)
            char t99 // value 2. hm, a green portal?
            (...)
            char tn  // bottom-left tile, at (width-1, height-1)
    ".layer\0" // another layer
        char header_length
        char type // value 3, player&enemies spawn points
        char something // you don't have to have header len fixed. you can add stuff later
                       // and because you were smart enough to put header length 
                       // older versions can know where the stuff of interest lays
                       // i.e. version one of the parser can read only the type of layer
                       // in version two, you add more meta-data  and the old parser
                       // just skips it, and goes straight to the .data section
            ".data\0"
                char t1  // zero
                char t2  // zero
                char t3  // zero
                (...)
                char t42 // a 1 - maybe the player spawn point. 5 tiles to the right
                         // there's a red portal
                (...)
                char t77 // a 2: some enemy spawn point
                (...)
                char tn  // last tile

,

Vorteile:

  • Sieht gut aus.
  • Lässt dich denken, du weißt etwas über Programmieren und machst Sachen auf die altmodische Art und Weise.
  • Sie können Ihre Ebenen manuell in einem Hex-Editor schreiben:
  • Im Allgemeinen schneller als INIs und XMLs, sowohl aus Schreib- als auch aus Lesesicht
  • Es ist wirklich ein langer Strom von Byte-Daten. Sie müssen keine Zeit damit verbringen, es hübsch und einrückungsmäßig aussehen zu lassen (wie das, was Sie mit XML tun möchten).
  • Es ist einfach, Dinge in die Header einzufügen. Wenn sich ein Datenelement am unteren Rand des Headers befindet, können alte Versionen von Parsern angewiesen werden, dies zu vermeiden und zu dem Teil der Datei zu springen, den sie verstehen.

Nachteile:

  • Sie müssen gut auf die Datenplatzierung achten.
    • Datenfelder müssen bestellt werden.
    • Sie müssen ihren Typ im Parser kennen - wie gesagt, es ist nur ein langer Strom von Bytes.
    • Das Verschieben von Daten an einen Ort (z. B. vergessen Sie, den Typ der Ebene zu schreiben; der Parser erwartet dort ein Byte und findet den Wert '.' - das ist nicht gut) bringt das gesamte Datenarray von diesem Punkt an durcheinander.
  • Es ist schwieriger, direkt darauf zuzugreifen - es gibt keine API, keine Funktion wie getLayerWidth () - all das müssen Sie selbst implementieren.
  • Es wird möglicherweise viel Platz verschwendet. Nehmen Sie zum Beispiel die dritte Schicht. Es wird sicherlich mit vielen Nullen gefüllt sein. Dies kann jedoch umgangen werden, wenn Sie eine Art Komprimierung verwenden. Aber das ist wieder einmal ein Durcheinander mit Sachen auf niedriger Ebene ...

Aber das Beste an diesem Ansatz ist meiner Meinung nach, dass Sie alles selbst machen können. Viele Versuche und Irrtümer, aber am Ende lernen Sie viel.

Vladimir Mitrovic
quelle
Sie speichern nur ... sagen wir IDs für die Kacheln, aber was ist mit ihren Metadaten? Wie soll ich speichern, ob Kacheln passierbar sind oder nicht? Was ist mit Triggern und vielleicht sogar Skripten / Code / Funktionsaufrufen, die damit verbunden sind?
Christian Ivicevic
@ChristianIvicevic: Es gibt eine Reihe von Möglichkeiten, dies zu tun:
Vladimir Mitrovic
@ChristianIvicevic: 1. Packen Sie Metadaten in ein einzelnes Byte. Sie haben nur acht mögliche Kacheln? Großartig, speichern Sie den Rest fünf Bits für etwas anderes. In Ihrem Fall haben Sie nach passablen Kacheln gefragt. Sie können diese Informationen im ersten Bit (0. Bit) speichern lassen. Ein bisschen Manipulation :) reicht aus ( codepad.org/Q6zfTV44 ). 2. Verwenden Sie dafür Ebenen. Haben Sie eine Ebene mit einem eindeutigen Typ und füllen Sie sie mit Nullen und Einsen, Einsen für passable und Nullen für unpassierbare Kacheln. 3. Verwenden Sie mehr als ein Byte pro Kachel. Ein Byte für den Wert, das andere für Metadaten.
Vladimir Mitrovic
@ChristianIvicevic: Bei Skripten haben Sie vermutlich einen Skriptparser implementiert, der funktioniert. Sie können Ihrer Kartendatei einen Abschnitt hinzufügen und dort ablegen : pastebin.com/yUKncz19 ODER Sie können sie in einer separaten Datei ablegen und einen Dateinamen im Abschnitt ".scripts" speichern. Sie stellen dann einen weiteren Abschnitt ".layer" voran, in dem beschrieben wird, welche Kacheln welches Skript auslösen: pastebin.com/BgPCR2xQ
Vladimir Mitrovic
Eine ganze Reihe von Beispielen - TOP! Jetzt werde ich mich an XML halten, um einige grundlegende Ebenen zum Debuggen / Arbeiten zu erstellen, und schließlich werde ich zu einem solchen Binärformat übergehen, das Sie für die rohe Map-Datei beschrieben haben, die die Daten enthält, und diese Datei mit den beiden Kachelsätzen (PNG usw.) und packen die Skriptdatei (en) in eine Zip-Datei, damit alles besser strukturiert ist. Es wird an dem Schreiben von Code liegen, der tatsächlich solche Binärdaten liest - aber das ist ein anderes Thema in meiner Geschichte ... danke!
Christian Ivicevic
9

Sie können das vom gekachelten Editor verwendete TMX-Kartenformat (sowie mehrere andere Karteneditoren) verwenden.

Auch wenn Sie Tiled nicht selbst verwenden, unterstützt das TMX-Format alle von Ihnen genannten Funktionen und verfügt über mehrere vorhandene Loader / Parser für eine Vielzahl von Sprachen. Es ist auch sehr einfach, das Format zu verstehen und es für Ihr eigenes Spiel zu erweitern.

Firas Assaad
quelle
Ich werde mein aktuelles XML-Konzept ein wenig auf dem TMX-Kartenformat basieren lassen und dann alle Dateien mit RapidXML lesen - später werde ich zu einem benutzerdefinierten Binärdateiformat übergehen.
Christian Ivicevic