Behebung der fehlenden „Clear Player Data“-Option in Fortnite Creative: Verse-Resets und Custom-Backend-State-Recovery
Kurz und knapp
Dieser Artikel befasst sich mit einem kritischen Fehler in Fortnite Creative/UEFN, bei dem die Option „Clear Player Data“ in der Publishing-Konsole verschwindet. Er beschreibt eine manuelle Lösung mittels Schema-Versionierung in Verse, um veraltete Save-States von Spielern beim Session-Start automatisch zu überschreiben. Zudem werden die Einschränkungen der nativen UEFN-Persistenz erläutert und externe Backend-Lösungen wie [horizOn](https://horizon.pm) als skalierbare Alternative für komplexere Multiplayer-Spiele vorgestellt.
Deine neueste Fortnite Creative- oder UEFN-Map ist startbereit. Du hast deine Economy refaktoriert, dein Klassensystem überarbeitet und deinen Gameplay-Loop optimiert. Doch beim Veröffentlichen stoßt du auf einen Albtraum, der die Engine lahmlegt: Die Option „Press Triangle to Clear Player Data“ ist vom Bestätigungsbildschirm verschwunden. Du sitscht in der Falle. Deine Spieler laden deine Map mit veralteten Persistence-Daten, was zu sofortigen Stack Overflows, fehlerhaften Questreihen und Spielabstürzen durch State-Mismatches führt.
Genau dieses Problem plagt Creator in den Entwicklerforen von Epic, wo das plötzliche Fehlen des Reset-Buttons keinen UI-basierten Mechanismus zur Bereinigung veralteter Production-States übrig lässt. Wenn sich das Schema deiner Player-Persistence-Daten ändert, versucht das Backend von Epic, die alten serialisierten Daten auf deine neuen Strukturen abzubilden. Wenn dein Code nicht darauf ausgelegt ist, diese abweichenden Properties elegant abzufangen, zerschießt du den Game-State für 100 % deiner wiederkehrenden Spieler.
In diesem Tutorial tauchen wir tief in die technischen Workarounds für diesen Bug ein. Wir zeigen dir, wie du ein robustes Schema-Versionierungssystem in Verse aufbaust, lokale UEFN-Einstellungen konfigurierst, um Test-States zu löschen, und wie dich Custom-External-Backends von den Einschränkungen der Closed-Platform-Persistence befreien können.
Die Ursache: Warum Schemas bei Versions-Upgrades fehlschlagen
In Fortnite Creative und dem Unreal Editor for Fortnite (UEFN) werden persistente Daten in der Datenbank-Infrastruktur von Epic mithilfe von weak_map-Strukturen gespeichert. Eine weak_map verknüpft eine Player-Instanz mit einer benutzerdefinierten Klasse, die mit dem <persistable>-Spezifizierer gekennzeichnet ist. Im Gegensatz zu traditionellen SQL- oder Dokumenten-Datenbanken führt der Storage-Layer von Epic keine formelle Migrations-Pipeline aus, wenn du ein Update veröffentlichst. Stattdessen versucht er, den binären Payload aus dem Cloud-Save des Spielers in die aktuelle Verse-Klassendefinition zu deserialisieren.
Wenn du eine Variable löschst, eine Property umbenennst oder einen Typ änderst (z. B. ein Feld von int in float), stößt der Deserializer auf einen Mismatch. Je nach Schweregrad der Änderung führt diese Schema-Divergenz zu einem von drei Fehlerzuständen (Failure-Modes):
- Silent Failures: Die Runtime weist den geänderten Properties Standardwerte zu, was den Fortschritt ohnehin löscht, aber ohne Absturz.
- State-Corruption: Die Anwendung liest ungültige Speicherzustände (Memory-States) oder stößt auf unerwartete Datenstrukturen, was an anderer Stelle zu Logikfehlern führt.
- Schwere Runtime-Crashes: Die Map lädt nicht oder die Verse-VM stoppt die Ausführung, weil sie die persistente Map nicht entpacken kann.
Wenn der Button „Clear Player Data“ in der Publisher-Konsole verschwindet, verlierst du die Möglichkeit, die Daten global zurückzusetzen. Wenn ein Entwickler grundlegende Änderungen an seinem Save-Data-Layout vornimmt, ist das Spiel kaputt.
Der manuelle Fix: Schema-Versionierung in Verse
Da du dich nicht darauf verlassen kannst, dass das Publishing-Portal von Epic die Daten löscht, musst du einen Fallback auf Softwareebene implementieren. Die Strategie ist einfach: Füge direkt einen Versions-Tracker in deine persistierbaren Klassen ein. Indem du die gespeicherte Version des Spielers beim Session-Start mit der aktiven Anwendungsversion vergleichst, kannst du veraltete Spielstände identifizieren und mit Standardwerten überschreiben.
Hier ist eine produktionsbereite Verse-Implementierung, die einen versionsgesteuerten Persistence-Layer einrichtet.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Simulation }
# A simple persistable class representing our player save schema.
# Note: Classes tagged with <persistable> can only contain certain types like int, float, string, and maps.
player_save_data := class<persistable>:
# The SchemaVersion tracks which generation of player data this struct belongs to.
SchemaVersion : int = 0
GoldCount : int = 0
XP : int = 0
HasCompletedTutorial : int = 0 # Using int as a boolean flag representation for older Verse constraints
# The persistence manager device handles loading, validating, and migrating player data.
persistence_manager_device := class(creative_device):
# By updating this value in the Editor, we force a schema migration next time the game runs.
@editable
TargetSchemaVersion : int = 2
# Map to store player data in memory
var PlayerDataMap : weak_map(player, player_save_data) = map{}
# Initialize the persistence logic for a joining player
InitializePlayer(Player : player) : void =
# Check if the player already has persistent data
if (ExistingData := PlayerDataMap[Player]):
# If the stored version is older than our target version, trigger a manual reset
if (ExistingData.SchemaVersion < TargetSchemaVersion):
Print("Data version mismatch. Local: {ExistingData.SchemaVersion}, Target: {TargetSchemaVersion}. Resetting player data.")
ResetPlayerData(Player)
else:
Print("Loaded existing player data. Version: {ExistingData.SchemaVersion}")
else:
# If no data exists, initialize a new record with the current version
Print("No player data found. Initializing new record.")
ResetPlayerData(Player)
# Re-initializes a player's record with default values at the current version
ResetPlayerData(Player : player) : void =
NewData := player_save_data:
SchemaVersion := TargetSchemaVersion
GoldCount := 0
XP := 0
HasCompletedTutorial := 0
# Save the freshly initialized data block to the persistence map
if (set PlayerDataMap[Player] = NewData):
Print("Successfully saved fresh persistent data block.")
Analyse des Versionierungs-Skripts
Dieses Pattern basiert auf einer frühzeitigen Validierung innerhalb der InitializePlayer-Methode. Die Schritt-für-Schritt-Logik sieht wie folgt aus:
- Der Entry-Check: Wenn ein Spieler beitritt, fragt das System die
PlayerDataMapnach einer vorhandenen Instanz vonplayer_save_dataab. - Die Versions-Prüfung: Wenn ein Save-State existiert, überprüft die Engine dessen
SchemaVersion-Property. - Der Migrations-Trigger: Wenn die
SchemaVersionniedriger ist als die@editable-PropertyTargetSchemaVersiondes Devices, ruft das SkriptResetPlayerData(Player)auf. - State-Überschreibung:
ResetPlayerDataerstellt eine neue Instanz vonplayer_save_datamit der neuenTargetSchemaVersionund aktualisiert die Map.
Indem du die TargetSchemaVersion in der Konfiguration deines Devices innerhalb von UEFN manuell erhöhst, erzwingst du einen gezielten Daten-Wipe für alle Spieler bei ihrer nächsten Session-Initialisierung. Dadurch wird der fehlende Epic-UI-Button irrelevant.
Schritt-für-Schritt-Testverfahren
Um dieses Migrations-Skript innerhalb deines Playtest-Zyklus sicher zu validieren, folge diesen Schritten nacheinander:
- Ziehe dein Custom-
persistence_manager_devicein das Viewport deines UEFN-Levels. - Setze im Details-Panel von UEFN die anfängliche
TargetSchemaVersionauf 1. - Starte eine Play-Session, sammle etwas Score oder Gold und beende das Spiel.
- Aktualisiere zurück im Editor das Feld
TargetSchemaVersionauf dem Device von 1 auf 2. - Starte die Session erneut. Beobachte die Ausgabe im Console-Log: 'Data version mismatch. Local: 1, Target: 2. Resetting player data.'
- Überprüfe, ob das Gold und der Spielfortschritt deines Spielers sauber zurückgesetzt wurden, ohne die Verse-VM zum Absturz zu bringen.
Lokale Entwicklungseinstellungen: Umgehen von Save-Files während der Iteration
Während die Runtime-Codeversionierung Probleme auf Live-Servern löst, sorgt sie beim lokalen Playtesting für unerwünschte Friction. Die Versionsnummer in UEFN jedes Mal manuell zu erhöhen, wenn du ein in Arbeit befindliches Feature änderst, ist mühsam. Um deinen Debugging-Prozess zu optimieren, kannst du die lokale Persistenz über deine Editor-Einstellungen komplett umgehen.
Folge diesen Schritten, um die lokale Persistenz zu deaktivieren:
- Navigiere in UEFN zu den Island Settings im Outliner-Panel.
- Suche nach dem Unterabschnitt User Options - Game Rules.
- Suche das Toggle Enable Persistence und deaktiviere es.
- Wenn du Multiplayer-Loops lokal testest, öffne die Editor Preferences, gehe zum Menü In-Game Play und suche das Kontrollkästchen Clear Local Saved Data On Launch. Wenn du dieses aktivierst, startet jede Simulation mit einem leeren Cache.
Lokale Bypasses simulieren jedoch keine Produktionsszenarien. Während die Iterationsgeschwindigkeit beim Testen mit deaktiviertem Persistence-Caching von 45 Sekunden auf unter 5 Sekunden sinken kann, musst du mindestens einen vollständigen Staging-Zyklus mit aktivierter Persistenz durchführen, bevor du live gehst. Hier können sich Verbindungsprobleme häufen. Lokale Playtest-Setups sind berüchtigt für Treiberkonflikte, wodurch Entwickler wertvolle Zeit verlieren, wenn sie Unreal Engine-Netzwerktreiber bei UEFN-Session-Launch-Timeouts diagnostizieren.
UEFNs integrierte Persistence-Limitations
Die Nutzung des nativen Persistence-Layers von Verse bringt erhebliche architektonische Einschränkungen mit sich. Selbst wenn die Optionen zum Löschen in der Cloud perfekt funktionieren, sind Entwickler an strenge Plattform-Limits gebunden:
- Storage-Budgets: Persistierbare Datenblöcke sind auf 12 KB pro Spieler und Session begrenzt. Das Überschreiten dieses Limits verhindert das Speichern des States.
- Nur primitive Typen: Du kannst keine Custom-Klassen serialisieren, die nicht als
<persistable>markiert sind, und du kannst keine Referenzen auf Game-Objects (wiecreative_deviceoder dynamische Actors) speichern. - Keine externen Abfragen: Das System von Epic ist eine komplette Blackbox. Du kannst Spielerdaten nicht über ein externes Dashboard abfragen, User-States auf Exploits prüfen oder Datenbanken dynamisch migrieren.
- Telemetry-Bottlenecks: Das Entwerfen komplexer Analytics-Dashboards in UEFN wird durch Einschränkungen bei der String-Länge stark ausgebremst. So kämpfen Entwickler beispielsweise ständig mit Restriktionen beim Knacken des UEFN-Analytics-Device-Event-Name-Limits von 32 Zeichen.
Für Gelegenheits-Minispiele sind diese Grenzen überschaubar. Aber für komplexe persistente RPGs, Live-Ops-Shooter oder plattformübergreifende Titel blockiert die ausschließliche Nutzung proprietärer Closed-Loop-Persistenz deinen Skalierungspfad.
Die Alternative des Architekten: Externe Datenbank-States mit horizOn
Um ein skalierbares Spiel zu entwickeln, das nicht durch plattformspezifische UI-Bugs oder proprietäre Datengrößen eingeschränkt ist, setzen Entwickler auf externe Backend-Architekturen. Anstatt Game-States in den lokalen Memory-Maps von Verse zu speichern, kann dein UEFN-Projekt über das http_client-Modul in Verse mit einer externen Datenbank kommunizieren.
Dieser Ansatz gibt dir die volle Kontrolle über den Player-Save-Cycle. Wenn du Player-Persistence-Daten löschen musst, musst du nicht nach einem fehlenden Button in einer Epic-Konsole suchen oder ein Code-Update aufspielen. Du kannst einfach ein administratives Datenbank-Skript ausführen oder auf einen einzigen Button in deinem Backend-Control-Panel klicken, um Spieler-Tabellen zu löschen, zu migrieren oder zu patchen.
Dies selbst zu bauen erfordert die Einrichtung von Load Balancern, Datenbank-Sharding und SSL-Zertifikatsverwaltung – problemlos 4 bis 6 Wochen Arbeit. Mit horizOn sind diese Backend-Dienste bereits vorkonfiguriert, sodass du dein Spiel veröffentlichen kannst, anstatt dich um deine Infrastruktur zu kümmern.
Indem du Player-States über horizOn leitest, wird dein Verse-Code zu einem einfachen Client, der States über saubere REST-Endpunkte liest und schreibt:
# Pseudocode showing HTTP-based state synchronization with [horizOn](https://horizon.pm)
sync_manager_device := class(creative_device):
# Send player progress to [horizOn](https://horizon.pm) endpoint
SavePlayerState(Player : player, SaveData : player_save_data) : void =
# In a real environment, you construct a JSON payload and dispatch it
# via the Verse http_client. This bypasses the 12KB local persistence limit.
RequestURL := "https://api.horizon.pm/v1/players/{GetPlayerID(Player)}/state"
Print("Dispatching persistent payload to [horizOn](https://horizon.pm) at: {RequestURL}")
# This keeps the server-side payload lightweight (under 240 bytes)
# while securing long-term storage off the Fortnite engine.
Dieses Setup entkoppelt deinen Gameplay-Loop vollständig von den physischen Backend-Bugs der Plattform. Wenn ein Patch deine lokale Save-Kompatibilität beeinträchtigt, kannst du eine gezielte Schema-Migration in deiner horizOn-Datenbank in Echtzeit auslösen – ganz ohne Unterbrechung des Live-Game-Servers. So vermeidest du das Risiko, Live-Server wegen geringfügiger struktureller Anpassungen lahmzulegen.
Best Practices für das Management der Game-State-Persistenz
Egal, ob du bei nativen Verse-Maps bleibst oder eine externe Server-Architektur nutzt: Die Aufrechterhaltung eines stabilen Persistenz-Lebenszyklus ist für Multiplayer-Spiele unerlässlich. Halte dich an diese Prinzipien, um Spieler-States zu schützen:
- Implementiere implizite Versionierung frühzeitig: Integriere immer einen
SchemaVersion-Integer in dein initiales Save-Data-Layout. Selbst wenn du nicht planst, dein Schema zu ändern, verhindert der Versions-Key von Tag eins an katastrophale Datenkorruption in der Zukunft. - Erzwinge State-Validierungsregeln: Validiere beim Lesen des geladenen States die Wertebereiche jedes Attributs. Wenn die gespeicherte Goldanzahl eines Spielers negativ ist oder das strukturelle Limit deiner Map überschreitet, setze diese spezifische Property zurück, um Economy-Exploits zu verhindern.
- Minimiere die Speicherhäufigkeit: Das Schreiben in die Cloud ist teuer. Statt die Datenbank bei jedem eingesammelten Coin zu aktualisieren, solltest du deine Schreibvorgänge bündeln. Triggere Speichervorgänge bei wichtigen Meilensteinen wie dem Match-Abschluss, einem Spieler-Level-up oder wenn der Spieler die Session verlässt.
- Erstelle saubere Fallback-Strukturen: Erlaube niemals, dass ein fehlgeschlagener Lesevorgang einen Spieler daran hindert, das Spiel zu betreten. Wenn der Deserialisierungsprozess des Persistence-Payloads fehlschlägt, weiche sofort auf eine Standard-State-Struktur aus.
- Entkopple Gameplay-Code von der Storage-Logik: Halte deine Storage-Abfragen in einem dedizierten Persistence-Manager-Device isoliert. Deine Waffen-, Progressions- und UI-Devices sollten dieses Manager-Device abfragen, anstatt direkte Aufrufe an die Data-Map zu tätigen.
Nächste Schritte
Sich bei kritischen State-Migrationen auf das Epic-Konsolen-UI zu verlassen, ist ein riskantes Design-Pattern. Wenn die Option „Clear Player Data“ fehlschlägt, dient die Runtime-Versionierung in Verse als deine erste Verteidigungslinie. Wenn dein Spiel jedoch hochfrequente Speichervorgänge, komplexe Telemetrie oder vollständige administrative Kontrolle erfordert, ist die Migration deiner Datenbank auf ein dediziertes Backend die ultimative Lösung.
Bereit, dein Multiplayer-Backend zu skalieren? Teste horizOn kostenlos oder wirf einen Blick in die API-Docs.