Rozwiązanie problemu z brakującą opcją Clear Player Data w Fortnite Creative: resety w Verse i odzyskiwanie stanu w niestandardowym backendzie
W skrócie
Artykuł omawia problem zniknięcia opcji „Clear Player Data” z konsoli publikacyjnej Epic w Fortnite Creative oraz przedstawia techniczne metody radzenia sobie z uszkodzeniami stanów graczy po zmianach schematów danych. Autorzy prezentują implementację systemu wersjonowania danych bezpośrednio w języku Verse jako lokalną metodę awaryjną oraz konfigurację edytora w celach testowych. Jako długofalowe i bardziej skalowalne rozwiązanie sugerują przeniesienie persystencji na zewnętrzną bazę danych zintegrowaną za pomocą REST API i usługi horizOn.
Twoja najnowsza mapa w Fortnite Creative lub UEFN jest już gotowa. Zrefaktoryzowałeś ekonomię, przeprojektowałeś system klas i zoptymalizowałeś gameplay loop. Jednak gdy przechodzisz do publikacji, napotykasz koszmar blokujący silnik: opcja „Press Triangle to Clear Player Data” zniknęła z ekranu potwierdzenia publikacji. Jesteś w pułapce. Gracze wchodzą na Twoją mapę ze starymi danymi persystencji, co powoduje natychmiastowe stack overflowy, zepsute linie zadań i błędy spójności stanu (state mismatches), które crashują całą grę.
Ten konkretny problem nęka twórców na forach deweloperskich Epic, gdzie nagły brak przycisku resetu uniemożliwia wyczyszczenie nieaktualnych stanów produkcyjnych z poziomu interfejsu użytkownika. Kiedy zmienia się schemat (schema) danych persystencji gracza, backend Epic próbuje zmapować stare zserializowane dane na Twoje nowe struktury. Jeśli Twój kod nie jest zaprojektowany tak, aby bezproblemowo obsługiwać te niezgodne właściwości, całkowicie zablokujesz (bricked) stan gry dla 100% powracających graczy.
W tym poradniku szczegółowo omówimy techniczne obejścia tego błędu. Pokażemy Ci, jak zbudować solidny system wersjonowania schematu (schema versioning) w Verse, skonfigurować lokalne ustawienia UEFN w celu czyszczenia stanów testowych oraz sprawdzimy, jak niestandardowy, zewnętrzny backend może uwolnić Cię od ograniczeń persystencji zamkniętej platformy.
The Root Cause: Dlaczego schematy (schemas) ulegają awarii przy aktualizacjach wersji
W Fortnite Creative i Unreal Editor for Fortnite (UEFN) trwałe dane są przechowywane w infrastrukturze bazodanowej Epic przy użyciu struktur weak_map. Konstrukcja weak_map wiąże instancję gracza z niestandardową klasą oznaczoną specyfikatorem <persistable>. W przeciwieństwie do tradycyjnych baz SQL lub dokumentowych, warstwa przechowywania danych Epic nie uruchamia formalnego procesu migracji (migration pipeline) po wydaniu aktualizacji. Zamiast tego próbuje zdeserializować dowolny binarny payload istniejący w zapisie chmurowym gracza do aktualnej definji klasy Verse.
Gdy usuniesz zmienną, zmienisz nazwę właściwości lub zmienisz typ (np. zmieniając pole z int na float), deserializator napotka niezgodność. W zależności od powagi zmiany, ta rozbieżność w schemacie prowadzi do jednego z trzech trybów awarii:
- Ciche awarie (Silent Failures): Środowisko uruchomieniowe przypisuje domyślne wartości do zmodyfikowanych właściwości, co i tak powoduje utratę postępu, ale bez wywoływania crashu.
- Uszkodzenie stanu (State Corruption): Aplikacja odczytuje nieprawidłowe stany pamięci lub napotyka nieoczekiwane struktury danych, co wyzwala błędy logiczne w innych miejscach.
- Poważne crashe środowiska uruchomieniowego (Severe Runtime Crashes): Mapa nie ładuje się lub maszyna wirtualna Verse (Verse VM) zatrzymuje wykonywanie, ponieważ nie może rozpakować trwałej mapy (persistent map).
Gdy przycisk „Clear Player Data” w konsoli wydawcy znika, tracisz możliwość globalnego wyczyszczenia danych i rozpoczęcia od nowa. Jeśli deweloper wprowadzi fundamentalne zmiany w układzie danych zapisu (save data layout), gra ulega uszkodzeniu.
The Manual Fix: Wersjonowanie schematu (Schema Versioning) wewnątrz Verse
Ponieważ nie możesz liczyć na to, że portal publikacyjny Epic wyczyści dane, musisz wdrożyć rozwiązanie programistyczne. Strategia jest prosta: wprowadź wskaźnik wersji (version tracker) bezpośrednio do swoich klas typu persistable. Porównując zapisaną wersję gracza z aktywną wersją aplikacji na początku sesji, możesz zidentyfikować nieaktualne zapisy i nadpisać je wartościami domyślnymi.
Oto gotowa do użycia produkcyjnego implementacja w Verse, która konfiguruje wersjonowaną warstwę persystencji.
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.")
Szczegółowe omówienie skryptu wersjonowania
Ten wzorzec opiera się na wczesnej walidacji wewnątrz metody InitializePlayer. Logika krok po kroku wygląda następująco:
- Wstępna weryfikacja (Entry Check): Gdy gracz dołącza, system odpytuje
PlayerDataMapo istniejącą instancjęplayer_save_data. - Ocena wersji (Version Assessment): Jeśli stan zapisu istnieje, silnik sprawdza jego właściwość
SchemaVersion. - Wyzwalacz migracji (Migration Trigger): Jeśli
SchemaVersionjest niższa niż właściwość@editableurządzeniaTargetSchemaVersion, skrypt wywołujeResetPlayerData(Player). - Nadpisanie stanu (State Overwrite):
ResetPlayerDatatworzy nową instancjęplayer_save_dataustawioną na nową wartośćTargetSchemaVersioni aktualizuje mapę.
Ręcznie zwiększając TargetSchemaVersion w konfiguracji urządzenia w UEFN, wymuszasz czyszczenie danych u wszystkich graczy przy ich kolejnej inicjalizacji sesji, co sprawia, że brakujący przycisk w UI od Epic przestaje mieć znaczenie.
Procedura testowa krok po kroku
Aby bezpiecznie zweryfikować ten skrypt migracyjny w cyklu testów rozgrywki (playtests), wykonaj następujące kroki:
- Przeciągnij swoje niestandardowe urządzenie
persistence_manager_devicedo rzutni poziomu (viewportu) w UEFN. - W panelu UEFN Details ustaw początkową wartość
TargetSchemaVersionna 1. - Uruchom sesję gry, zdobądź trochę punktów lub złota, a następnie zakończ grę.
- Po powrocie do edytora zaktualizuj pole
TargetSchemaVersionna urządzeniu z 1 na 2. - Uruchom sesję ponownie. Obserwuj dane wyjściowe w logach konsoli: 'Data version mismatch. Local: 1, Target: 2. Resetting player data.'
- Upewnij się, że złoto i postęp gracza zostały zresetowane bez wywołania crashu maszyny wirtualnej Verse (Verse VM).
Ustawienia lokalnego środowiska deweloperskiego: Omijanie plików zapisu podczas iteracji
Chociaż wersjonowanie kodu w czasie wykonywania (runtime) rozwiązuje problemy na serwerach produkcyjnych (live), dodaje niepożądane utrudnienia podczas lokalnych testów. Ręczne podbijanie numeru wersji w UEFN za każdym razem, gdy modyfikujesz rozwijaną funkcję, jest uciążliwe. Aby usprawnić proces debugowania, możesz całkowicie pominąć lokalną persystencję za pomocą ustawień edytora.
Wykonaj poniższe kroki, aby wyłączyć lokalną persystencję:
- W UEFN przejdź do Island Settings w panelu Outliner.
- Wyszukaj podsekcję User Options - Game Rules.
- Znajdź przełącznik Enable Persistence i go wyłącz.
- Jeśli testujesz lokalnie pętle multiplayer, otwórz Editor Preferences, przejdź do menu In-Game Play i znajdź pole wyboru Clear Local Saved Data On Launch. Zaznaczenie tej opcji gwarantuje, że każda symulacja rozpocznie się z czystym cachem.
Ograniczenia wbudowanej persystencji w UEFN
Korzystanie z natywnej warstwy persystencji Verse wiąże się ze znacznymi ograniczeniami architektonicznymi. Nawet gdy opcje czyszczenia chmury działają bezbłędnie, deweloperzy są ograniczeni restrykcyjnymi limitami platformy:
Dla prostych minigier te granice są akceptowalne. Jednak w przypadku złożonych, trwałych RPG, strzelanek typu live-ops czy tytułów międzyplatformowych, poleganie wyłącznie na własnościowej persystencji w zamkniętej pętli blokuje Twoją ścieżkę skalowania.
# This keeps the server-side payload lightweight (under 240 bytes)
# while securing long-term storage off the Fortnite engine.
## Najlepsze praktyki (Best Practices) zarządzania persystencją stanu gry
Niezależnie od tego, czy pozostajesz przy natywnych mapach Verse, czy korzystasz z zewnętrznej architektury serwerowej, utrzymanie stabilnego cyklu życia persystencji jest kluczowe dla gier typu multiplayer. Przestrzegaj poniższych zasad, aby chronić stany graczy:
1. **Wdróż niejawne wersjonowanie na wczesnym etapie (Implement Implicit Versioning Early):** Zawsze uwzględniaj liczbę całkowitą `SchemaVersion` w początkowym układzie danych zapisu. Nawet jeśli nie planujesz modyfikować schematu, posiadanie klucza wersji od pierwszego dnia zapobiega katastrofalnemu uszkodzeniu danych w przyszłości.
2. **Wymuszaj zasady walidacji stanu (Enforce State Validation Rules):** Podczas odczytu załadowanego stanu sprawdzaj zakresy każdego atrybutu. Jeśli zapisana liczba złota gracza jest ujemna lub przekracza limit strukturalny Twojej mapy, zresetuj tę konkretną właściwość, aby zapobiec exploitom ekonomii.
3. **Minimalizuj częstotliwość zapisu (Minimize Save Frequency):** Zapisywanie w chmurze jest kosztowne. Zamiast aktualizować bazę danych przy każdym podniesieniu monety, grupuj (batch) operacje zapisu. Wyzwalaj zapis w kluczowych momentach (milestones), takich jak ukończenie meczu, awans gracza na wyższy poziom lub gdy gracz opuszcza sesję.
4. **Twórz czyste struktury awaryjne (Construct Clean Fallback Structs):** Nigdy nie pozwól, aby nieudany odczyt uniemożliwił graczowi wejście do gry. Jeśli deserializacja payloadu persystencji nie powiedzie się, natychmiast przywróć domyślną strukturę stanu (fallback).
5. **Oddziel kod rozgrywki od logiki przechowywania (Decouple Gameplay Code from Storage Logic):** Przechowuj zapytania dotyczące zapisu w odizolowanym, dedykowanym urządzeniu zarządzającym persystencją. Twoje urządzenia obsługujące broń, progresję i interfejs użytkownika powinny odpytywać ten manager zamiast wykonywać bezpośrednie połączenia do mapy danych.
## Kolejne kroki
Poleganie na interfejsie konsoli Epic w celu zarządzania krytycznymi migracjami stanu to ryzykowny wzorzec projektowy. Kiedy zawodzi opcja „Clear Player Data”, wersjonowanie w czasie wykonywania wewnątrz Verse służy jako pierwsza linia obrony. Jeśli jednak Twoja gra wymaga częstych zapisów, złożonej telemetryki lub pełnej kontroli administracyjnej, ostatecznym rozwiązaniem jest migracja bazy danych do dedykowanego backendu.
---
Źródło: [clear players data button in creative 1](https://forums.unrealengine.com/t/clear-players-data-button-in-creative-1/2735737)