ue-serialization-savegames

Use when implementing save/load systems, player progress persistence, or data serialization in Unreal Engine. Triggers on: save game, USaveGame, FArchive, serialization, SaveGameToSlot, config, persist data, save file, load game. See references/save-system-architecture.md for full slot management an

By quodsoler · 789 installs

npx skills add quodsoler/unreal-engine-skills --skill ue-serialization-savegames

Source repository · Upstream listing

UE Serialization & Save Games Target engine: UE 5.8 . APIs below are verified against the 5.8 headers; older forms are listed under "Deprecated — do not use". This skill covers persisting game state: USaveGame objects through UGameplayStatics , the platform ISaveGameSystem , FArchive binary serialization, UPROPERTY(SaveGame) actor snapshots, save versioning, and config/settings storage. Build.cs modules: Core , CoreUObject , Engine ; add DeveloperSettings to PublicDependencyModuleNames for UDeveloperSettings , and Json + JsonUtilities to PrivateDependencyModuleNames for FJsonObjectConverter . Context Read .agents/ue project context.md if it exists (module names, conventions, enabled plugins, GAS/networking setup). Do not stop if it is missing. Identify the area from the request and the codebase. Ask only when two plausible readings would produce different code. Request is about… Go to A save object, slot names, save/load/delete [USaveGame and the Slot API]( usavegame and the slot api) Not blocking the game thread, callbacks [Async Save and Load]( async save and load) Per player saves, settings profiles, split screen [ULocalPlayerSaveGame]( ulocalplayersavegame) Raw bytes, FArchive , memory buffers [FArchive and Binary Serialization]( farchive and binary serialization) Snapshotting actors, world state, transforms [Actor State with UPROPERTY(SaveGame)]( actor state with upropertysavegame) A USTRUCT needing its own Serialize ; old saves breaking after a patch [USTRUCT Custom Serialization]( ustruct custom serialization), [Versioning]( versioning) Console/cloud storage, slot enumeration, save UI [ISaveGameSystem and Platform Storage]( isavegamesystem and platform storage) Project settings, user options, .ini , JSON, compression [Config, Settings, and File Formats]( config settings and file formats) Slot manager subsystem, metadata, thumbnails, checksums, encryption [Save system architecture](references/save system architecture.md) USaveGame and the Slot API USaveGame is an abstract UObject declared in GameFramework/SaveGame.h:23 . Subclass it, add UPROPERTY fields, and route everything through UGameplayStatics . UGameplayStatics::SaveGameToSlot writes every non transient UPROPERTY — it does not filter on the SaveGame flag ( Kismet/GameplayStatics.h:1159 ). Mark fields Transient to exclude them. The SaveGame specifier only matters for archives with ArIsSaveGame set — see [Actor State]( actor state with upropertysavegame). Slot functions, all static on UGameplayStatics ( Kismet/GameplayStatics.h ): Call Signature CreateSaveGameObject USaveGame (TSubclassOf<USaveGame SaveGameClass) ( :1124 ) SaveGameToSlot bool (USaveGame , const FString& SlotName, const int32 UserIndex) ( :1167 ) LoadGameFromSlot USaveGame (const FString& SlotName, const int32 UserIndex) ( :1211 ) DoesSaveGameExist bool (const FString& SlotName, const int32 UserIndex) ( :1175 ) DeleteGameInSlot bool (const FString& SlotName, const int32 UserIndex) ( :1231 ) SaveGameToMemory bool (USaveGame , TArray<uint8 & OutSaveData) ( :1134 ) LoadGameFromMemory USaveGame (const TArray<uint8 & InSaveData) ( :1182 ) SaveDataToSlot bool (const TArray<uint8 &, const FString& SlotName, const int32 UserIndex) ( :1143 ) LoadDataFromSlot bool (TArray<uint8 &, const FString& SlotName, const int32 UserIndex) ( :1191 ) Async Save and Load The async entry points take plain (non dynamic) delegates, so the bound function must not be a UFUNCTION : DECLARE DELEGATE ThreeParams(FAsyncSaveGameToSlotDelegate, const FString&, const int32, bool) ( GameplayStatics.h:44 ) DECLARE DELEGATE ThreeParams(FAsyncLoadGameFromSlotDelegate, const FString&, const int32, USaveGame ) ( GameplayStatics.h:47 ) CreateLambda works when there is no owning UObject to keep alive; capture by value, because the call returns before the write finishes. The delegate runs on the game thread ( check(IsInGameThread()) , GameplayStatics.cpp:2417 ), and runs synchronously inside the call when the slot name is empty or serialization fails ( :2424 ). SaveGameToMemory itself always runs on the game thread ( :2410 ); only the platform write is async. ULocalPlayerSaveGame ULocalPlayerSaveGame is declared in GameFramework/SaveGame.h:47 (there is no LocalPlayerSaveGame.h ). It binds a save to one local player, resolves the platform user index automatically, and provides versioning hooks for subclasses to override. GetLatestDataVersion() returns the current schema number. HandlePostLoad() compares it with GetSavedDataVersion() , which is the value stored at the last save, and migrates old data. HandlePreSave() sanitises fields before they are written, and HandlePostSave(bool bSuccess) is where save results arrive. Call Super:: in each. A complete subclass with a two step migration is in [references/save system architecture.md](references/save system architecture.md 7 per player saves and thumbnails). The native delegate overload takes a ULocalPlayer ; the APlayerController overload takes the dynamic FOnLocalPlayerSaveGameLoaded instead. Other verified members ( GameFramework/SaveGame.h:50 224 ): CreateNewSaveGameForLocalPlayer , GetLocalPlayerController , GetLocalPlayer , SetLocalPlayer , GetPlatformUserId , GetPlatformUserIndex , GetSaveSlotName , SetSaveSlotName , GetSavedDataVersion , GetInvalidDataVersion , WasLoaded , IsSaveInProgress , WasLastSaveSuccessful , WasSaveRequested , InitializeSaveGame , ResetToDefault . FArchive and Binary Serialization FArchive ( Serialization/Archive.h ) is bidirectional: one operator<< body handles both read and write. FMemoryWriter / FMemoryReader move bytes in and out of a TArray<uint8 . Both take bIsPersistent ( MemoryWriter.h:26 , MemoryReader.h:52 ); pass true so the archive behaves like an on disk write rather than a transient in memory one. Overriding virtual void UMyObject::Serialize(FArchive& Ar) gives byte level control on a UObject ; call Super::Serialize(Ar) first so the tagged property block is written. Define a free FArchive& operator<<(FArchive& Ar, FMyCustomData& Data) to make a plain struct archive serializable. FBufferArchive ( Serialization/BufferArchive.h:47 ) derives from both the memory writer and TArray<uint8 , so the archive is the buffer. Actor State with UPROPERTY(SaveGame) UPROPERTY(SaveGame) sets CPF SaveGame ( UObject/ObjectMacros.h:458 , specifier at :1194 ), which is only honoured by archives with ArIsSaveGame set. That is how you snapshot live actors: wrap a memory archive in FObjectAndNameAsStringProxyArchive ( Serialization/ObjectAndNameAsStringProxyArchive.h:21 ) so UObject and FName references survive as strings instead of load order dependent indices. FObjectAndNameAsStringProxyArchive also exposes bResolveRedirectors and bResolveCoreRedirects (both default false ); set them when assets may have been renamed between builds. FNameAsStringProxyArchive ( Serialization/NameAsStringProxyArchive.h:11 ) handles FName only. Full world capture pipeline, per actor FGuid identity, the actor save interface and respawn ordering: [Save system architecture](references/save system architecture.md). USTRUCT Custom Serialization A USTRUCT hook is bool Serialize(FArchive& Ar) plus a TStructOpsTypeTraits specialization with WithSerializer = true . Without the traits specialization the function is never called — the engine gates on TStructOpsTypeTraits<CppStruct ::WithSerializer ( UObject/Class.h:1307 ; trait declared in UObject/StructOpsTypeTraits.h:24 ). Return true to mean "fully handled; skip the default tagged property path". Related traits on the same base ( StructOpsTypeTraits.h ): WithPostSerialize , WithStructuredSerializer , WithSerializeFromMismatchedTag , WithNetSerializer , WithIdentical . Versioning Explicit version field — the simplest option for a USaveGame written through UGameplayStatics , because the field is just another UPROPERTY : FCustomVersion — per archive versions ( Serialization/CustomVersion.h : FCustomVersion at :39 , FCustomVersionRegistration at :211 ). SaveGameToSlot / SaveGameToMemory write every registered custom version into the file header and restore them on load ( GameplayStatics.cpp:233,207 ), so CustomVer works inside a USaveGame . A bare FMemoryWriter / FMemoryReader pair carries no versions: serialize Ar.GetCustomVersions() yourself ( Archive.h:555,562 ) or CustomVer returns 1 on load. ISaveGameSystem and Platform Storage UGameplayStatics routes through the platform's ISaveGameSystem ( Engine/Public/SaveGameSystem.h:19 ), reached via IPlatformFeaturesModule::Get().GetSaveGameSystem() ( Engine/Public/PlatformFeatures.h:41 ). Use it directly only for capabilities UGameplayStatics does not expose — enumerating slots, native save UI, or multi user checks. Interface members ( SaveGameSystem.h:34 58 ): PlatformHasNativeUI , DoesSaveSystemSupportMultipleUsers , DoesSaveGameExist , DoesSaveGameExistWithResult , GetSaveGameNames , SaveGame , LoadGame , DeleteGame . ISaveGameSystem also declares the async set DoesSaveGameExistAsync , SaveGameAsync , LoadGameAsync , LoadGameIfExistsAsync , DeleteGameAsync , GetSaveGameNamesAsync and InitAsync , all keyed by FPlatformUserId ( :78 97 ); FBaseAsyncSaveGameSystem ( :178 ) is the helper base that implements them on UE::Tasks . Never build save paths by hand: FGenericSaveGameSystem::GetSaveGamePath ( :169 ) is the desktop fallback only, and console and cloud backends ignore the filesystem entirely. Config, Settings, and File Formats Settings are a separate channel from save slots: .ini for options and tuning, USaveGame for progress. Need API Header Project Settings page, Config=Game , DefaultConfig UDeveloperSettings + GetDefault<T () ; override GetContainerName / GetCategoryName / GetSectionName Engine/DeveloperSettings.h:23,31 35 (module DeveloperSettings ) Player options (resolution, audio, keybinds) UGameUserSettings::ApplySettings(bool bCheckForCommandLineOverrides) , SaveSettings() , LoadSettings(bool bForceReload) GameFramework/GameUserSettings.h:49,311,307 Write/read an object's UPROPERTY(Config) fields UObject::SaveConfig() , LoadConfig() , OverrideConfigSection(FString&) UObject/Object.h:1283,1389,1370 Raw .ini keys GConfig GetString/SetString(Section, Key, Value, GGameIni) , GConfig Flush(bRemoveFromCache, GGameIni) Misc/ConfigCacheIni.h:1402,1408,1395 ; GGameIni at CoreGlobals.h:439 USTRUCT to/from JSON text; byte array to/from a file (tooling only) FJsonObjectConverter::UStructToJsonObjectString / JsonObjectStringToUStruct ; FFileHelper::SaveArrayToFile / LoadFileToArray under FPaths::ProjectSavedDir() JsonObjectConverter.h:156,313 ; Misc/FileHelper.h:185,79 ; Misc/Paths.h:290 Section [/Script/ModuleName.ClassName] maps to the class CDO; register a UGameUserSettings subclass with GameUserSettingsClassName=/Script/MyGame.MyGameUserSettings under [/Script/Engine.Engine] in DefaultEngine.ini . Compression uses FCompression::CompressMemoryBound / CompressMemory / UncompressMemory ( Misc/Compression.h:73,108,143 ) with an FName format — NAME Zlib , NAME Oodle , NAME Gzip , NAME LZ4 ( UObject/UnrealNames.inl:210 214 ) — and must store the uncompressed size alongside the blob; FArchiveSaveCompressedProxy(TArray<uint8 &, FName, ECompressionFlags) ( Serialization/ArchiveSaveCompressedProxy.h:28 , Flush() at :36 ) and FArchiveLoadCompressedProxy ( ArchiveLoadCompressedProxy.h:22 ) wrap the same thing as archives. Worked