Das einfache SaveGame-Tutorial dauert fünf Minuten und funktioniert perfekt — im Tutorial. Dann fügst du einen zweiten Save-Slot hinzu, oder shippst ein Update, das deine Daten ändert, oder merkst, dass das Spiel in dem Frame einfriert, in dem es schreibt. Plötzlich reicht die Fünf-Minuten-Variante nicht mehr. In diesem Post geht es um die Teile, die die Tutorials auslassen — die, die entscheiden, ob du dein Save-System mitten im Projekt neu schreibst.

Die Fünf-Minuten-Variante (und wo sie endet)

Erstell ein Blueprint-Child von SaveGame. Füg Variablen für alles hinzu, was du persistieren willst. Save Game to Slot (das Objekt, ein Slot-Name, ein User-Index). Zum Laden: Load Game from SlotCast To deine Klasse → Variablen lesen. Das ist das ganze Tutorial, und es funktioniert wirklich.

Es endet in dem Moment, in dem du einen zweiten Slot willst, ein Update, das ein Feld hinzufügt, oder einen Save, der nicht stottert. Gehen wir die einzeln durch.

Async, damit der Save nicht hitcht

Save Game to Slot ist synchron — es blockiert den Game-Thread, während es auf die Disk schreibt. Bei einem winzigen Save merkst du nichts. Bei einem großen, oder auf einer langsamen Disk, ist es ein sichtbares Einfrieren — und manche Konsolen-Zertifizierungen lehnen Hitches während eines Saves rundweg ab.

Nimm stattdessen Async Save Game to Slot. Es schreibt auf einem Background-Thread und feuert einen Completed-Pin, wenn fertig — dieselbe Latent-Node-Idee wie beim Async-Loading. Behalt die synchrone Variante nur für winzige Saves während Pause oder im Menü, wo ein paar Millisekunden Stall ohnehin unsichtbar sind.

Mehrere Slots, ohne alles neu zu lesen

Die meisten Spiele brauchen mehrere Slots und einen Load-Screen, der sie auflistet: "Slot 1 — Level 3, 2h 14m, gestern". Der Fehler ist, jede volle Save-Datei zu laden, um diese Liste zu bauen — langsam, und du deserialisierst Daten, die du nicht mal zeigst.

Das Muster: halt ein separates, winziges Metadaten-SaveGame, das nur die Anzeige-Infos hält — Name, Level, Spielzeit, Timestamp, vielleicht ein Thumbnail. Der Load-Screen liest nur das. Der volle Save lädt erst, wenn der Spieler wirklich einen Slot wählt. Kleine Datei für die Liste, große Datei on demand.

Versioniere deine Saves ab Tag eins

Das ist das, was Anfänger am meisten bereuen auszulassen. Füg deinem SaveGame jetzt ein Integer-Feld SaveVersion hinzu, bevor du irgendwas shippst. Wenn ein künftiges Update Felder hinzufügt oder umbenennt, prüfst du beim Laden die Version und migrierst alte Saves, statt sie zu brechen.

Ohne es macht dein erster Content-Patch jeden Spieler-Save ungültig. Mit ihm schreibst du ein kleines "wenn Version < 2, füll das neue Feld mit einem Default" und alle behalten ihren Fortschritt. Ein Integer jetzt erspart einen Support-Albtraum später.

Speichere Daten, keine Objekte

Du kannst keine Hard-Reference auf einen gespawnten Actor oder ein platziertes Level-Objekt speichern und erwarten, dass es zurückkommt — der Pointer bedeutet nächste Session nichts. Speicher stattdessen Identifier: einen Row-Namen, eine ID, einen Soft-Path, einen Transform. Beim Laden re-finden oder re-spawnen aus denen. (Wenn Hard vs Soft Reference noch unklar ist, siehe Soft References.)

Und speicher nur, was du nicht regenerieren kannst. Abgeleitete Werte, vollen World-State, Default-Settings — auslassen. Speicher das Delta: was der Spieler geändert hat, nicht die ganze Spielwelt.


Plugin tip
Das Standard-SaveGame gibt dir ein Verb — ein Objekt in einen Slot schreiben. Slot-Management, Profile, Versionierung, sicheres Disk-I/O: das baust du alles selbst, jedes Projekt. FoxEasySave liefert genau diese Schicht — Slot- und Profil-basierte Saves, Versionierung, Disk-Handling — als Blueprint-Nodes, sodass du dein Design behältst und das Plumbing überspringst. Optional, aber es ist der Unterschied zwischen einem Wochenende und einem Nachmittag.

Typische Fehler

  • Synchroner Save auf einer großen Datei. Sichtbarer Hitch, möglicher Cert-Fail. Nimm Async Save Game to Slot.
  • Kein Versions-Feld. Dein erstes Update bricht jeden bestehenden Save. Füg SaveVersion vor dem Launch hinzu.
  • Hard-Object-References speichern. Sie überleben die Session nicht. Speicher IDs / Soft-Paths und löse sie beim Laden neu auf.
  • Den vollen Save nur laden, um die Slot-Liste zu zeigen. Halt schlanke Metadaten separat.
  • "Noch kein Save" nicht behandeln. Load Game from Slot gibt bei einer frischen Installation None zurück. Null-Check und ein neues Spiel starten.
  • Alles speichern. Aufgeblähte Dateien heißen langsame Saves und langsame Loads. Speicher Änderungen, nicht die Welt.

Wie du prüfst, dass es funktioniert

  • Check nach einem Save, dass die Datei wirklich aufgetaucht ist: sie landet in Saved/SaveGames/ in deinem Projekt (und im User-Ordner der Plattform in einem gepackten Build).
  • Print String den Result-Bool des Saves und das Is Valid des geladenen Objekts. Stille Fehler sind hier häufig — ein falscher Slot-Name lädt nichts und sagt nichts.
  • Test den Upgrade-Pfad absichtlich: mach einen Save, erhöh SaveVersion, lad ihn. Wenn er crasht, ist deine Migration das, was du fixen musst, bevor Spieler drauf treffen.

Die 30-Sekunden-Zusammenfassung

  • Basis: SaveGame-Child + Save Game to Slot / Load Game from Slot + Cast.
  • Nimm Async Save Game to Slot, damit das Schreiben nie den Frame einfriert.
  • Halt schlanke Per-Slot-Metadaten, damit der Load-Screen keine vollen Saves liest.
  • Füg eine Versionsnummer hinzu, bevor du shippst — so überleben alte Saves Updates.
  • Speicher IDs und Deltas, nie Hard-Object-References oder regenerierbare Daten.

Nächsten Sonntag, wenn die Votes halten: DataTable vs DataAsset vs Primary DataAsset — drei Wege, die Daten deines Spiels zu speichern, und die echten Gründe für jeden.

— Marco