Erhaltung des Spielflusses bei aufwändiger Funktion



  • Guten Abend,

    Bei meinem Spiel (einem Jump'n'Run) habe ich eine Klasse GameState , die den aktuellen Spielstatus, sprich die vorliegende Situation der Welt (Tiles, Gegner, Lifte...) speichert. In jedem Level sind mehrere Checkpoints vorhanden, bei denen man seinen Spielstatus sichern kann, um beim Tod der Spielfigur von dort aus weiterzuspielen. Beim Passieren eines solchen Checkpoints werden also sämtliche Spielelemente kopiert und gesichert, um beim Respawn wieder geladen zu werden. Ich sehe keine Möglichkeit, nur einen Teil zu sichern, da sich sehr viele Spielelemente ständig verändern können.

    Man kann sich vorstellen, dass dieses Speichern bei grösseren Levels länger dauern kann. Am meisten fallen dabei die Tiles ins Gewicht; bei einer Kartengrösse von 250 * 100 Tiles sind das allein 25'000 Kopien, zusätzlich zur Speicheranforderung mittels verschachtelten std::vector s und Löschen des alten Status. Entsprechend wird der Spielfluss für kurze Zeit unterbrochen, was ich zu umgehen versuche.

    Ich habe bisher an Threads gedacht: Eine Funktion sichert den Spielstatus in Ruhe, während das Spiel weiterläuft. Jedoch bin ich da etwas skeptisch, da sich die Spiellogik kurz nach dem Erreichen des Checkpoints bereits wieder verändert hat. Somit würde die Funktion Spielstati aus unterschiedlichen Zeiten sichern, wodurch Inkonsistenzen auftreten.

    Ich sehe keine wirklich gute Lösung für mein Problem und wäre deshalb froh um einige Ratschläge. Es wäre auch nicht wahnsinnig dramatisch, wenn ich den Spielfluss unterbrechen müsste, mit einer entsprechenden Meldung im Sinne von "Spiel speichern..." könnte ich den Vorgang abrunden. Trotzdem wäre es interessant zu wissen, ob hierfür irgendwelche gängigen Vorgehensweisen existieren. 🙂



  • Um wieviel Megabyte handelt es sich denn (Statusinformationen von 25000 Tiles sind nicht gerade viel)? Auch brauchst du den Spielstatus nicht unbedingt in eine Datei schreiben, sondern kopierst einfach deine Datenstruktur im Speicher. Das sollte schnell gehen. Falls der Spieler den Checkpoint gern persistent haben moechte, dann kann er das ja gern, nur nicht waehrend des Spielens sondern vielleicht im Hauptmenue.

    Alternativ kannst du es auch so machen wie z.B. bei Diablo. Wird ein Checkpoint/Waypoint erreicht, wird nur der Spielerstatus (Quest, Inventar, Boss XY getaetet, ...) gesichert und beim Verlassen des Spiels gespeichert. So findet er zwar eine andere Welt vor, aber der Spielfluss wird nicht unnoetig gestoert.



  • knivil schrieb:

    Auch brauchst du den Spielstatus nicht unbedingt in eine Datei schreiben, sondern kopierst einfach deine Datenstruktur im Speicher. Das sollte schnell gehen.

    Das finde ich gut, die kopierten Daten kann man dann ja auch nebenläufig in die Datei abspeichern.
    Von was für einer Zeitspanne reden wir eigentlich (bei großen Levels)? 1s, 10s?


  • Mod

    normalerweise speichert niemand das ganz level mit ab, sondern nur fuer den spieler relevante daten die sich gegenueber dem orginallevel veraendert haben. das sind bei einem jump&run ein paar bytes



  • Danke für die Antworten.

    knivil schrieb:

    Um wieviel Megabyte handelt es sich denn (Statusinformationen von 25000 Tiles sind nicht gerade viel)?

    sizeof(Tile) ist 56, also etwa 1.4 MByte.

    knivil schrieb:

    Auch brauchst du den Spielstatus nicht unbedingt in eine Datei schreiben, sondern kopierst einfach deine Datenstruktur im Speicher. Das sollte schnell gehen.

    Das mache ich momentan auch, alles im Arbeitsspeicher.

    knivil schrieb:

    Alternativ kannst du es auch so machen wie z.B. bei Diablo. Wird ein Checkpoint/Waypoint erreicht, wird nur der Spielerstatus (Quest, Inventar, Boss XY getaetet, ...) gesichert und beim Verlassen des Spiels gespeichert. So findet er zwar eine andere Welt vor, aber der Spielfluss wird nicht unnoetig gestoert.

    Hm, ich möchte eigentlich schon, dass die Welt die selbe bleibt. Auch zum Beispiel, dass man nochmals alle Gegner besiegen muss, wenn man schon einen Teil erledigt hat und dann stirbt.

    Badestrand schrieb:

    Von was für einer Zeitspanne reden wir eigentlich (bei großen Levels)? 1s, 10s?

    Etwa 0.5 bis 3 Sekunden bei obigem Beispiel. Aber argh, ich war leider zu blöd, die Debug-Laufzeitumgebung auch im Release-Modus abzuschalten. Jetzt dauert es nur noch Bruchteile einer Sekunde. Bei grösseren Levels könnte das trotzdem problematisch werden, auch das Speicher-Anfordern.

    rapso schrieb:

    normalerweise speichert niemand das ganz level mit ab, sondern nur fuer den spieler relevante daten die sich gegenueber dem orginallevel veraendert haben. das sind bei einem jump&run ein paar bytes

    Ja, das habe ich auch gedacht. Nur müsste man dann bei jeder Änderung am Level die Unterschiede zu vorher wieder speichern. Also die ganze Zeit, da sich das Level ständig verändert. Und ich wollte den Austausch eigentlich auf das Checkpoint-Erreichen und Respawnen beschränken, anstatt über die ganze Spiellogik zu verteilen.

    Jedoch ist mir ein potenzieller Designfehler aufgefallen. Vielleicht habt ihr euch schon wegen der 56 Byte pro Tile gefragt: Ich speichere neben wirklich wichtigen Daten wie Tile-Typ, Animationsschritt auch noch zwei Container. Der eine beinhaltet Zeiger auf Gegner, die beim Betreten des Tiles aktiviert werden. Der andere hält spezifische Eigenschaften gewisser Tiles, zum Beispiel Zielkoordinaten bei Teleportern, zu öffnende Türen bei Schaltern und so weiter.

    Natürlich habe ich dadurch recht viel Overhead - und das, wobei nur sehr wenige Tiles überhaupt solche Eigenschaften haben. Es wäre wohl besser, ich würde eine std::map haben, die nur gewissen Tiles Eigenschaften zuordnet. Dadurch hätte man auch viel weniger Speicherverbrauch. Da wäre sizeof(Tile) wohl nur noch 8 - um Faktor 7 kleiner! Die Umstellung würde sicher recht viel Aufwand mit sich bringen, aber könnte mein Problem langfristig lösen und würde nebenbei noch Speicher schonen...



  • Du solltest einfach deine Daten besser in statisch und dynamisch unterteilen. Das macht die Savegames kleiner und damit auch das kopieren schneller. z.b. dein welche Enemy werden aktiviert ist eigentlich statisch. Was sich veraendert, ist nur eoin Bit fuer "schon aktiviert". Da du Pointer ehh nicht speichern solltest, musst du hier auf IDs umstellen. Dann machst du ein Bitarray, das eben fuer jede ID angibt ob schon aktiviert und speicherst nur dieses Bitarray.

    Eine ander Moeglichkeit ist eine Kopie aller Daten zu haben, die aber mit einer Queue geaendert werden. Wenn du speichern willst, legst du in die Queue ein "Stop" und startetst einen Thread, der aus dieser Kopie abspeichert, sobald dieses Stop in der Queue vorn liegt. Wenn abgespeichert wurde, loescht du das "Stop" aus der Queue wieder und alle "aufgestauten" Aenderungen werden nun durchgefuehrt. Hier kann es nur das Problem geben, das so oft gespeichert werden soll, das sich auch die Threads aufstauen, weil ihr Stop noch nicht abgearbeitet werden kann. f'`8k

    Autocogito

    Gruß, TGGC (Was Gamestar sagt...)



  • was verändert sich denn an deiner tilemap, dass du sie in ein savegame speichern musst?



  • Ich würds grob wie folgt machen:
    Du hast a) ein komplettes Level im Speicher und b) den Teil des Levels zusätzlich nocheinmal im Speicher, der gerade auf dem Schirm angezeigt wird. Beim Fortbewegen werden die Teile, die neu auf den Schirm kommen aus dem Komplettlevel kopiert und an entsprechender Stelle in den Anzeigebereich geladen. Gleichzeitig werden die Teile, die aus dem Anzeigebereich verschwinden, in den Komplettlevel zurückgeschrieben und aus dem Anzeigebereich gelöscht.
    Speichern läuft dann wie folgt:
    a) Schreibe auf der Stelle einmal den ganzen Anzeigebereich ins Komplettlevel
    b) Schalte das normale "zurückschreiben beim Fortbewegen" aus (im Anzeigebereich-Puffer sammeln sich dann erstmal Daten)
    c) Kopiere den Komplettlevel in die Sicherungsdatei
    d) Schalte das Zurückschreiben wieder ein (der Anzeigebereichspuffer schrumpft wieder auf die normale Größe)
    Damit ist der kritische Vorgang nicht die komplettkopie des levels sondern nur das Festschreiben des Anzeigepuffers.



  • Toll. Du machst das, was ich beschrieb. Dafuer noch unnoetig kompliziert. f'`8k

    Autocogito

    Gruß, TGGC (Was Gamestar sagt...)



  • Angenommen es gibt keine statischen Levelelemente, nur eine Art Ausgangspunkt ...



  • @ TGGC
    Vielen Dank für den Hinweis mit dem statisch/dynamisch! Das scheint mir wirklich eine sehr gute Idee zu sein, so könnte ich vieles vereinfachen. Ich denke, wenn ich das so durchziehe, ist auch das Kopieren viel weniger aufwändig, sodass ich gar keine zusätzlichen Threads mehr brauche.

    @ TravisG
    Einige Animationen müssen aufeinander abgestimmt sein. Gewisse Tiles können zerstört werden, Tore können geöffnet/geschlossen werden, und dann sind noch die ausgelösten Gegner. Aber ich werde jetzt eben versuchen, mein Konzept so einzurichten, dass ich viel weniger Änderungen berücksichtigen muss.

    @ pumuckl
    Ich weiss nicht, ob sich das so bei mir übertragen lässt. An der Spiellogik beteiligt ist ja nicht nur der Anzeige-Teil, sondern die gesamte Karte, sei es aufgrund von Gegnern, Liften oder abgefeuerten Projektilen, die auch der Kausalität unterworfen sind. Das ist ja eigentlich auch was rapso sagte, aber eben: Das ganze Level kann sich verändern.

    Ich glaube, vorerst werde ich TGGCs Tipp berücksichtigen und schauen, dass ich die Tile-Eigenschaften reduzieren kann. Somit muss ich nur noch das Notwendige sichern.


Anmelden zum Antworten