Terug naar Blog

Oplossen van de ontbrekende Fortnite Creative Clear Player Data-optie: Verse-resets en custom backend state recovery

Gepubliceerd op 19 juli 2026
Oplossen van de ontbrekende Fortnite Creative Clear Player Data-optie: Verse-resets en custom backend state recovery

Kort samengevat

Wanneer de 'Clear Player Data'-optie in Fortnite Creative/UEFN verdwijnt, kunnen creators geen verouderde player persistence-data meer via de UI wissen. Dit artikel legt uit hoe je een robuust schema versioning-systeem in Verse bouwt om handmatig verouderde save-data te resetten bij sessie-initialisatie. Daarnaast worden lokale editor-instellingen besproken om persistence te omzeilen tijdens het testen. Tot slot wordt getoond hoe een externe backend zoals horizOn helpt om platformbeperkingen te omzeilen en volledige controle over de game state te krijgen.

Je nieuwste Fortnite Creative- of UEFN-map is klaar. Je hebt je economie gerefactord, je class-systeem opnieuw ontworpen en je gameplay loop geoptimaliseerd. Maar wanneer je wilt publiceren, stuit je op een engine-brekende nachtmerrie: de "Press Triangle to Clear Player Data"-optie is verdwenen uit het publicatie-bevestigingsscherm. Je zit vast. Je spelers laden je map in met verouderde persistence-data, wat leidt tot directe stack overflows, gebroken quest lines en game-crashing state mismatches.

Dit specifieke probleem teistert creators op de developer-forums van Epic, waar het plotseling ontbreken van de resetknop ervoor zorgt dat er geen UI-gebaseerd mechanisme meer is om verouderde production states op te schonen. Wanneer het schema van je player persistence-data veranderd is, probeert de backend van Epic de oude geserialiseerde data op je nieuwe structuren te mappen. Als je code niet is ontworpen om deze mismatched eigenschappen elegant af te handelen, brick je de game state voor 100% van je terugkerende spelers.

In deze tutorial duiken we diep in de technische workarounds voor deze bug. We laten je zien hoe je een robuust schema versioning-systeem bouwt in Verse, hoe je lokale UEFN-instellingen configureert om teststates te wissen, en we onderzoeken hoe custom externe backends je kunnen bevrijden van de beperkingen van closed-platform persistence.

De root cause: waarom schema's falen bij versie-upgrades

In Fortnite Creative en Unreal Editor for Fortnite (UEFN) wordt persistente data opgeslagen in de database-infrastructuur van Epic met behulp van weak_map-structuren. Een weak_map koppelt een player-instance aan een custom class die is gemarkeerd met de <persistable>-specifier. In tegenstelling tot traditionele SQL- of documentdatabases voert de storage-laag van Epic geen formele migratie-pipeline uit wanneer je een update uitbrengt. In plaats daarvan probeert deze de binaire payload die in de cloud-save van de speler staat, te deserialiseren naar de huidige Verse class-definitie.

Wanneer je een variabele verwijdert, een property hernoemt of een type wijzigt (bijvoorbeeld het veranderen van een veld van int naar float), stuit de deserializer op een mismatch. Afhankelijk van de ernst van de wijziging resulteert deze schema-divergentie in een van de volgende drie failure modes:

  • Silent Failures: De runtime wijst default-waarden toe aan de gewijzigde properties, waardoor de voortgang alsnog wordt gewist, maar zonder te crashen.
  • State Corruption: De applicatie leest ongeldige memory states in of stuit op onverwachte datastructuren, wat elders logic errors triggert.
  • Severe Runtime Crashes: De map laadt niet, of de Verse VM halt de uitvoering omdat deze de persistente map niet kan uitpakken.

Wanneer de 'Clear Player Data'-knop in de publisher console verdwijnt, verlies je de mogelijkheid om wereldwijd met een schone lei te beginnen. Als een developer fundamentele wijzigingen aanbrengt in de lay-out van zijn save-data, is de game broken.

De handmatige fix: Schema versioning in Verse

Aangezien je er niet op kunt rekenen dat de publishing portal van Epic de data wist, moet je een fallback op softwareniveau implementeren. De strategie is eenvoudig: introduceer een version tracker direct in je persistable classes. Door de opgeslagen versie van de speler bij de start van de sessie te vergelijken met de actieve applicatieversie, kun je verouderde saves identificeren en deze overschrijven met default-waarden.

