Eine Möglichkeit, potenziell unendliche 2D-Kartendaten zu speichern?


29

Ich habe einen 2D-Plattformer, der derzeit Chunks mit 100 mal 100 Kacheln verarbeiten kann, wobei die Chunk-Koordinaten als Longs gespeichert werden, so dass dies die einzige Begrenzung für Maps ist (maxlong * maxlong). Alle Entity-Positionen usw. usw. sind blockrelevant und daher gibt es dort keine Begrenzung.

Das Problem, das ich habe, ist, wie ich diese Chunks speichern und darauf zugreifen kann, ohne Tausende von Dateien zu haben. Haben Sie Ideen für ein möglichst schnelles und kostengünstiges HD-Archivformat, bei dem nicht alles auf einmal geöffnet werden muss?


2
Einige Datenstrukturen, die Sie für mehr Inspiration untersuchen könnten, sind dünne Matrizen und (mehrstufige) Seitentabellen .
Andrew Russell

Niedrige Priorität: Können Sie klären, ob der Datentyp "long" 32- oder 64-Bit ist?
Randolf Richardson

2
@Randolf vorausgesetzt, dass dies C # ist, bedeutet er vermutlich das C #, longdas 64-Bit ist (also maxlong ist Int64.MaxValue).
Andrew Russell

Notch hat einige interessante Dinge zu den unendlichen Karten in Minecraft in seinem Blog hier zu sagen: notch.tumblr.com/post/3746989361/terrain-generation-part-1
dlras2

Antworten:


17

Erstellen Sie ein benutzerdefiniertes Kartenformat für Ihr Spiel. Es ist einfacher als Sie vielleicht denken. Verwenden Sie einfach die BinaryWriter-Klasse. Schreiben Sie zuerst die Überschrift in ein paar Zentimetern. Informationen, die in den Header aufgenommen werden sollen:

  • Die magische Zeichenfolge / magische Nummer Ihres Dateiformats.
  • Der Anfang / das Ende / die Größe der in dieser Datei beschriebenen Blöcke

