Ue Specifiers
fjz13/UnrealSpecifiers
A skill your agent uses when writing, editing, reviewing, validating, or explaining Unreal Engine C++ reflected declarations: UCLASS, USTRUCT, UENUM, UINTERFACE, UPROPERTY, UFUNCTION, UPARAM, UMETA…
A skill your agent uses when implementing save/load, player progress persistence, slot management, or binary serialization in Unreal Engine C++.
$ npx skills add quodsoler/unreal-engine-skills --skill ue-serialization-savegames -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install quodsoler/unreal-engine-skills ue-serialization-savegames --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/ue-serialization-savegames .claude/skills/ue-serialization-savegames && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "ue-serialization-savegames" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-serialization-savegames into .claude/skills/ue-serialization-savegames/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-serialization-savegames", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-serialization-savegamesType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add quodsoler/unreal-engine-skills --skill ue-serialization-savegames -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install quodsoler/unreal-engine-skills ue-serialization-savegames --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/ue-serialization-savegames .agents/skills/ue-serialization-savegames && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "ue-serialization-savegames" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-serialization-savegames into .agents/skills/ue-serialization-savegames/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-serialization-savegames", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add quodsoler/unreal-engine-skills --skill ue-serialization-savegames -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install quodsoler/unreal-engine-skills ue-serialization-savegames --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/ue-serialization-savegames .cursor/skills/ue-serialization-savegames && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "ue-serialization-savegames" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-serialization-savegames into .cursor/skills/ue-serialization-savegames/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-serialization-savegames", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/quodsoler/unreal-engine-skills.git --path skills/ue-serialization-savegames--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add quodsoler/unreal-engine-skills --skill ue-serialization-savegames -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install quodsoler/unreal-engine-skills ue-serialization-savegames --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/ue-serialization-savegames .gemini/skills/ue-serialization-savegames && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "ue-serialization-savegames" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-serialization-savegames into .gemini/skills/ue-serialization-savegames/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-serialization-savegames", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install quodsoler/unreal-engine-skills ue-serialization-savegamesInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add quodsoler/unreal-engine-skills --skill ue-serialization-savegames -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/ue-serialization-savegames .github/skills/ue-serialization-savegames && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "ue-serialization-savegames" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-serialization-savegames into .github/skills/ue-serialization-savegames/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-serialization-savegames", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add quodsoler/unreal-engine-skills --skill ue-serialization-savegames -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install quodsoler/unreal-engine-skills ue-serialization-savegames --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/quodsoler/unreal-engine-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/ue-serialization-savegames .opencode/skills/ue-serialization-savegames && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "ue-serialization-savegames" agent skill from https://github.com/quodsoler/unreal-engine-skills/tree/main/skills/ue-serialization-savegames into .opencode/skills/ue-serialization-savegames/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "ue-serialization-savegames", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
ue-serialization-savegamesA skill your agent uses when implementing save/load, player progress persistence, slot management, or binary serialization in Unreal Engine C++.
Ue Serialization Savegames is an agent skill from quodsoler/unreal-engine-skills. Use when implementing save/load, player progress persistence, slot management, or binary serialization in Unreal Engine C++. Also use when the user mentions 'save game', 'USaveGame', 'SaveGameToSlot', 'AsyncSaveGameToSlot', 'LoadGameFromSlot', 'DoesSaveGameExist', 'DeleteGameInSlot', 'ULocalPlayerSaveGame', 'UPROPERTY(SaveGame)', 'FArchive', 'FMemoryWriter', 'FCustomVersion', 'save versioning', 'migrate old saves', 'save file corrupted', 'UDeveloperSettings', or 'GConfig'. For asset references, see…
Its SKILL.md is about 6.6k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files, including reference files (for example `references/save-system-architecture.md`).
It sits in Game Development. It works with C++ and Unreal Engine. The repository describes itself as: Unreal Engine C++ skills for AI coding agents. 27 skills covering gameplay, rendering, networking, animation, and more. Works with Claude Code, Cursor, Windsurf, and any agent… The licence is MIT.
Read from SKILL.md and the folder at commit f3742d7. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are cpp).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Ue Serialization Savegames loads about 6.6k tokens when it runs, and up to ~15k if it reads all its reference files. Until then it costs about 166 tokens; SKILL.md has 1,432 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from quodsoler/unreal-engine-skills at commit f3742d7, republished under its MIT licence (© quodsoler). 1,432 words, ~6,562 tokens.
.claude/skills/ue-serialization-savegames/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.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.
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 |
| Not blocking the game thread, callbacks | Async Save and Load |
| Per-player saves, settings profiles, split-screen | ULocalPlayerSaveGame |
Raw bytes, FArchive, memory buffers | FArchive and Binary Serialization |
| Snapshotting actors, world state, transforms | Actor State with UPROPERTY(SaveGame) |
A USTRUCT needing its own Serialize; old saves breaking after a patch | USTRUCT Custom Serialization, Versioning |
| Console/cloud storage, slot enumeration, save UI | ISaveGameSystem and Platform Storage |
Project settings, user options, .ini, JSON, compression | Config, Settings, and File Formats |
| Slot manager subsystem, metadata, thumbnails, checksums, encryption | Save system architecture |
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.
// MySaveGame.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/SaveGame.h"
#include "UObject/SoftObjectPath.h"
#include "MySaveGame.generated.h"
USTRUCT(BlueprintType)
struct FMyInventoryItem
{
GENERATED_BODY()
UPROPERTY(SaveGame) FName ItemId;
UPROPERTY(SaveGame) int32 Quantity = 0;
};
UCLASS(BlueprintType)
class MYGAME_API UMySaveGame : public USaveGame
{
GENERATED_BODY()
public:
UPROPERTY(SaveGame) int32 SaveVersion = 0; // bump alongside MySaveStage::Latest
UPROPERTY(SaveGame) float PlayerHealth = 100.f;
UPROPERTY(SaveGame) FVector LastCheckpoint = FVector::ZeroVector;
UPROPERTY(SaveGame) TArray<FMyInventoryItem> InventoryItems;
UPROPERTY(SaveGame) TMap<FName, int32> AbilityLevels;
/** Store asset references as paths; a hard pointer cannot round-trip through a file. */
UPROPERTY(SaveGame) FSoftObjectPath LastEquippedWeapon;
/** Excluded from SaveGameToSlot because it is Transient. */
UPROPERTY(Transient) float RuntimeOnlyScratch = 0.f;
};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) |
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)// MySaveSubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "MySaveSubsystem.generated.h"
class UMySaveGame; class USaveGame;
UCLASS()
class MYGAME_API UMySaveSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
void RequestSave(float PlayerHealth);
void RequestLoad();
private:
void HandleSaveComplete(const FString& SlotName, const int32 UserIndex, bool bSuccess);
void HandleLoadComplete(const FString& SlotName, const int32 UserIndex, USaveGame* LoadedSave);
void RunMigrations(UMySaveGame* Save);
UPROPERTY()
TObjectPtr<UMySaveGame> CurrentSave = nullptr;
bool bSaveInProgress = false;
};// MySaveSubsystem.cpp
#include "MySaveSubsystem.h"
#include "MySaveGame.h"
#include "Kismet/GameplayStatics.h"
static const FString GMySaveSlot = TEXT("MainSave");
static constexpr int32 GMyUserIndex = 0; // see ULocalPlayerSaveGame for per-user consoles
void UMySaveSubsystem::RequestSave(float PlayerHealth)
{
if (bSaveInProgress) { return; } // overlapping writes to one slot can truncate the file
if (!CurrentSave)
{
CurrentSave = Cast<UMySaveGame>(
UGameplayStatics::CreateSaveGameObject(UMySaveGame::StaticClass()));
}
if (!CurrentSave) { return; }
CurrentSave->PlayerHealth = PlayerHealth;
CurrentSave->SaveVersion = MySaveStage::Latest; // in-memory data is current; see Versioning
// Synchronous form blocks the game thread until the platform write finishes:
// UGameplayStatics::SaveGameToSlot(CurrentSave, GMySaveSlot, GMyUserIndex);
bSaveInProgress = true;
UGameplayStatics::AsyncSaveGameToSlot(CurrentSave, GMySaveSlot, GMyUserIndex,
FAsyncSaveGameToSlotDelegate::CreateUObject(this, &UMySaveSubsystem::HandleSaveComplete));
}
void UMySaveSubsystem::HandleSaveComplete(const FString& SlotName, const int32 UserIndex, bool bSuccess)
{
bSaveInProgress = false;
UE_LOG(LogMyGame, Log, TEXT("Save '%s' (user %d) ok=%d"), *SlotName, UserIndex, bSuccess ? 1 : 0);
}
void UMySaveSubsystem::RequestLoad()
{
UGameplayStatics::AsyncLoadGameFromSlot(GMySaveSlot, GMyUserIndex,
FAsyncLoadGameFromSlotDelegate::CreateUObject(this, &UMySaveSubsystem::HandleLoadComplete));
}
void UMySaveSubsystem::HandleLoadComplete(const FString& SlotName, const int32 UserIndex, USaveGame* LoadedSave)
{
CurrentSave = Cast<UMySaveGame>(LoadedSave); // null when the slot is missing or unreadable
if (!CurrentSave)
{
CurrentSave = Cast<UMySaveGame>(
UGameplayStatics::CreateSaveGameObject(UMySaveGame::StaticClass()));
return;
}
RunMigrations(CurrentSave);
}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 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.
The native delegate overload takes a ULocalPlayer*; the APlayerController* overload takes the dynamic FOnLocalPlayerSaveGameLoaded instead.
// Synchronous: returns null only for invalid parameters, otherwise creates a fresh instance.
UMyLocalPlayerSave* Save = Cast<UMyLocalPlayerSave>(
ULocalPlayerSaveGame::LoadOrCreateSaveGameForLocalPlayer(
UMyLocalPlayerSave::StaticClass(), PlayerController, TEXT("PlayerSlot0")));
// Asynchronous, native delegate: pass the ULocalPlayer, not the controller.
const ULocalPlayer* LocalPlayer = PlayerController ? PlayerController->GetLocalPlayer() : nullptr;
const bool bScheduled = ULocalPlayerSaveGame::AsyncLoadOrCreateSaveGameForLocalPlayer(
UMyLocalPlayerSave::StaticClass(), LocalPlayer, TEXT("PlayerSlot0"),
FOnLocalPlayerSaveGameLoadedNative::CreateUObject(this, &AMyPlayerController::HandleSaveLoaded));
// Writing back; results arrive through HandlePostSave, not the return value.
Save->AsyncSaveGameToSlotForLocalPlayer(); // bool: the save was requested
Save->SaveGameToSlotForLocalPlayer(); // synchronousOther verified members (GameFramework/SaveGame.h:50-224): CreateNewSaveGameForLocalPlayer, GetLocalPlayerController, GetLocalPlayer, SetLocalPlayer, GetPlatformUserId, GetPlatformUserIndex, GetSaveSlotName, SetSaveSlotName, GetSavedDataVersion, GetInvalidDataVersion, WasLoaded, IsSaveInProgress, WasLastSaveSuccessful, WasSaveRequested, InitializeSaveGame, ResetToDefault.
FArchive (Serialization/Archive.h) is bidirectional: one operator<< body handles both read and write.
Ar.IsLoading() // Archive.h:272
Ar.IsSaving() // Archive.h:284
Ar.IsError() // Archive.h:398 — check after every block
Ar.Tell() // Archive.h:185 — int64 position
Ar.IsSaveGame() // Archive.h:659 — reads the ArIsSaveGame field
Ar.ArIsSaveGame = true; // Archive.h:942 — public bitfield, no setter exists
Ar.UsingCustomVersion(Guid); // Archive.h:2006
Ar.CustomVer(Guid); // Archive.h:269 — int32FMemoryWriter / 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.
#include "Serialization/MemoryWriter.h"
#include "Serialization/MemoryReader.h"
bool UMyObject::WriteBlob(TArray<uint8>& OutBytes)
{
FMemoryWriter Writer(OutBytes, /*bIsPersistent=*/true);
int32 Magic = 0x4D595347; // 'MYSG'
int32 Version = 2;
Writer << Magic << Version << BinaryBlob;
return !Writer.IsError();
}
bool UMyObject::ReadBlob(const TArray<uint8>& InBytes)
{
FMemoryReader Reader(InBytes, /*bIsPersistent=*/true);
int32 Magic = 0;
int32 Version = 0;
Reader << Magic << Version;
if (Reader.IsError() || Magic != 0x4D595347 || Version < 1) { return false; }
Reader << BinaryBlob;
return !Reader.IsError();
}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.
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.
// MyActorRecord.cpp — include MemoryWriter.h, MemoryReader.h,
// ObjectAndNameAsStringProxyArchive.h and GameFramework/Actor.h.
void FMyActorRecord::SaveActor(AActor* Actor)
{
if (!Actor) { return; }
ActorClass = Actor->GetClass();
ActorTransform = Actor->GetActorTransform();
ByteData.Reset();
FMemoryWriter MemWriter(ByteData, /*bIsPersistent=*/true);
FObjectAndNameAsStringProxyArchive Ar(MemWriter, /*bInLoadIfFindFails=*/true);
Ar.ArIsSaveGame = true; // only UPROPERTY(SaveGame) fields are written
Actor->Serialize(Ar);
}
void FMyActorRecord::RestoreActor(AActor* Actor) const
{
if (!Actor || ByteData.Num() == 0) { return; }
Actor->SetActorTransform(ActorTransform);
FMemoryReader MemReader(ByteData, /*bIsPersistent=*/true);
FObjectAndNameAsStringProxyArchive Ar(MemReader, /*bInLoadIfFindFails=*/true);
Ar.ArIsSaveGame = true;
Actor->Serialize(Ar);
}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.
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".
// MyStatBlock.h — include "CoreMinimal.h" then "MyStatBlock.generated.h".
USTRUCT(BlueprintType)
struct MYGAME_API FMyStatBlock
{
GENERATED_BODY()
UPROPERTY(SaveGame) float HP = 100.f;
UPROPERTY(SaveGame) float Stamina = 100.f;
bool Serialize(FArchive& Ar);
};
template<>
struct TStructOpsTypeTraits<FMyStatBlock> : public TStructOpsTypeTraitsBase2<FMyStatBlock>
{
enum
{
WithSerializer = true
};
};// MyStatBlock.cpp — include "MyStatBlock.h" and "MySaveVersion.h".
bool FMyStatBlock::Serialize(FArchive& Ar)
{
Ar.UsingCustomVersion(FMySaveVersion::GUID);
if (Ar.CustomVer(FMySaveVersion::GUID) < FMySaveVersion::SplitStamina)
{
float LegacyHealth = 0.f; // the old layout stored a single float
Ar << LegacyHealth;
HP = LegacyHealth;
Stamina = 100.f;
}
else
{
Ar << HP << Stamina;
}
return true;
}Related traits on the same base (StructOpsTypeTraits.h): WithPostSerialize, WithStructuredSerializer, WithSerializeFromMismatchedTag, WithNetSerializer, WithIdentical.
Explicit version field — the simplest option for a USaveGame written through UGameplayStatics, because the field is just another UPROPERTY:
namespace MySaveStage
{
constexpr int32 Initial = 0, AddedInventory = 1, SoftRefForWeapon = 2, Latest = SoftRefForWeapon;
}
void UMySaveSubsystem::RunMigrations(UMySaveGame* Save)
{
if (!Save || Save->SaveVersion == MySaveStage::Latest) { return; }
if (Save->SaveVersion < MySaveStage::AddedInventory) { Save->InventoryItems.Reset(); }
if (Save->SaveVersion < MySaveStage::SoftRefForWeapon) { Save->LastEquippedWeapon.Reset(); }
Save->SaveVersion = MySaveStage::Latest; // stamp only after every step ran
}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.
// MySaveVersion.h — include "CoreMinimal.h" and "Misc/Guid.h".
struct FMySaveVersion
{
enum Type { Initial = 0, AddedQuestData = 1, SplitStamina = 2, VersionPlusOne, Latest = VersionPlusOne - 1 };
/** Generate once with FGuid::NewGuid(), then hardcode forever. */
static const FGuid GUID;
};
// MySaveVersion.cpp — include "MySaveVersion.h" and "Serialization/CustomVersion.h".
const FGuid FMySaveVersion::GUID(0xA1B2C3D4, 0xE5F60718, 0x293A4B5C, 0x6D7E8F90);
// Registers the version with every archive for the module's lifetime.
FCustomVersionRegistration GRegisterMySaveVersion(
FMySaveVersion::GUID, FMySaveVersion::Latest, TEXT("MySaveVersion"));
// Inside any Serialize(); UsingCustomVersion is required when saving (CustomVer asserts
// otherwise, Archive.cpp:646) and a harmless no-op when loading (Archive.cpp:631).
// CoreData and QuestData are UPROPERTY members of UMyObject.
void UMyObject::SerializeVersioned(FArchive& Ar)
{
Ar.UsingCustomVersion(FMySaveVersion::GUID);
const int32 Version = Ar.CustomVer(FMySaveVersion::GUID);
Ar << CoreData;
if (Version >= FMySaveVersion::AddedQuestData) { Ar << QuestData; }
else if (Ar.IsLoading()) { QuestData.Reset(); }
}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.
#include "SaveGameSystem.h"
#include "PlatformFeatures.h"
ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem();
if (!SaveSystem) { return; }
const bool bPerUser = SaveSystem->DoesSaveSystemSupportMultipleUsers();
TArray<FString> FoundSlots;
SaveSystem->GetSaveGameNames(FoundSlots, /*UserIndex=*/0);
const ISaveGameSystem::ESaveExistsResult R = SaveSystem->DoesSaveGameExistWithResult(TEXT("MainSave"), 0);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.
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 UDeveloperSettings class, GConfig calls, JSON round-trip, compression, checksum (FCrc::MemCrc32, Misc/Crc.h:29) and FAES encryption code: Save system architecture.
| Do not emit | Use in 5.8 | Source |
|---|---|---|
#include "GameFramework/LocalPlayerSaveGame.h" | #include "GameFramework/SaveGame.h" | ULocalPlayerSaveGame declared at GameFramework/SaveGame.h:47; no such header exists |
Ar.SetIsSaveGame(true) | Ar.ArIsSaveGame = true; | public bitfield at Serialization/Archive.h:942; no setter in the header |
void FMyStruct::Serialize(FArchive&) | bool Serialize(FArchive&) + TStructOpsTypeTraits with WithSerializer = true | gate at UObject/Class.h:1307, trait at UObject/StructOpsTypeTraits.h:24 |
COMPRESS_ZLIB | NAME_Zlib | COMPRESS_ZLIB_DEPRECATED in Misc/CompressionFlags.h:18 |
FCompression::GetCompressionFormatFromDeprecatedFlags(Flags) | pass NAME_Zlib / NAME_Oodle directly | UE_DEPRECATED(5.5) in Misc/Compression.h:173 |
UGameUserSettings::WindowPosX / WindowPosY | the WindowPositions array | UE_DEPRECATED(5.6) in GameFramework/GameUserSettings.h:480,485 |
UFUNCTION() on an AsyncSaveGameToSlot callback | a plain member function | FAsyncSaveGameToSlotDelegate is DECLARE_DELEGATE_ThreeParams (GameplayStatics.h:44), not dynamic |
ISteamRemoteStorage, SteamRemoteStorage(), IOnlineTitleFileInterface | ISaveGameSystem via IPlatformFeaturesModule::Get().GetSaveGameSystem() | none of these appear in any 5.8 engine header |
Assuming UPROPERTY(SaveGame) filters SaveGameToSlot: it does not — SaveGameToSlot writes all non-transient properties (GameplayStatics.h:1159). Use Transient to exclude a field; SaveGame matters only for archives with ArIsSaveGame = true.
Serializing an actor without the proxy archive: a bare FMemoryWriter stores UObject and FName references as indices that do not survive a restart. Always wrap it in FObjectAndNameAsStringProxyArchive.
Passing an APlayerController* with the native local-player delegate: the FOnLocalPlayerSaveGameLoadedNative overload takes const ULocalPlayer*. Call PlayerController->GetLocalPlayer() first.
Getting versioning backwards: set SaveVersion = Latest only after every migration step has run (and on every save, so fresh saves are not migrated next load). Ar.UsingCustomVersion() is mandatory on the saving archive — CustomVer check()s otherwise — while on a loading archive it is a no-op and CustomVer returns the file's version, or -1 if the archive holds none (Archive.cpp:631,646-648).
Hardcoding Saved/SaveGames/*.sav paths: consoles and cloud backends never touch that directory; use UGameplayStatics or ISaveGameSystem.
ue-cpp-foundations — UPROPERTY/USTRUCT specifiers, UObject lifetime, subsystem typesue-data-assets-tables — FSoftObjectPath, FPrimaryAssetId, async loading behind saved referencesue-gameplay-framework — UGameInstance as the save manager host, GameMode-driven autosave pointsue-world-level-streaming — persisting streamed-level and World Partition actor stateue-async-threading — running serialization or compression off the game threadue-networking-replication — server-authoritative saves versus client-local profiles© quodsoler, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file (references) in skills/ue-serialization-savegames of quodsoler/unreal-engine-skills.
Open the folder on GitHubat commit f3742d7
Ue Serialization Savegames next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Ue Serialization Savegames this skillquodsoler/unreal-engine-skills | 360 | — | ~6.6k | Automated safety check: Pass | MIT | |
| Ue Specifiersfjz13/UnrealSpecifiers | 1.1k | — | ~1.4k | Automated safety check: Pass | Custom licence | |
| Unreal Engine MCPEpicGames/unreal-engine-skills-for-claude-code-plugin | 332 | — | ~1.8k | Automated safety check: Pass | MIT | |
| Unreal Live CodingItalink/UnrealClientProtocol | 127 | — | ~1k | Automated safety check: Pass | MIT | |
| Unreal Cpp Gameplayukanwat/overtime | 387 | 1 repos | ~1.5k | Automated safety check: Pass | Apache-2.0 | |
| Unreal Enhanced Inputukanwat/overtime | 387 | 1 repos | ~1.5k | Automated safety check: Pass | Apache-2.0 |
fjz13/UnrealSpecifiers
A skill your agent uses when writing, editing, reviewing, validating, or explaining Unreal Engine C++ reflected declarations: UCLASS, USTRUCT, UENUM, UINTERFACE, UPROPERTY, UFUNCTION, UPARAM, UMETA…
EpicGames/unreal-engine-skills-for-claude-code-plugin
Drives a live Unreal Editor through the unreal-mcp server: spawn actors, edit Blueprints, materials and sequences, change properties and trigger Live Coding.
Italink/UnrealClientProtocol
Trigger Live Coding compilation via UCP. An agent skill from Italink/UnrealClientProtocol.
ukanwat/overtime
Write Unreal Engine 5 C++ gameplay code: the UCLASS/UPROPERTY/UFUNCTION reflection macros, the Gameplay Framework (GameMode, Pawn, Character, PlayerController, Actor components), and the module…
ukanwat/overtime
Set up player input in Unreal Engine 5 with Enhanced Input: Input Actions, Input Mapping Contexts, modifiers and triggers, adding the mapping context, and binding actions by ETriggerEvent.
ukanwat/overtime
Create and control VFX in Unreal Engine 5 with Niagara: systems and emitters, modules and the spawn/update stages, exposed User parameters, and spawning or driving effects from Blueprints or C++.
quodsoler/unreal-engine-skills
Unreal Engine C++ guidance for AActor and UActorComponent: lifecycle hooks, spawning, component creation and attachment, ticking, interfaces and composition.
quodsoler/unreal-engine-skills
Guides Unreal Engine C++ AI work across AI controllers, behavior trees, blackboards, perception, navmesh pathfinding, EQS and Smart Objects.
quodsoler/unreal-engine-skills
Reference for writing and debugging Unreal Engine 5.8 animation C++: AnimInstance and proxy, montages, notifies, curves, blend spaces, state machines, linked layers and root motion.
quodsoler/unreal-engine-skills
Picks and applies the right Unreal Engine C++ concurrency API, from UE::Tasks and FPipe to ParallelFor, FRunnable, locks and timers, with game-thread safety rules.
quodsoler/unreal-engine-skills
A skill your agent uses when playing, mixing or debugging sound in Unreal Engine — one-shot SFX, looping ambience, music, dialogue or procedural audio.
quodsoler/unreal-engine-skills
Reference for exposing Unreal Engine C++ to Blueprint and calling Blueprint from C++, covering UFUNCTION and UPROPERTY specifiers, latent and async nodes, and interfaces.
Works with
Categories
A skill your agent uses when implementing save/load, player progress persistence, slot management, or binary serialization in Unreal Engine C++. Ue Serialization Savegames is an agent skill from quodsoler/unreal-engine-skills. Use when implementing save/load, player progress persistence, slot management, or binary serialization in Unreal Engine C++.
Ue Serialization Savegames fits situations like: implementing save/load; player progress persistence; slot management; binary serialization in Unreal Engine C++.
Run `npx skills add quodsoler/unreal-engine-skills --skill ue-serialization-savegames -a claude-code`. Or copy the skill folder (skills/ue-serialization-savegames in quodsoler/unreal-engine-skills) into .claude/skills/ue-serialization-savegames in your project. Claude Code loads it when a task matches its description.
Run `npx skills add quodsoler/unreal-engine-skills --skill ue-serialization-savegames -a codex`. Or copy the skill folder (skills/ue-serialization-savegames in quodsoler/unreal-engine-skills) into .agents/skills/ue-serialization-savegames in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add quodsoler/unreal-engine-skills --skill ue-serialization-savegames -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/ue-serialization-savegames, .gemini/skills/ue-serialization-savegames, .github/skills/ue-serialization-savegames and .opencode/skills/ue-serialization-savegames in your project.
SKILL.md names no scripts, command-line tools or credentials: Ue Serialization Savegames is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Ue Serialization Savegames is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.6k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 8.2k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Ue Serialization Savegames: Ue Specifiers (fjz13/UnrealSpecifiers, 1.1k stars), Unreal Engine MCP (EpicGames/unreal-engine-skills-for-claude-code-plugin, 332 stars), Unreal Live Coding (Italink/UnrealClientProtocol, 127 stars) and Unreal Cpp Gameplay (ukanwat/overtime, 387 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
quodsoler (a GitHub user) maintains it in quodsoler/unreal-engine-skills, which has 360 GitHub stars. The repository holds 31 skills in this directory. The repository was last updated on September 28, 2026.
Source: quodsoler/unreal-engine-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.