Ich habe einen Ebenenlader erstellt, der in einem XML-Format gespeicherte Ebenen lädt. Dies bedeutet, dass ich einen XML-Parser verwende, um die Ebenen zu lesen. Es funktioniert, aber das Laden etwas größerer Level dauert zu lange. Jetzt suche ich nach einer Möglichkeit, das Laden zu beschleunigen. Ich muss nicht die gesamte Datei auf einmal laden, da der Player ohnehin nicht die gesamte Karte sehen kann. Ein teilweises Laden wäre also in Ordnung.
Ich dachte an einen kachelbasierten Lademechanismus, bei dem mehrere Teile der Karte vorhanden sind und der Lader nur den Teil / die Kachel lädt, auf dem der Benutzer steht. Aber ich bin mir nicht sicher, ob das eine gute Idee ist.
loading
levels
world-editor
Vaillancourt
quelle
quelle
Antworten:
Zunächst sollten Sie messen, wo genau der Engpass liegt, damit Sie keine Zeit damit verschwenden, Dinge zu verbessern, die bereits gut genug sind. Der Engpass könnte einer der folgenden sein:
In Ihrer Frage wird jedoch speziell das teilweise Laden von Kartenteilen erwähnt. Nehmen wir für diese Antwort an, Sie haben Ihren Code profiliert und festgestellt, dass das Lesen und / oder Parsen der XML-Datei das Problem ist und dass das teilweise Laden tatsächlich die naheliegendste Lösung für Ihr Problem ist.
Bei XML-basierten Dateiformaten ist dies normalerweise nicht möglich. Ein DOM-basierter XML-Parser muss das gesamte XML-Dokument analysieren und validieren, bevor Sie Daten daraus extrahieren können. Ein SAX-basierter XML-Parser muss mindestens alles bis zu dem Knoten lesen, den Sie suchen. Wenn Sie also ein teilweises Laden zulassen möchten, müssen Sie das XML-basierte Format aufgeben und Ihr eigenes Binärdateiformat erfinden, in dem Map Chunks an bekannten Dateipositionen beginnen, zu denen Sie selektiv wechseln können
seek
, ohne die Daten dazwischen lesen zu müssen.Wenn Sie XML weiterhin verwenden möchten, können Sie jeden Kartenblock als separate XML-Datei speichern.
quelle
Obwohl XML für praktisch alles und auch für Anwendungen mit hohem Volumen und geringer Latenz verwendet wird, ist es für fast alles ein miserables Format, insbesondere für Anwendungen mit zeitlichen Einschränkungen, einschließlich Spielen (außer möglicherweise zum Speichern) die Einstellungen des Spiels). Selbst für Live-Daten ist ein einfaches Format mit binären Tags, das im Grunde dasselbe wie XML tut und nur nicht von Menschen lesbar ist (z. B. ein 16-Bit-Knotentyp, 32-Bit-Länge, gefolgt von Inhalten), um ein Vielfaches schneller zu lesen und schreibe.
XML ist verlockend, weil es für Menschen lesbar ist und Sie nicht mehr Tools als einen Texteditor benötigen, um eine XML-Datei zu schreiben. Es ist jedoch auch zu ausführlich, verarbeitet mehrere Datentypen (Binärdaten, große Mengen von Zahlen usw.) nicht so gut und die Verarbeitung dauert sehr unleserlich.
Sie können das Parsen erheblich beschleunigen, indem Sie einen anderen Parser verwenden. Es gibt viele Alternativen (Rapidxml, Pugixml, was auch immer), die jedoch nicht das allgemeine Problem lösen, dass das Format zu ausführlich und für den Menschen lesbar ist.
Während es akzeptabel und nicht allzu ungewöhnlich ist, XML in der eigenen Produktionspipeline zu verwenden (obwohl die meisten Leute lieber JSON oder YAML verwenden würden), gibt es normalerweise ein maßgeschneidertes Tool, das während des Erstellungsprozesses ausgeführt wird und Ihre XML-Dateien konvertiert zu einem proprietären Binärformat, das idealerweise (nicht unbedingt, aber idealerweise) einfach geladen oder so wie es ist speicherabgebildet werden kann. Kein Parsen, nur Speicherzuordnung und Setzen eines Zeigers.
Dasselbe Tool (oder ein ähnliches Tool) liest normalerweise auch andere Daten ein, z. B. Texturen, die in einer Vielzahl von Formaten gespeichert sind, die möglicherweise nicht trivial zu analysieren sind, und fügt sie in gewisser Weise in eine binäre Spieldatei ein So können sie einfach einsatzbereit zugeordnet werden.
Mit dem richtigen Format sollte das Laden eher 0,45 Sekunden als 45 Sekunden betragen.
quelle
Es gibt eine Reihe von Faktoren, um dieses Problem anzugehen, obwohl Sie auf dem richtigen Weg sind.
Einzellast
Der erste Ansatz besteht, wie Sie bereits versucht haben, darin, alles auf einmal zu laden. Dadurch stehen Ihre gesamte Ladezeit und Datei-E / A im Vordergrund. Wie Sie bereits bemerkt haben, kann Ihre anfängliche Ladezeit mit zunehmender Kartengröße für den Benutzer ärgerlich werden. Dadurch wird auch eine Obergrenze für Ihre maximale Weltgröße erstellt, die auf dem minimal erforderlichen Speicher der Zielplattform basiert.
Es gibt zwei andere Möglichkeiten, wie ich damit umgehen kann: Sie können die Welt aufteilen und sie vom Bildschirm laden, wenn sich der Player ihnen nähert (Laden des Streams); und Sie können Ladezonen haben.
Zonen laden
Durch das Laden von Zonen können Sie eine maximale Blockgröße finden, die sich positiv auswirkt, und Ihre Welt in nicht größere Teile unterteilen. Wenn der Spieler eine Transitregion (Tür, Höhle, Kartenkante) betritt, findet eine Art Übergangsgrafik oder -animation statt, um den Spieler abzulenken. Die neue Zone wird aus dem Dateisystem geladen, während die alte gespeichert und entladen wird. Dies ist eine Mitte des Straßenansatzes. Sie können zwar Welten haben, die viel größer als die Speicherkapazität des Zielgeräts sind, und alle Ihre E / A-Anstrengungen unternehmen, bevor der Player die Kontrolle erhält. Dies erhöht jedoch die E / A-Operationen gegenüber Ihrem ursprünglichen Ansatz. Dies kann auch die Kontinuität des Spiels beeinträchtigen, was für den Stil Ihres Spiels günstig oder nicht günstig sein kann. Die Final Fantasy-Reihe ist wahrscheinlich die bekannteste für diese Art von Ansatz.
Laden des Streams
Was ich als das bekannteste Beispiel für das Laden von Streams betrachten würde, ist Minecraft. Chunks werden in den Speicher geladen und aus diesem entfernt, wenn sich die Nähe des Players ändert. Wie beim Laden von Zonen wird beim Laden von Streams auch die Obergrenze für die Weltgröße entfernt. Außerdem werden die Unterbrechungen des Ladevorgangs aus der Benutzererfahrung entfernt. Dies ist jedoch der Ansatz mit der höchsten Datei-E / A-Belastung, da Zonen während einer Wiedergabesitzung häufig geladen und aus dem Speicher gelöscht werden können. Es muss auch darauf geachtet werden, eine Zone in einer näheren Nähe zu laden, als sie aus dem Speicher entfernt wurde, um zu verhindern, dass ein Spieler, der wiederholt über eine Blockgrenze läuft, das Spiel bei E / A-Operationen vergräbt. Für 3D-Spiele eine Methode namens Dogleggingwird oft verwendet, wodurch die Sicht des Spielers auf den Bereich, der geladen wird, blockiert wird. Dies hat normalerweise die Form eines Flurs mit einer Kurve, kann aber jedes Hindernis sein, das die Sichtlinie des Spielers blockiert.
Welcher dieser Ansätze für Ihr Spiel am besten geeignet ist, liegt bei Ihnen.
quelle
Werfen wir einen Blick auf smart ... was genau ist smart?
Sie geben an, dass Sie XML als Format zum Speichern Ihrer Ebenen verwenden. XML ist ein hierarchisches Format, das in einer Datei gespeichert wird. Somit haben wir 2 Hauptfaktoren, die unsere Ladezeiten beeinflussen:
E / A-Geschwindigkeit: Dies ist eine harte Einschränkung . Sie können die Lese- und Schreibgeschwindigkeit mit Ihrem Code nicht beeinflussen. Zumindest nicht zu viel. Sie können Dinge wie die Prozessor-Cache-Größe usw. verwenden, aber das ist auf niedriger Ebene und auch von Computer zu Computer unterschiedlich.
Ihre XML-Struktur: Hier können Sie wild werden. Sie können entscheiden, wie Sie Dinge speichern und was früher kommt, was später. Sie können sogar damit beginnen, Teile einer Ebene in kleinere Datasets zu gruppieren und diese Datasets dann in Ihrem großen XML-Dokument zu ordnen, wobei die früher benötigten näher am Dateianfang liegen.
Wenn Sie also den zweiten Faktor erweitern, können Sie Ihr Level in einem Format wie dem folgenden speichern:
Jeder
<section>
enthält einen Teil des Levels, so dass jeweils einer davon geladen werden kann.Nachdem wir das Format in etwas Nützlicheres organisiert haben, besteht der nächste Schritt darin, sicherzustellen, dass wir diese Funktion tatsächlich nutzen können. Eine Möglichkeit besteht darin, die gesamte Datei in den Speicher zu laden, sie dann zu zerlegen und die Level-Daten erst nach dem Zerlegen zu analysieren. Auf diese Weise können Sie bei Bedarf zusätzliche Abschnitte laden, ohne die teure Erstellung von Texturen, Physikobjekten und was nicht, bevor Sie sie benötigen.
Wenn Ihre Datei aus irgendeinem seltsamen Grund zu groß ist, um in den Speicher geladen zu werden, können Sie anstelle des wahrscheinlich verwendeten DOM-Parsers einen SAX-Parser verwenden . Der Vorteil eines SAX-Parsers besteht darin, dass er nicht die gesamte Datei analysieren muss, bevor Sie daran arbeiten können. Es liest und verarbeitet auch die Datei, die sich von Natur aus von einem DOM-Parser unterscheidet, so dass das Lesen beider Dateien sowieso gut sein kann.
Ein weiteres Thema, das sich aus Ihren Kommentaren zu anderen Antworten zu ergeben scheint, ist die Organisation Ihrer Daten . Sie sollten sich im Allgemeinen bemühen, Ihre Spiellogik nach Möglichkeit von Ihren Spieldaten zu entkoppeln. Das Laden einer Ebene darf beispielsweise nicht bedeuten, eine Datei von der Disc zu lesen und alles zu erstellen, was in jeder Zeile der Datei beschrieben wird. Es ist möglicherweise besser, die Daten stattdessen in einer speicherinternen Datenstruktur zu speichern und dann nur bei Bedarf daran zu arbeiten.
quelle
<section>
. Der Loader muss jedes dieser Tags einzeln in den Speicher laden. Wenn Sie also alle Tags auf ein oder zwei Buchstaben kürzen, wird dies wahrscheinlich zu einer erheblichen Beschleunigung führen, vorausgesetzt, es gibt viele Tags. (Siehe HTML-Tags)Ändern Sie Ihre XML-Datei nicht, wenn Sie ein nützliches Format für Ihre Arbeit finden.
Der Trick besteht darin, die Datei zu behalten, die Sie mögen, aber Ihr Spiel diese Datei nicht direkt verwenden zu lassen. Schreiben Sie eine einfache Routine (die als Teil des Packens des Spiels ausgeführt wird), um Ihr XML in optimale Blockgrößen zu analysieren, die dynamisch geladen werden sollen, und erstellen Sie gleichzeitig einen Index für die Blöcke, die Sie zu Beginn Ihres Spiels in den Speicher laden können.
Als sehr vereinfachtes Beispiel, wenn Ihre Zeichnungsentfernung 0,9 km beträgt, könnte jede Blockdatei ein Quadrat von 1 km Ihres Weltrasters darstellen.
Ihr Parser teilt Ihre Welt in 1 km² große Blockdateien auf
Lassen Sie den Parser in jeder Blockdatei die Dateinamen der 8 angrenzenden Blockdateien hinzufügen. Auf diese Weise müssen Sie nicht auf den Index klicken, um zu wissen, welche Nachbarn geladen werden sollen. Fügen Sie außerdem die Koordinate oben links und die Koordinate unten rechts des Rasterblocks hinzu, damit Sie leicht testen können, ob sich ein Spieler in diesem Block befindet
Der Parser erstellt dann eine Indexdatei, indem er alle Blöcke durchläuft, in denen der Name der Blockdatei, die Koordinate oben links und die Koordinate unten rechts gespeichert sind
Das Spiel wird geladen, durchsucht den Index, um herauszufinden, in welchem Block sich der Spieler befindet. Lädt die Blockdatei, liest die benachbarte Blockliste aus dieser Blockdatei und lädt auch diese Blöcke. Sie haben jetzt 9 Chunks um Ihren Player geladen. Wenn sich Ihr Spieler durch das Level bewegt und der Standort Ihres Spielers über den aktuellen Block hinausgeht, durchsuchen Sie die anderen geladenen Blöcke nach dem Block, in dem sich der Spieler gerade befindet. Verwenden Sie die Liste der neuen Blöcke des neuen Blocks, um die neu benachbarten Blöcke zu laden und die entladenen zu entladen nicht länger nebeneinander.
Wenn sich der Spieler bewegt (teleportiert) und die geladenen Blöcke durchsucht, um herauszufinden, in welchem er sich befindet, schlägt dies fehl. An diesem Punkt suchen Sie erneut nach dem Index
quelle
Dies ignoriert im Grunde die Vorschläge zum Profilieren Ihrer Ladezeit, zum Bewerten der Komplexität des Dateiformats usw. und ich sehe jetzt eine Variante von Stephans Vorschlag für Ladezonen.
Überlegen Sie, ob Sie ein SEHR großes Level hatten - wie es ein MMO getan hätte. Realistisch gesehen würden Sie nicht das Ganze auf einmal laden. Stattdessen würden Sie es in Stücken tun. Oder laden Sie es auf verschiedenen Detailebenen - Details auf niedriger Ebene (und damit wahrscheinlich kleinere Datendateien) für weit entfernte Dinge und nur auf hoher Detailebene in der Nähe des Players.
Der Ansatz wäre also ungefähr so ...
Es wird davon ausgegangen, dass Detailabschnitte auf niedriger Ebene sehr schnell geladen werden und auf hoher Ebene mehr Zeit benötigt wird.
Dies hilft, indem Sie genau das laden können, was Sie benötigen, nicht alles. Sie haben auch die Möglichkeit, die Prozessorlast zu steuern, da Sie keine entfernten Details rendern oder simulieren müssen - nur dort, wo sich der Player befindet.
Es ist eine allgemeine Antwort, aber es könnte für Sie funktionieren. Es ist definitiv ein Ansatz für ein sehr großes Niveau, aber für ein kleines nicht wirklich praktisch. Vielleicht liegen Ihre Bedürfnisse irgendwo dazwischen und es wird helfen.
quelle