und auch (und hier kommt der leistungskritische Teil

  • Ints, die die Startposition innerhalb der Datei beschreiben. Sie müssen also nicht nach bestimmten Stücken suchen.

Mit der obigen Methode können (und sollten) Sie einen Index Ihres Dateiinhalts erstellen, der eine Art Beschreibung (einen benutzerdefinierten Namen für die Region / den Block oder nur die Koordinaten) und als zweiten Wert die Position in der Datei enthält.

Wenn Sie dann einen bestimmten Block laden möchten, müssen Sie nur im Index suchen. Wenn Sie die Position erhalten haben, setzen Sie einfach fileStream.Position = PositionOfChunkFromIndex und Sie können sie laden.

Es geht nur um die Gestaltung des Dateiformats, wobei der Header den Inhalt der Datei am effizientesten beschreibt.

Speichern Sie einfach die Dateien mit einer benutzerdefinierten Erweiterung, die Sie erstellt haben, und los geht's.

BONUS: Fügen Sie BZip2-Komprimierung zu bestimmten Bereichen der Datei / zum gesamten Inhalt (nicht zum Header !!) hinzu, damit Sie bestimmte Teile aus der Datei entpacken können, um einen sehr geringen Speicherbedarf zu erzielen.


12
Es ist erwähnenswert, dass Sie, wenn Sie diese Datei im laufenden Betrieb ändern, entweder eine feste Größe oder einen externen Header / Index benötigen, sodass Sie der Datei Blöcke hinzufügen können, ohne die Datei neu schreiben zu müssen gesamte Datei (aufgrund der Änderung der Offsets).
Andrew Russell

Implementieren Sie zu diesem Zeitpunkt nicht einfach eine Flatfile-Datenbank?
Ape-inago

13

Ich hatte ein ähnliches Problem und beschloss, eine eigene Struktur für den Umgang mit den Daten zu erstellen. Es basiert lose auf einem Quadtree, ist aber unendlich (mindestens so groß wie ein Int) erweiterbar in alle Richtungen. Es wurde entwickelt, um gitterbasierte Daten zu verarbeiten, die von einem zentralen Punkt aus erweitert wurden, so wie es Minecraft jetzt tut. Es ist platzsparend im Speicher und sehr schnell.

Sie können für jeden Knoten eine minimale Größe angeben (eine Größe von 7 wäre 128 x 128). Sobald ein Knoten einen bestimmten Prozentsatz seiner Unterknoten hat, wird er automatisch zu einem zweidimensionalen Array geglättet. Dies bedeutet, dass ein sehr dicht besiedelter Teil (z. B. ein vollständig erforschter Kontinent) die Leistung eines Arrays (sehr schnell) aufweist, ein dünn besiedelter Teil (z. B. eine Küstenlinie, die auf und ab gewandert ist, jedoch nicht im Landesinneren erforscht wurde) jedoch haben eine gute Leistung und eine geringe Speichernutzung.

Mein Code kann gefunden werden hier . Der Code ist vollständig, getestet (Unit- und Load-Tests) und ziemlich optimiert. Das Innenleben ist jedoch noch nicht gut dokumentiert, aber alle öffentlichen Methoden sind so, dass es brauchbar sein sollte. Wenn jemand es ausprobieren möchte, kann er sich gerne mit Fragen oder Kommentaren an mich wenden.

Ich habe es noch nicht zum Speichern von Daten in einer Datei verwendet, aber es ist ein interessantes Problem, und ich werde es möglicherweise als nächstes angehen.


Das ist also im Grunde ein erweiterbarer Baum, oder? Was vermisse ich?
KaoD

2
Die größte Verbesserung gegenüber einem erweiterbaren Baum besteht darin, dass bestimmte Knoten des Baums, die stark bevölkert sind (standardmäßig 70%), in 2D-Arrays "abgeflacht" werden, anstatt wie Bäume strukturiert zu bleiben. Dies gibt Ihnen die Geschwindigkeit einer Array-Suche ohne die (unendliche) Größe eines unendlichen Arrays.
dlras2

Sowohl Blatt- als auch Innenknoten, richtig? Interessante Idee, könnte gute Ergebnisse liefern, ich werde es versuchen, wenn ich das jemals brauche. Übrigens +1 für das Verteilen des Codes und die schnelle Antwort! Oh, und Unit-Tests auch gemacht, ich mache das (leider) nie in meinen persönlichen Projekten :)
kaoD

Wir machen keine Unit-Tests bei meiner Arbeit, so traurig, dass es meine Art zu rebellieren ist. Ich habe eine Demo-App dafür erfunden, die zeigt, wie sie gefüllt und geglättet wird, wenn ich das in den nächsten paar aufräumen kann Tage, damit es vorzeigbar ist, werde ich es auch hier posten. Es macht viel mehr Sinn, wenn Sie es sehen.
dlras2

1
Ich habe es aus den Augen verloren, sorry! Ich würde es immer noch gerne aufräumen lassen, aber ich überarbeite langsam einen Teil des Codes zwischen Unterricht und Hausaufgaben, so dass das für eine Weile nicht sein wird. Im Moment ist die alte, unhübsche Demo hier: j.mp/qIwKYt Mit unhübsch meine ich teilweise, dass es eine Menge Erklärungen erfordert. Vergessen Sie also nicht, die README- Datei zu lesen und fragen Sie hier oder per Email.
dlras2

3

Sie könnten stattdessen eine Datenbank verwenden - PostgreSQL verfügt über einige spezielle Indizierungsfunktionen, die für diese Art von Daten optimiert sind, die sich anhand der X- und Y-Koordinaten befinden. Sie können auch festlegen, dass sich die zurückgegebenen Daten nicht in einem quadratischen oder rechteckigen Bereich, sondern in einem bestimmten Radius befinden.

  PostgreSQL (kostenlos und Open Source)
  http://www.postgresql.org/

Es gibt auch andere Datenbanken, und für die Client-Seite sind bestimmte Typen möglicherweise besser geeignet, da sie eigenständig ausgeführt werden können (initiiert von Ihrer Game-Client-Anwendung) oder als Teil einer Codebibliothek enthalten sein können dass Sie "nur verwenden können." Der Vorteil ist, dass Sie kein Indizierungsschema entwerfen müssen, da die meisten SQL-Datenbank-Engines dies bereits recht gut können.

