Speichern von prozedural erzeugter schurkenhafter Welt in Zonen


7

Ich entwickle gerade ein 3D-Roguelike-Spiel, das in einer sehr großen Welt stattfinden wird. Die Welt wird durch einen prozeduralen Algorithmus generiert, der zur Laufzeit von einem externen Skript bereitgestellt wird. Um das Rendern zu beschleunigen und den Speicherbedarf zu minimieren, teile ich die Welt in Zonen ein. Eine Zone ist ein 2D-Rechteck mit einer maximalen Größe von 1024 x 1024 (muss kein Quadrat sein), das dynamisch zugewiesene (über ein einzelnes new) Array von Tile's enthält. Tileist so klein wie möglich definiert, um die Speichernutzung zu minimieren. Nur die Zonen, die auf dem Bildschirm oder in unmittelbarer Nachbarschaft sichtbar sind, befinden sich im Speicher. Der Rest wird je nach Bedarf aus der Festplattendatei geladen.

Das Problem, das ich habe, ist, dass der Kartengenerator nicht über das logische Zonenlayout Bescheid wissen muss, sodass er diese Art von Karte generieren kann (insbesondere für höhere Z-Ebenen wie Lofts in der Stadt): Karte über Generator generiert

Dies wäre viel zu viel Speicherverschwendung, um es in 1024 x 1024 einzuschließen. Es ist daher besser, es in kleinere Chuncks wie diese zu packen:

Endzonen

Zum Schluss die Frage: Wie kann ich die vom World Generator Script gelieferten Daten in die tatsächliche Weltstruktur im Speicher konvertieren?

Woran ich bisher gedacht habe:

  1. Das Skript hat eine Methode SetTile(x, y, tileType), und die Welt wird automatisch alle 100 Zellen neu berechnet, damit die Zonen so effizient wie möglich sind. Dies erfordert VIELES Kopieren, Verschieben, Ändern der Größe usw. von Zonen - das ist ein sehr langsamer Vorgang, aber das Weltlayout wird in logischer Reihenfolge gehalten, sodass es beispielsweise einfacher wäre, Methoden wie GetTile(x, y)im Skript zu implementieren
  2. Das Skript verfügt über eine Methode, mit SetTile(x, y, tileType)der nur vorübergehend eine Kachel in std :: vector gespeichert wird. Nachdem alles erledigt ist, führt die Welt einen zweiten Durchgang aus, in dem Zonen berechnet werden. Dies ist viel schneller, hat aber einen Nachteil: Da Kacheln in keiner bestimmten Reihenfolge festgelegt sind, ist es wirklich schwierig, eine GetTile(x, y)Methode im Skript zu haben. Vielleicht gibt es eine Lösung für diesen Nachteil, von der ich einfach keine Ahnung habe - es std::mapist viel zu langsam, um hier verwendet zu werden.
  3. Wenn dies nicht außerhalb des Bildschirms möglich ist, besteht die Lösung möglicherweise darin, Zonen im Skript verfügbar zu machen und explizit zu erstellen (ich meine, in die Skript-API- CreateZone()Funktion und ähnliches zu integrieren). Ich möchte dies nicht, da Skripte für Endbenutzer und Benutzer verfügbar gemacht werden Ich möchte sie so robust wie möglich machen.
  4. Vielleicht gibt es eine andere Lösung? Vielleicht kann ich die Weltgenerierung auf andere Weise organisieren (ich brauche noch Skripte, aber es kann ein Skript sein, das nur einen Teil der Welt generiert und dann eine Verbindung herstellt)?

Zusätzliche Dinge, die ich gerade angesprochen habe: Die ganze Welt kann zu groß sein, um sofort in Erinnerung zu bleiben. Das Problem wird also noch weiter verdreht - ich muss den Teil der Weltgeneration (im Skript) in Stufen aufteilen. Irgendwelche Vorschläge?

Was die Technologie betrifft: Ich verwende C ++ 03 'ohne Boost und AngelScript als Skriptsprache


3D wie in 3D gesehen oder 3D wie Zwergenfestung?
DampeS8N

@ DampeS8N 3D wie in der Zwergenfestung
PiotrK

Antworten:


4

