Professional Save/Load System for Unreal Engine
SmartSave is a complete, production-ready save/load system for Unreal Engine 5. It replaces the engine's basic USaveGame with a professional solution that handles everything from simple property serialization to encrypted, compressed, multi-slot saves with automatic backups.
Up to 999 manual save slots, plus dedicated quick-save and rotating auto-save slots. Each slot stores metadata, timestamps, play time, and a screenshot thumbnail.
Mark any property with UPROPERTY(SaveGame) and it's automatically serialized. No boilerplate, no manual byte packing. Supports all UE property types.
Save and load on background threads to avoid hitches. Async Blueprint nodes provide proper Completed/Failed execution pins for clean visual scripting.
Encrypt save files with a passphrase to prevent players from editing save data. Set the passphrase at runtime via code — no config files needed.
Reduce save file size by 60-80%. LZ4 offers near-instant compression; Zlib provides maximum compression ratio for shipping builds.
Rotating .bak files protect against corruption. If a save is corrupted, SmartSave automatically recovers from the most recent backup.
When you update your game and the save format changes, the migration system automatically upgrades old save files — like database migrations.
Local disk by default. Drop in a custom backend for Steam Cloud, Epic Online Services, or any other storage. Template implementations included.
A ready-made UI panel with slot list, thumbnails, save/load/delete buttons, and keyboard support. Show it with a single function call.
Built-in profiling reports show per-actor serialization time, compression overhead, I/O duration, and data sizes. Identify bottlenecks instantly.
SmartSave is built on Unreal Engine's GameInstance Subsystem pattern. The central USaveSubsystem lives on the Game Instance, meaning it persists across level transitions and is automatically available everywhere.
SmartSavePlugin folder into your project's Plugins/ directory:
YourProject/
Plugins/
SmartSavePlugin/
SmartSavePlugin.uplugin
Source/
Resources/
Content/
.uproject file and select "Generate Visual Studio project files" (or use your preferred IDE).
Module Dependencies
SmartSave only depends on core engine modules (Core, CoreUObject, Engine, InputCore, ImageWrapper, RenderCore, RHI, Slate, SlateCore). No external plugins required.
If you want to use SmartSave classes directly in your C++ code, add the module dependency to your game's .Build.cs:
PublicDependencyModuleNames.AddRange(new string[]
{
"Core", "CoreUObject", "Engine",
"SmartSaveCore" // ← Add this
});
Blueprint-Only Projects
If you work exclusively in Blueprint, you do not need to modify any Build.cs files. The plugin exposes all functionality via Blueprint nodes automatically.
This section gets you up and running in under 5 minutes. By the end, you'll be able to save and load your game state.
Slot Index to 0 and Slot Name to "My First Save".
Slot Index to 0.
// Get the subsystem (available anywhere you have a UWorld)
USaveSubsystem* SaveSys = GetGameInstance()->GetSubsystem<USaveSubsystem>();
// Save to slot 0
ESmartSaveResult Result = SaveSys->SaveToSlot(0, TEXT("My First Save"));
// Load from slot 0
ESmartSaveResult Result = SaveSys->LoadFromSlot(0);
SmartSave automatically serializes any property marked with the SaveGame specifier. No additional code needed.
UCLASS()
class AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
// These are automatically saved and loaded:
UPROPERTY(SaveGame, EditAnywhere, BlueprintReadWrite)
float Health = 100.0f;
UPROPERTY(SaveGame, EditAnywhere, BlueprintReadWrite)
int32 Score = 0;
UPROPERTY(SaveGame, EditAnywhere, BlueprintReadWrite)
FString PlayerName = TEXT("Hero");
UPROPERTY(SaveGame, EditAnywhere, BlueprintReadWrite)
TArray<FString> Inventory;
// This is NOT saved (no SaveGame specifier):
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float TemporaryBuff = 0.0f;
};
That's it!
Any actor with SaveGame properties that also implements ISaveableInterface or has a USaveableComponent will automatically have those properties saved and restored. Transforms (position, rotation, scale) are included by default.
The easiest way to make any actor saveable — no C++, no interfaces. Just add a component.
Saveable component (USaveableComponent).
Save Transform — saves position, rotation, scale (default: on)Save Properties — saves all SaveGame variables (default: on)Enabled — include in save operations (default: on)On Pre Save — prepare data before savingOn Post Load — rebuild state after loadingOn Modify Save Data — add custom key-value dataOn Load Data — read custom key-value dataRuntime-Spawned Actors
The Saveable component automatically generates a persistent GUID for runtime-spawned actors. This means actors created with SpawnActor are fully supported — they will be respawned on load at the correct position with all their saved properties.
For maximum control, implement ISaveableInterface on your actor class. This gives you access to lifecycle hooks for custom save/load logic.
// MyActor.h
#include "SaveableInterface.h"
UCLASS()
class AMyActor : public AActor, public ISaveableInterface
{
GENERATED_BODY()
public:
// Automatically saved properties
UPROPERTY(SaveGame)
float Health = 100.0f;
UPROPERTY(SaveGame)
int32 Score = 0;
// ISaveableInterface overrides
virtual void OnPreSave_Implementation() override;
virtual void OnPostLoad_Implementation() override;
virtual void ModifySaveData_Implementation(FSaveDataRecord& Record) override;
virtual void OnLoadData_Implementation(const FSaveDataRecord& Record) override;
virtual bool ShouldSave_Implementation() const override;
};
// MyActor.cpp
void AMyActor::OnPreSave_Implementation()
{
// Prepare data before saving (e.g., cache computed values)
}
void AMyActor::OnPostLoad_Implementation()
{
// Rebuild runtime state after loading
// (e.g., refresh UI, restart timers, update materials)
}
void AMyActor::ModifySaveData_Implementation(FSaveDataRecord& Record)
{
// Add custom key-value pairs (for data that isn't a UPROPERTY)
Record.CustomData.Add(TEXT("Difficulty"), TEXT("Hard"));
Record.CustomData.Add(TEXT("QuestStep"), TEXT("3"));
}
void AMyActor::OnLoadData_Implementation(const FSaveDataRecord& Record)
{
// Read back custom data
if (const FString* Diff = Record.CustomData.Find(TEXT("Difficulty")))
{
CurrentDifficulty = *Diff;
}
}
bool AMyActor::ShouldSave_Implementation() const
{
// Return false to exclude this actor from saving
return !bIsTemporary;
}
| Method | When Called | Purpose |
|---|---|---|
OnPreSave() |
Before serialization | Prepare/cache data for saving |
OnPostLoad() |
After properties are restored | Rebuild runtime state, refresh UI |
ModifySaveData() |
During save, after OnPreSave | Add custom key-value pairs to save record |
OnLoadData() |
During load, after properties restored | Read back custom key-value pairs |
ShouldSave() |
Before saving each actor | Return false to skip this actor |
GetSaveActorId() |
During save/load matching | Custom unique ID generation |
SmartSave is configured via Project Settings → Plugins → SmartSave. All settings have sensible defaults — you can ship without changing anything.
| Setting | Default | Description |
|---|---|---|
MaxSaveSlots | 20 | Maximum number of manual save slots (1–999). |
QuickSaveSlotIndex | 1000 | Slot reserved for quick-save. Set to -1 to disable. |
| Setting | Default | Description |
|---|---|---|
bAutoSaveEnabled | true | Enable automatic periodic saving. |
AutoSaveIntervalSeconds | 300.0 | Seconds between auto-saves (min: 10). |
MaxAutoSaveSlots | 3 | Number of rotating auto-save slots (1–99). |
AutoSaveStartIndex | 2000 | Starting slot index for auto-saves. |
| Setting | Default | Description |
|---|---|---|
bCompressionEnabled | true | Compress save data to reduce file size. |
CompressionAlgorithm | LZ4 | Algorithm: None, LZ4 (fast), or Zlib (smaller). |
CurrentSaveVersion | 1 | Your game's save version. Increment when format changes. |
| Setting | Default | Description |
|---|---|---|
bCaptureScreenshot | true | Capture a screenshot when saving. |
ScreenshotWidth | 320 | Thumbnail width in pixels (32–1920). |
ScreenshotHeight | 180 | Thumbnail height in pixels (32–1080). |
ScreenshotQuality | 85 | JPEG quality (1–100). |
| Setting | Default | Description |
|---|---|---|
SaveDirectoryOverride | (empty) | Custom save directory. Empty = default platform path. |
SaveFileExtension | smartsav | File extension for save files. |
| Setting | Default | Description |
|---|---|---|
bEncryptionEnabled | false | Encrypt save files with AES-256. |
EncryptionPassphrase | (default key) | Passphrase for key derivation. Change this! |
| Setting | Default | Description |
|---|---|---|
bBackupEnabled | true | Create .bak files before overwriting saves. |
MaxBackupVersions | 2 | Number of backup versions to keep (1–10). |
bAutoRecoverFromBackup | true | Auto-recover from backup on corruption. |
| Setting | Default | Description |
|---|---|---|
bDebugJsonOutput | false | Export human-readable JSON alongside binary saves. |
bVerboseLogging | false | Enable detailed log output for all operations. |
The most common use case — save to a specific slot and load it back later.
// Node: "SmartSave - Save To Slot"
Slot Index: 0
Slot Name: "Chapter 1 - Before Boss"
Async: false
// Node: "SmartSave - Load From Slot"
Slot Index: 0
Async: false
USaveSubsystem* SaveSys = GetGameInstance()->GetSubsystem<USaveSubsystem>();
// Save
ESmartSaveResult Result = SaveSys->SaveToSlot(0, TEXT("Chapter 1 - Before Boss"));
if (Result != ESmartSaveResult::Success)
{
UE_LOG(LogTemp, Error, TEXT("Save failed: %s"),
*USmartSaveBlueprintLibrary::SmartSaveResultToString(Result));
}
// Load
ESmartSaveResult Result = SaveSys->LoadFromSlot(0);
One-button save/load to a dedicated slot (default: slot 1000). Perfect for F5/F9 keybinds.
SaveSys->QuickSave(); // Saves to QuickSaveSlotIndex (default: 1000)
SaveSys->QuickLoad(); // Loads from the same slot
// Nodes: "SmartSave - Quick Save" and "SmartSave - Quick Load"
// No parameters needed — uses the slot configured in Project Settings.
Automatic periodic saving with rotating slots. When all auto-save slots are used, the oldest is overwritten.
// Start auto-save timer (uses interval from Project Settings)
SaveSys->StartAutoSave();
// Stop auto-save
SaveSys->StopAutoSave();
// Check status
bool bActive = SaveSys->IsAutoSaveActive();
// Manually trigger an auto-save (uses the next rotating slot)
SaveSys->AutoSave();
Slot Layout
With default settings, auto-saves use slots 2000, 2001, 2002 (rotating). Manual saves use 0–19. Quick-save uses 1000. These ranges are fully configurable.
For large save files or when you need to show a "Saving..." UI without blocking the game.
SmartSave provides latent Blueprint nodes with proper Completed / Failed output execution pins:
// Search for "Async Save To Slot" in the Blueprint context menu.
// Connect your logic to the output pins:
// On Completed → Hide loading spinner, show "Game Saved!"
// On Failed → Show error message to player
// Save asynchronously (bAsync = true)
SaveSys->SaveToSlot(0, TEXT("My Save"), true);
// Listen for completion
SaveSys->OnSaveCompleted.AddDynamic(this, &AMyActor::OnSaved);
SaveSys->OnSaveFailed.AddDynamic(this, &AMyActor::OnSaveFailed);
SmartSave includes a ready-to-use save/load menu UI. Show it with one function call — no Widget Blueprints or UMG setup needed.
// Search for these nodes in Blueprint:
// "SmartSave - Show Save/Load Menu"
// "SmartSave - Hide Save/Load Menu"
// "SmartSave - Toggle Save/Load Menu"
// "SmartSave - Is Menu Visible"
// Toggle the menu (e.g., on Escape or a UI button)
USmartSaveBlueprintLibrary::ToggleSaveLoadMenu(this);
// Or via the subsystem directly
SaveSys->GetMenuManager()->ShowMenu();
SaveSys->GetMenuManager()->HideMenu();
SaveSys->GetMenuManager()->ToggleMenu();
// Check visibility
bool bVisible = SaveSys->GetMenuManager()->IsMenuVisible();
Customization
The built-in menu uses pure Slate and provides a solid starting point. For advanced customization, you can create your own UMG Widget Blueprint using the SmartSave Blueprint API to query slots, save, load, and delete — the same functions the built-in menu uses.
// Get all existing save slots
TArray<FSaveSlotInfo> Slots = SaveSys->GetAllSlots();
for (const FSaveSlotInfo& Slot : Slots)
{
UE_LOG(LogTemp, Log, TEXT("Slot %d: %s | %s | PlayTime: %s"),
Slot.SlotIndex,
*Slot.SlotName,
*Slot.Timestamp.ToString(),
*Slot.PlayTime.ToString());
}
// Check if a specific slot exists
if (SaveSys->DoesSlotExist(0))
{
FSaveSlotInfo Info = SaveSys->GetSlotInfo(0);
}
// Delete a slot
SaveSys->DeleteSlot(0);
Enrich your save slots with game-specific information that appears in the UI:
// Call these before saving — they apply to the next save operation
SaveSys->SetPlayerLevel(25);
SaveSys->SetPlayerInfo(TEXT("Warrior - Darkwood Forest"));
SaveSys->SetUserMetadata(TEXT("Difficulty"), TEXT("Hard"));
SaveSys->SetUserMetadata(TEXT("Chapter"), TEXT("3"));
// Now save — metadata is included automatically
SaveSys->SaveToSlot(0, TEXT("Before the Dragon"));
| Property | Type | Description |
|---|---|---|
SlotIndex | int32 | Slot number (0-based). |
SlotName | FString | User-facing display name. |
Timestamp | FDateTime | When the save was created. |
PlayTime | FTimespan | Total accumulated play time. |
PlayerLevel | int32 | Player progression (game-specific). |
PlayerInfo | FString | Character name/info (game-specific). |
MapName | FString | Active level at save time. |
UserMetadata | TMap<FString, FString> | Arbitrary custom key-value pairs. |
SaveVersion | int32 | Version for migration system. |
bIsAutoSave | bool | Whether this is an auto-save. |
bIsQuickSave | bool | Whether this is the quick-save. |
ThumbnailData | TArray<uint8> | Raw JPEG screenshot data. |
Store game-wide data that isn't tied to any specific actor — quest progress, inventory, unlocked abilities, world state flags, etc. This data persists across all save slots.
// Set global data (persisted with every save)
SaveSys->SetGlobalCustomData(TEXT("MainQuest"), TEXT("Chapter3_DefeatDragon"));
SaveSys->SetGlobalCustomData(TEXT("Gold"), TEXT("15000"));
SaveSys->SetGlobalCustomData(TEXT("UnlockedAreas"), TEXT("Forest,Cave,Castle"));
// Read global data
FString Quest = SaveSys->GetGlobalCustomData(TEXT("MainQuest"));
FString Gold = SaveSys->GetGlobalCustomData(TEXT("Gold"));
// Get all global data
TMap<FString, FString> AllData = SaveSys->GetAllGlobalCustomData();
// Remove specific entry
SaveSys->RemoveGlobalCustomData(TEXT("Gold"));
// Clear everything
SaveSys->ClearGlobalCustomData();
Use Case: Inventory System
Store your inventory as a serialized JSON string in Global Custom Data. On load, parse the string back into your inventory struct. This keeps your inventory independent of any specific actor in the world.
Prevent players from manually editing save files by enabling AES-256 encryption. SmartSave derives a 256-bit key from your passphrase using SHA-256.
For enhanced security, set the passphrase at runtime so it never appears in config files:
// In your GameInstance or GameMode BeginPlay:
USaveSubsystem* SaveSys = GetGameInstance()->GetSubsystem<USaveSubsystem>();
SaveSys->SetEncryptionEnabled(true);
SaveSys->SetEncryptionPassphrase(TEXT("MyGame_V1_SuperSecretKey_2024!"));
// Check status
bool bEncrypted = SaveSys->IsEncryptionEnabled();
Important
Changing the passphrase will make all existing encrypted saves unreadable. If you need to change the key, implement a migration that re-encrypts old saves with the new key, or provide a recovery mechanism.
Security Note
Client-side encryption is a deterrent, not absolute protection. Determined hackers with memory debuggers can extract the key. For competitive games, combine with server-side validation.
SmartSave automatically creates backup copies of save files before overwriting them. If a save becomes corrupted, the system automatically attempts recovery from the most recent backup.
slot_0.smartsav, the existing file is renamed to slot_0.smartsav.bak..bak already exists, it becomes .bak2, and so on (up to MaxBackupVersions)..bak, then .bak2, etc.In Project Settings:
bBackupEnabled = true — enable/disable backupsMaxBackupVersions = 2 — keep up to N backup versionsbAutoRecoverFromBackup = true — automatically try backups on corruptionWhen you update your game and the save data format changes (new properties, renamed fields, restructured data), the migration system automatically upgrades old save files.
CurrentSaveVersion in Project Settings (e.g., increment from 1 to 2).// MigrationV1ToV2.h
#include "SaveMigration.h"
UCLASS()
class UMigrationV1ToV2 : public UObject, public ISaveMigration
{
GENERATED_BODY()
public:
virtual int32 GetSourceVersion() const override { return 1; }
virtual int32 GetTargetVersion() const override { return 2; }
virtual bool Migrate(FSaveGameData& SaveData, FString& OutError) override
{
// Example: rename a global data key
if (FString* OldVal = SaveData.GlobalCustomData.Find(TEXT("PlayerGold")))
{
SaveData.GlobalCustomData.Add(TEXT("Currency_Gold"), *OldVal);
SaveData.GlobalCustomData.Remove(TEXT("PlayerGold"));
}
return true;
}
};
// In your GameInstance::Init() or GameMode::BeginPlay():
UMigrationV1ToV2* Migration = NewObject<UMigrationV1ToV2>();
SaveSys->RegisterMigration(Migration);
SmartSave uses a swappable storage architecture. The default ULocalSaveStorage writes to local disk. You can replace it with any custom backend by implementing the ISaveStorage interface.
| Method | Description |
|---|---|
WriteSaveData(SlotIndex, Data, OutError) | Write raw binary save data. |
ReadSaveData(SlotIndex, OutData, OutError) | Read raw binary save data. |
DeleteSaveData(SlotIndex, OutError) | Delete a save slot. |
DoesSaveExist(SlotIndex) | Check if a save exists. |
GetAllSaveSlotIndices(OutIndices) | List all slots with data. |
WriteThumbnail(SlotIndex, Data, OutError) | Write screenshot JPEG data. |
ReadThumbnail(SlotIndex, OutData, OutError) | Read screenshot JPEG data. |
WriteDebugJson(SlotIndex, Json, OutError) | Write debug JSON output. |
UCLASS()
class UMyCloudStorage : public UObject, public ISaveStorage
{
GENERATED_BODY()
public:
virtual bool WriteSaveData(int32 SlotIndex, const TArray<uint8>& Data,
FString& OutError) override
{
// Upload to your cloud service
return MyCloudAPI::Upload(SlotIndex, Data);
}
// ... implement all ISaveStorage methods ...
};
// Register it:
UMyCloudStorage* Cloud = NewObject<UMyCloudStorage>();
SaveSys->SetStorageBackend(Cloud);
Included Templates
SmartSave includes template implementations for Steam Cloud (USteamSaveStorage) and Epic Online Services (UEOSSaveStorage). These provide the correct interface structure — you just need to fill in the platform SDK calls for your project.
SmartSave provides a complete event system for reacting to save/load operations. All delegates are BlueprintAssignable.
| Delegate | Parameters | When Fired |
|---|---|---|
OnSaveStarted | int32 SlotIndex | When a save operation begins. |
OnSaveCompleted | int32 SlotIndex | When a save succeeds. |
OnSaveFailed | int32 SlotIndex, FString Error | When a save fails. |
OnLoadStarted | int32 SlotIndex | When a load operation begins. |
OnLoadCompleted | int32 SlotIndex | When a load succeeds. |
OnLoadFailed | int32 SlotIndex, FString Error | When a load fails. |
// Bind in BeginPlay
SaveSys->OnSaveCompleted.AddDynamic(this, &AMyHUD::ShowSaveNotification);
SaveSys->OnSaveFailed.AddDynamic(this, &AMyHUD::ShowSaveError);
SaveSys->OnLoadCompleted.AddDynamic(this, &AMyHUD::OnGameLoaded);
// Handler functions
void AMyHUD::ShowSaveNotification(int32 SlotIndex)
{
// Show "Game Saved!" toast notification
}
void AMyHUD::ShowSaveError(int32 SlotIndex, const FString& Error)
{
// Show error dialog
}
void AMyHUD::OnGameLoaded(int32 SlotIndex)
{
// Refresh UI, update HUD elements
}
Every save/load operation generates a detailed performance report. Use it to identify slow actors, compression overhead, or I/O bottlenecks.
// After a save/load operation:
FSaveProfileReport Report = SaveSys->GetLastProfileReport();
UE_LOG(LogTemp, Log, TEXT("Total: %.2fms | Actors: %d | Size: %d bytes"),
Report.TotalTimeMs,
Report.ActorCount,
Report.TotalDataSizeBytes);
// Or get a formatted string:
FString ReportStr = USmartSaveBlueprintLibrary::GetLastProfileReportString(this);
UE_LOG(LogTemp, Log, TEXT("%s"), *ReportStr);
| Field | Description |
|---|---|
TotalTimeMs | Total operation time. |
SerializationTimeMs | Time spent serializing/deserializing properties. |
CompressionTimeMs | Time spent on compression/decompression. |
EncryptionTimeMs | Time spent on encryption/decryption. |
IOTimeMs | Time spent on disk I/O. |
ActorCount | Number of actors processed. |
TotalDataSizeBytes | Final save file size. |
ActorEntries[] | Per-actor timing and size (sorted slowest first). |
Access via: GetGameInstance()->GetSubsystem<USaveSubsystem>()
| Category | Function | Returns |
|---|---|---|
| Save/Load | SaveToSlot(SlotIndex, SlotName, bAsync) | ESmartSaveResult |
LoadFromSlot(SlotIndex, bAsync) | ESmartSaveResult | |
QuickSave() | ESmartSaveResult | |
QuickLoad() | ESmartSaveResult | |
AutoSave() | ESmartSaveResult | |
| Slots | GetAllSlots() | TArray<FSaveSlotInfo> |
GetSlotInfo(SlotIndex) | FSaveSlotInfo | |
DeleteSlot(SlotIndex) | bool | |
DoesSlotExist(SlotIndex) | bool | |
| Custom Data | SetGlobalCustomData(Key, Value) | void |
GetGlobalCustomData(Key) | FString | |
RemoveGlobalCustomData(Key) | void | |
ClearGlobalCustomData() | void | |
GetAllGlobalCustomData() | TMap<FString, FString> | |
| Metadata | SetPlayerLevel(Level) | void |
SetPlayerInfo(Info) | void | |
SetUserMetadata(Key, Value) | void | |
| Auto-Save | StartAutoSave() | void |
StopAutoSave() | void | |
IsAutoSaveActive() | bool | |
| Encryption | SetEncryptionEnabled(bEnable) | void |
SetEncryptionPassphrase(Passphrase) | void | |
IsEncryptionEnabled() | bool | |
| Storage | SetStorageBackend(NewStorage) | void |
| Menu | GetMenuManager() | USmartSaveMenuManager* |
| State | IsOperationInProgress() | bool |
GetPlayTime() | FTimespan | |
GetLastProfileReport() | FSaveProfileReport |
Available as Blueprint nodes. Automatically resolves the subsystem from WorldContext.
| Function | Description |
|---|---|
GetSmartSaveSubsystem() | Get the subsystem reference. |
SmartSaveToSlot() | Save to slot (convenience). |
SmartLoadFromSlot() | Load from slot (convenience). |
SmartQuickSave() | Quick save (convenience). |
SmartQuickLoad() | Quick load (convenience). |
SmartAutoSave() | Auto save (convenience). |
SmartGetAllSlots() | Get all save slot info. |
SmartDoesSlotExist() | Check if slot exists. |
SmartDeleteSlot() | Delete a slot. |
CreateThumbnailTexture() | Convert JPEG bytes to UTexture2D. |
ShowSaveLoadMenu() | Show built-in save/load UI. |
HideSaveLoadMenu() | Hide built-in save/load UI. |
ToggleSaveLoadMenu() | Toggle save/load UI. |
IsSaveLoadMenuVisible() | Check if menu is visible. |
SmartSaveResultToString() | Convert result enum to string. |
GetLastProfileReport() | Get profiling report. |
GetLastProfileReportString() | Get report as formatted text. |
| Value | Meaning |
|---|---|
Success | Operation completed successfully. |
Failed | Generic failure (check logs). |
SlotNotFound | Requested slot does not exist. |
CorruptedFile | Save file failed checksum validation. |
VersionMismatch | No migration found for this save version. |
OperationInProgress | Another save/load is already running. |
StorageError | Disk I/O error (full disk, permissions, etc.). |
InvalidSlot | Slot index is out of valid range. |
SmartSave ships with example classes you can use as a reference implementation or for quick testing.
A simple actor (visible cube) that demonstrates ISaveableInterface with SaveGame properties.
| Property | Type | Default |
|---|---|---|
Health | float | 100.0 |
Score | int32 | 0 |
PlayerName | FString | "ExamplePlayer" |
ItemCount | int32 | 0 |
The actor changes its properties every 2 seconds in Tick, so you can observe save/load restoring state.
A Player Controller with number-key bindings for testing all SmartSave features:
| Key | Action |
|---|---|
| 1 | Save to Slot 0 |
| 2 | Load from Slot 0 |
| 3 | Quick Save |
| 4 | Quick Load |
| 5 | Auto Save (manual trigger) |
| 6 | Delete Slot 0 |
| 7 | List all save slots (Output Log) |
| 8 | Toggle Save/Load Menu |
BP_ExampleGameMode.SmartSaveExampleController.SmartSaveExampleActor into your level.BP_ExampleGameMode.A: Yes. All functionality is exposed via Blueprint nodes. The USaveableComponent lets you make actors saveable without any C++ code. Just add the component and check the "SaveGame" box on variables.
A: Yes. The USaveSubsystem lives on the GameInstance, which persists across OpenLevel calls. Global Custom Data and subsystem state survive level changes. Actor data is re-collected per level.
A: Yes. Save data is organized per-level (FSaveLevelData), supporting both the persistent level and any number of streamed sub-levels. Each sub-level's actors are saved independently.
A: All UPROPERTY types supported by Unreal's serialization: basic types (int, float, bool, string), structs, enums, arrays (TArray), maps (TMap), sets (TSet), soft/hard object references, and nested structs. If it can be a UPROPERTY, it can be saved.
A: Yes. Actors spawned with SpawnActor that have a USaveableComponent or implement ISaveableInterface are automatically assigned a persistent GUID. On load, they are respawned at the correct location with all properties restored.
A: Check these things:
ISaveableInterface OR has a USaveableComponent.UPROPERTY(SaveGame) (C++) or the "SaveGame" checkbox (Blueprint).ShouldSave() returns true (default behavior).A: Enable compression in Project Settings (LZ4 is recommended for best speed/size ratio). Also check if you have actors saving unnecessary data — use ShouldSave() to exclude actors that don't need persistence, and avoid marking large transient data as SaveGame.
A: The save file's CRC32 checksum doesn't match. If backups are enabled, SmartSave will automatically try .bak files. Common causes: disk write interrupted (crash during save), manual file editing, or encryption passphrase changed.
A: You incremented CurrentSaveVersion without registering a migration for the old version. Create and register an ISaveMigration implementation (see Section 11).
SmartSave v1.0 • © DwarfDiaries • Built for Unreal Engine 5.6+