Ein Vorteil des Datenbankansatzes besteht darin, dass Sie Ihre Blöcke verkleinern können (oder Blöcke vollständig entfernen und nur Kacheln direkt verwenden, aber die Verwendung von mindestens kleinen Blöcken / Gruppen von vielen Kacheln kann je nach Ihrem Design effizienter sein). Verwenden Sie dann die SQL-Abfrage, um einen größeren Bereich einzublenden, als angezeigt werden kann. Durch das Vorladen, um nicht sichtbare Bereiche in der Nähe zu überlappen, können die Kacheln vorbereitet werden, bevor der Spieler seinen Charakter bewegt, was zu einem besseren (hoffentlich reibungsloseren) Spielerlebnis führt.

Ich habe bemerkt, dass einige Spiele einen "Cache" der Kartendaten auf der lokalen Festplatte speichern, nachdem sie diese zum ersten Mal abgerufen haben (dies ist zweifellos dazu gedacht, die Netzwerk-E / A zu reduzieren), wie zum Beispiel Ashen Empires:

  Ashen Empires (kostenlos spielbar, wunderschöne 2D-Implementierung)
  http://www.ashenempires.com/

Das Verfolgen von "zuletzt aktualisierten" Zeitstempeln mit jedem Block / jeder Kachel ist auch hilfreich, da die SQL-Abfrage, wenn lokal gespeicherte Daten verfügbar sind, eine zusätzliche Klausel "WHERE timestamp_column> $ local_timestamp" enthalten könnte, sodass nur aktualisierte Blöcke / Kacheln abgerufen werden heruntergeladen (zwei Vorteile, die durch das Einsparen von Bandbreite entstehen, sind geringere Verbindungskosten und eine geringere Verzögerung für Ihre Spieler, was sich mit zunehmender Beliebtheit Ihres Spiels noch deutlicher bemerkbar macht).

Ein Screenshot von Ashen Empires (ein paar Charaktere sind in einer örtlichen Bank und so wie diese Knochen auf dem Boden aussehen, sehen sie aus, als wären ein paar Skelettmonster hereingewandert und wurden wahrscheinlich von den Wachen der örtlichen Stadt geschlachtet):

Bildbeschreibung hier eingeben


2

Speichere und greife nicht auf sie zu, sondern speichere nur die erforderlichen zufälligen Samen sowie die Änderungen des Spielers auf der Karte. Generieren Sie dann zur Laufzeit die erforderlichen Teile (führen Sie Ihren Generierungsalgorithmus aus, und wenden Sie dann die Änderungen des Players an). Bei korrekter und konsistenter Generierungsprozedur ist die resultierende Karte für denselben Startwert immer dieselbe.

Theoretisch können Sie buchstäblich unendlich viele Karten erstellen, die auf diese Weise in einer sehr kleinen Datei gespeichert werden.


@Josh Petrie, danke für die signifikanten und großartigen sprachlichen / stilistischen Korrekturen an meinem Beitrag. Schade, dass ich eine Änderung nicht befürworten kann :-D
sh Code

1

Gibt es eine Möglichkeit, Chunks (Subkontinente / Länder in Ihrer Welt) zu partitionieren? Vielleicht können Sie also eine Art Index-Datei haben, mit der Sie schnell herausfinden können, welche Unterdatei / welcher Teil einer größeren Datei geladen werden muss, um einen Teil im Speicher zu haben ...


Es gibt immer eine Möglichkeit, Chunks zu partitionieren. IMMER. Unabhängig davon, ob sie für den Player sichtbar sind oder für den Rest des Systems relevant sind oder nicht, gibt es immer eine Möglichkeit, Weltdaten auf mehrere verschiedene Arten in Blöcke zu unterteilen.
sh Code

0

Sie könnten die Idee von Minecraft übernehmen. Ursprünglich hatten sie eine Datei pro Stück. Jetzt verwenden sie das MCRegion-Format, bei dem Blöcke in 32 x 32-Bereiche gruppiert und einer davon pro Datei gespeichert wird.

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.