Hier is een production-ready Verse-implementatie die een persistence-laag met versiebeheer opzet.

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.")

Deconstructie van het versioning-script

Dit patroon leunt op vroege validatie binnen de InitializePlayer-methode. De stapsgewijze logica verloopt als volgt:

  1. De entrycheck: Wanneer een speler joint, doorzoekt het systeem de PlayerDataMap naar een bestaande instance van player_save_data.
  2. De versie-evaluatie: Als er een save state bestaat, controleert de engine de SchemaVersion-property.
  3. De migratietrigger: Als SchemaVersion lager is dan de @editable-property TargetSchemaVersion van het device, roept het script ResetPlayerData(Player) aan.
  4. State-overschrijving: ResetPlayerData bouwt een nieuwe instance van player_save_data op, ingesteld op de nieuwe TargetSchemaVersion, en updatet de map.

Door handmatig de TargetSchemaVersion te verhogen in de configuratie van je device binnen UEFN, forceer je een datawipe voor alle spelers bij hun volgende sessie-initialisatie. Hierdoor wordt de ontbrekende Epic UI-knop irrelevant.

Stapsgewijze testprocedure

Volg deze stappen om dit migratiescript veilig te valideren binnen je playtest-cyclus:

  1. Sleep je custom persistence_manager_device in de viewport van je UEFN-level.
  2. Stel in het UEFN Details-paneel de initiële TargetSchemaVersion in op 1.
  3. Start een speelsessie, verzamel wat score of goud, en beëindig de game.
  4. Ga terug naar de editor en pas het TargetSchemaVersion-veld op het device aan van 1 naar 2.
  5. Start de sessie opnieuw. Let op de console log-output: 'Data version mismatch. Local: 1, Target: 2. Resetting player data.'
  6. Controleer of het goud en de progressie van je speler netjes zijn gereset zonder dat de Verse VM crasht.

Lokale development-instellingen: Save-bestanden omzeilen tijdens iteratie

Hoewel runtime-codeversioning problemen op live servers oplost, zorgt het voor ongewenste wrijving tijdens lokaal playtesten. Het handmatig ophogen van het versienummer in UEFN bij elke wijziging aan een feature in ontwikkeling is omslachtig. Om je debugging-proces te stroomlijnen, kun je lokale persistence volledig omzeilen via je editor-instellingen.

Volg deze stappen om lokale persistence uit te schakelen:

  1. Navigeer in UEFN naar de Island Settings in het Outliner-paneel.
  2. Zoek naar de subsectie User Options - Game Rules.
  3. Zoek de toggle Enable Persistence en schakel deze uit.
  4. Als je multiplayer loops lokaal test, open dan de Editor Preferences, ga naar het menu In-Game Play en zoek naar het selectievakje Clear Local Saved Data On Launch. Door dit in te schakelen, zorg je ervoor dat elke simulatie start met een lege cache.

Lokale bypasses simuleren echter geen productiescenario's. Hoewel de iteratiesnelheid tijdens het testen kan dalen van 45 seconden naar minder dan 5 seconden met uitgeschakelde persistence caching, moet je ten minste één volledige staging-cyclus met ingeschakelde persistence uitvoeren voordat je live gaat. Dit is waar verbindingsproblemen kunnen stapelen. Lokale playtest-setups staan bekend om driver-conflicten, waardoor developers kostbare tijd verliezen aan het diagnostiseren van Unreal Engine-netwerkdrivers tijdens UEFN session launch timeouts.

UEFN's Built-in Persistence Limitations

Het gebruik van de native persistence-laag van Verse brengt aanzienlijke architecturale beperkingen met zich mee. Zelfs als de cloud-clear-opties perfect werken, zijn developers gebonden aan strikte platformlimieten:

  • Storage Budgets: Persistable datablokken zijn gecapped op 12KB per speler per sessie. Het overschrijden van deze limiet voorkomt dat de state wordt opgeslagen.
  • Alleen primitieve types: Je kunt geen custom classes serialiseren die niet als <persistable> zijn gemarkeerd, en je kunt geen referenties naar game-objects (zoals creative_device of dynamische actors) opslaan.
  • Geen externe querying: Het systeem van Epic is een complete black box. Je kunt player-data niet opvragen via een extern dashboard, gebruikersstatussen controleren op exploits of databases dynamisch migreren.
  • Telemetry-bottlenecks: Het ontwerpen van complexe analytics-dashboards binnen UEFN wordt zwaar gelimiteerd door limieten op de stringlengte. Developers worstelen bijvoorbeeld voortdurend met beperkingen bij het doorbreken van de UEFN analytics device event name-limiet van 32 karakters.