Die Art und Weise, wie Dwarf Fortress und viele andere ähnliche 3D-Roguelikes mit riesigen Weltkarten umgehen, besteht darin, die Karte in Stücke zu zerlegen. In der Regel handelt es sich dabei um vertikale Abschnitte der Weltkarte, die im laufenden Betrieb leicht ein- und ausgeblendet werden können. So funktionieren Minecraft und Dwarf Fortress im Abenteuermodus.

Im Festungsmodus können Sie in der Zwergenfestung einen Block auswählen, in dem Sie spielen möchten. Mehr Teile bedeuten langsameres Spielen.

Dies löst im Allgemeinen die allgemeinen Leistungsprobleme und Probleme beim gleichzeitigen Laden einer gesamten Karte, selbst bei kleineren Karten. Ich würde mich bei komplexen Lösungen zurückhalten, bis Sie tatsächlich ein Problem haben. Hier sind jedoch einige Optionen. Aus Ihren Beispielen geht hervor, dass Sie sich für ein städtisches Spiel entscheiden. Wenn dies Wolkenkratzer bedeutet, die bis zu Hunderten von Stockwerken oder mehr reichen, kann dies sicherlich zu Leistungsproblemen führen, insbesondere wenn diese Stockwerke über mehrere Z-Ebenen verteilt sind und Wartungsräume zwischen den Stockwerken vorhanden sind, wie dies bei vielen hohen Gebäuden der Fall ist.

In diesem Fall gibt es überhaupt keinen Grund, die Karte nicht auch horizontal in Blöcke zu zerlegen. Sie können völlig leere von der Karte verwerfen. Oder wenn Ihr Spiel Bauarbeiten zulässt, lassen Sie sie einfach leer (oder mit Luft gefüllt oder was auch immer Ihr Spiel benötigt).

Exkurs: Eigentlich könnte ein Roguelike in massiv hohen Gebäuden, der den Luftdruck modelliert, wirklich großartig sein ...


Horizontale Stücke! Ich habe nicht über sie nachgedacht und sie scheinen in meinem Fall wirklich gut zu funktionieren. Vielen Dank!
PiotrK

3

Wenn ich Ihre Frage richtig verstehe, besteht Ihr Hauptanliegen darin, die generierten Kacheln zu speichern / darauf zuzugreifen. Gute Nachricht: Sie müssen nicht den leeren Platz für alle Kacheln reservieren, die noch nicht generiert wurden. Es gibt viele Möglichkeiten, "unregelmäßig geformte" Datenstrukturen zu speichern.

Eine mögliche Lösung besteht beispielsweise darin, eine Liste von Zeigern auf die Kacheln zu erstellen, die Sie bereits generiert haben.

Geben Sie hier die Bildbeschreibung ein

Die Liste (graues Kästchen) hat eine dynamische Größe, die bei 0 beginnt und mit jedem Zeiger (roter Kreis) wächst, den Sie eingeben. Wenn Sie also bisher nur 3 Kacheln generiert haben, wird eine Liste mit 3 Zeigern angezeigt. Auf diese Weise müssen Sie den Speicher nicht im Voraus für Millionen von Kacheln reservieren.

Geben Sie hier die Bildbeschreibung ein

Bei der Suche nach einer bestimmten Kachel durchlaufen Sie die Zeigerliste und überprüfen die Eigenschaften der Kachel, auf die der aktuelle Zeiger zeigt. In C #, beispielsweise mit einer Listendarstellung, würde eine Suche folgendermaßen aussehen:

Tile GetTile(int x, int y)
{
   foreach (var tile in tileList)
   {
      if (tile.X == x && tile.Y == y) return tile;
   }
   return null;
}

Es sollte kein Szenario geben, in dem alle Kacheln gleichzeitig generiert und in den Speicher geladen werden. Wenn sich der Spieler bewegt, entfernen Sie einfach die nicht mehr benötigten Zeiger aus der Liste (und die entsprechenden Kacheln aus dem Speicher). Stellen Sie jedoch sicher, dass Sie eine kleine Verzögerung haben, da sonst die Speicheroperationen zu Verzögerungen führen können, wenn sich der Spieler schnell in eine Zone oder ein Plättchen hinein- und herausbewegt.

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.