Voor simpele minigames zijn deze grenzen beheersbaar. Maar voor complexe persistente RPG's, live-ops shooters of cross-platform titels blokkeert het uitsluitend vertrouwen op closed-loop persistence je groeipad.

Het alternatief van de architect: Externe database-states met horizOn

Om een schaalbare game te bouwen die niet wordt beperkt door platformspecifieke UI-bugs of platformspecifieke datalimieten, stappen developers over op externe backend-architecturen. In plaats van game states op te slaan in de lokale memory maps van Verse, kan je UEFN-project een HTTP-verbinding opzetten met een externe database via de http_client-module in Verse.

Deze aanpak geeft je volledige controle over de player save-cyclus. Wanneer je player persistence-data moet wissen, hoef je niet te zoeken naar een ontbrekende knop op een Epic-console of een code-update te pushen. Je kunt eenvoudig een administratief databasescript uitvoeren of op één knop klikken in je backend-controlpanel om spelerstabellen te wissen, te migreren of te patchen.

Als je dit zelf wilt bouwen, moet je load balancers, database sharding en SSL-certificaatbeheer opzetten — al snel 4 tot 6 weken werk. Met horizOn zijn deze backend-services vooraf geconfigureerd, zodat je je kunt focussen op het shippen van je game in plaats van je infrastructuur.

Door spelerstates via horizOn te routeren, wordt je Verse-code een eenvoudige client die de state leest en schrijft via heldere REST-endpoints:

# 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.

Deze opzet ontkoppelt je gameplay loop volledig van de fysieke backend-bugs van het platform. Als een patch de compatibiliteit van je lokale saves verbreekt, kun je in real-time een gerichte schema-migratie activeren op je horizOn-database, zonder enige onderbreking voor de live gameserver. Je vermijdt het risico dat je live servers platlegt vanwege kleine structurele aanpassingen.

Best practices voor het beheren van game state-persistence

Of je nu kiest voor native Verse maps of gebruikmaakt van een externe serverarchitectuur, het behouden van een stabiele persistence-lifecycle is essentieel voor multiplayer games. Houd je aan deze principes om spelerstates te beschermen:

  1. Implementeer vroegtijdig impliciet versiebeheer: Voeg altijd een SchemaVersion-integer toe aan je initiële lay-out van save-data. Zelfs als je niet van plan bent je schema aan te passen, voorkomt een versiesleutel vanaf dag één catastrofale datacorruptie in de toekomst.
  2. Handhaaf regels voor state-validatie: Valideer de bereiken van elk attribuut bij het inlezen van de geladen state. Als het opgeslagen goud van een speler negatief is of de structurele limiet van je map overschrijdt, reset dan die specifieke property om economy exploits te voorkomen.
  3. Minimaliseer de schrijffrequentie: Schrijven naar de cloud is kostbaar. In plaats van de database bij te werken bij elke opgepakte munt, kun je writes beter batchen. Trigger saves tijdens belangrijke mijlpalen, zoals de voltooiing van een match, een level-up van de speler of wanneer de speler de sessie verlaat.
  4. Bouw schone fallback-structs: Zorg dat een mislukte read er nooit voor zorgt dat een speler de game niet meer in kan. Als de persistence payload niet kan worden gedeserialiseerd, val dan direct terug op een standaard (default) state-structuur.
  5. Ontkoppel gameplay-code van storage-logica: Houd je storage-queries geïsoleerd in een dedicated persistence manager device. Je wapens, progressie en UI-devices moeten deze manager aanroepen in plaats van rechtstreekse calls te maken naar de data map.

Volgende stappen

Vertrouwen op de console UI van Epic om kritieke state-migraties te beheren is een riskant design pattern. Wanneer de 'Clear Player Data'-optie faalt, dient runtime-versioning binnen Verse als je eerste verdedigingslinie. Echter, als je game behoefte heeft aan een hoge schrijffrequentie, complexe telemetry of volledige administratieve controle, dan is het migreren van je database naar een dedicated backend de ultieme oplossing.

Klaar om je multiplayer backend op te schalen? Probeer horizOn gratis of bekijk de API-docs.


Bron: clear players data button in creative 1