Version 1.0  •  Unreal Engine 5.6+

SmartSave

Professional Save/Load System for Unreal Engine

Slot-BasedMultiple save slots with metadata, thumbnails & timestamps
AsyncBackground save/load with Blueprint async nodes
EncryptedAES-256 encryption for anti-cheat protection
CompressedLZ4/Zlib compression for smaller save files
ExtensibleCustom storage backends, migrations & interfaces
UI IncludedReady-made Save/Load menu with one function call

by DwarfDiaries

Table of Contents

1. Overview & Features

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.

Slot-Based Saving

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.

Zero-Config Serialization

Mark any property with UPROPERTY(SaveGame) and it's automatically serialized. No boilerplate, no manual byte packing. Supports all UE property types.

Async Operations

Save and load on background threads to avoid hitches. Async Blueprint nodes provide proper Completed/Failed execution pins for clean visual scripting.

AES-256 Encryption

Encrypt save files with a passphrase to prevent players from editing save data. Set the passphrase at runtime via code — no config files needed.

LZ4/Zlib Compression

Reduce save file size by 60-80%. LZ4 offers near-instant compression; Zlib provides maximum compression ratio for shipping builds.

Automatic Backups

Rotating .bak files protect against corruption. If a save is corrupted, SmartSave automatically recovers from the most recent backup.

Save Migrations

When you update your game and the save format changes, the migration system automatically upgrades old save files — like database migrations.

Swappable Storage

Local disk by default. Drop in a custom backend for Steam Cloud, Epic Online Services, or any other storage. Template implementations included.

Built-in Save/Load Menu

A ready-made UI panel with slot list, thumbnails, save/load/delete buttons, and keyboard support. Show it with a single function call.

Performance Profiler

Built-in profiling reports show per-actor serialization time, compression overhead, I/O duration, and data sizes. Identify bottlenecks instantly.

Architecture

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.

SmartSave Architecture Diagram

2. Installation

  1. Copy the plugin folder
    Copy the SmartSavePlugin folder into your project's Plugins/ directory:
    YourProject/
      Plugins/
        SmartSavePlugin/
          SmartSavePlugin.uplugin
          Source/
          Resources/
          Content/
  2. Regenerate project files
    Right-click your .uproject file and select "Generate Visual Studio project files" (or use your preferred IDE).
  3. Compile
    Open the project in your IDE and build, or open the Unreal Editor (it will compile automatically).
  4. Verify installation
    In the Editor, go to Edit → Plugins and search for "SmartSave". Ensure it is enabled.
SmartSave plugin enabled in Unreal Editor Plugins window

Module Dependencies

SmartSave only depends on core engine modules (Core, CoreUObject, Engine, InputCore, ImageWrapper, RenderCore, RHI, Slate, SlateCore). No external plugins required.

Adding the C++ Module Dependency

If you want to use SmartSave classes directly in your C++ code, add the module dependency to your game's .Build.cs:

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

3. Quick Start Guide

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.

3.1 Basic Save & Load

Blueprint

  1. Open any Blueprint (Level Blueprint, Character, Game Mode, etc.)
  2. Right-click in the Event Graph and search for "SmartSave". You'll see all available nodes.
  3. Save: Drag out the "SmartSave - Save To Slot" node. Set Slot Index to 0 and Slot Name to "My First Save".
  4. Load: Drag out the "SmartSave - Load From Slot" node. Set Slot Index to 0.
  5. Connect to any event (button click, key press, etc.) and play!
Blueprint graph: Debug Key X connected to SmartSave Save To Slot node

C++

C++ — Save and Load
// 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);

3.2 Marking Properties for Save

SmartSave automatically serializes any property marked with the SaveGame specifier. No additional code needed.

C++

C++ — SaveGame Properties
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;
};

Blueprint

  1. Open your Blueprint class (Actor, Character, etc.).
  2. Select a variable in the My Blueprint panel.
  3. In the Details panel on the right, expand "Advanced".
  4. Check the "SaveGame" checkbox.
Blueprint Details panel with SaveGame checkbox enabled on Health variable

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.

3.3 Component-Based Approach (Blueprint-Only)

The easiest way to make any actor saveable — no C++, no interfaces. Just add a component.

  1. Open your actor Blueprint in the Blueprint Editor.
  2. Click "Add Component" and search for "Saveable". Add the Saveable component (USaveableComponent).
  3. Configure the component in the Details panel:
    • Save Transform — saves position, rotation, scale (default: on)
    • Save Properties — saves all SaveGame variables (default: on)
    • Enabled — include in save operations (default: on)
  4. Mark any variables you want saved with the "SaveGame" checkbox (see section 3.2).
  5. (Optional) Bind the component's events for custom save/load logic:
    • On Pre Save — prepare data before saving
    • On Post Load — rebuild state after loading
    • On Modify Save Data — add custom key-value data
    • On Load Data — read custom key-value data
Adding Saveable component to an actor Saveable component Details panel

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

3.4 Interface-Based Approach (C++)

For maximum control, implement ISaveableInterface on your actor class. This gives you access to lifecycle hooks for custom save/load logic.

C++ — ISaveableInterface Implementation
// 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;
};
C++ — Implementation
// 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

4. Project Settings

SmartSave is configured via Project Settings → Plugins → SmartSave. All settings have sensible defaults — you can ship without changing anything.

SmartSave Project Settings panel showing Slots, Auto-Save, Serialization, Screenshot, Storage, Encryption, Backup and Debug categories

Slots

SettingDefaultDescription
MaxSaveSlots20Maximum number of manual save slots (1–999).
QuickSaveSlotIndex1000Slot reserved for quick-save. Set to -1 to disable.

Auto-Save

SettingDefaultDescription
bAutoSaveEnabledtrueEnable automatic periodic saving.
AutoSaveIntervalSeconds300.0Seconds between auto-saves (min: 10).
MaxAutoSaveSlots3Number of rotating auto-save slots (1–99).
AutoSaveStartIndex2000Starting slot index for auto-saves.

Serialization & Compression

SettingDefaultDescription
bCompressionEnabledtrueCompress save data to reduce file size.
CompressionAlgorithmLZ4Algorithm: None, LZ4 (fast), or Zlib (smaller).
CurrentSaveVersion1Your game's save version. Increment when format changes.

Screenshot / Thumbnail

SettingDefaultDescription
bCaptureScreenshottrueCapture a screenshot when saving.
ScreenshotWidth320Thumbnail width in pixels (32–1920).
ScreenshotHeight180Thumbnail height in pixels (32–1080).
ScreenshotQuality85JPEG quality (1–100).

Storage

SettingDefaultDescription
SaveDirectoryOverride(empty)Custom save directory. Empty = default platform path.
SaveFileExtensionsmartsavFile extension for save files.

Encryption

SettingDefaultDescription
bEncryptionEnabledfalseEncrypt save files with AES-256.
EncryptionPassphrase(default key)Passphrase for key derivation. Change this!

Backup & Recovery

SettingDefaultDescription
bBackupEnabledtrueCreate .bak files before overwriting saves.
MaxBackupVersions2Number of backup versions to keep (1–10).
bAutoRecoverFromBackuptrueAuto-recover from backup on corruption.

Debug

SettingDefaultDescription
bDebugJsonOutputfalseExport human-readable JSON alongside binary saves.
bVerboseLoggingfalseEnable detailed log output for all operations.

5. Save Operations

5.1 Manual Save & Load

The most common use case — save to a specific slot and load it back later.

Blueprint C++
Blueprint
// 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
C++
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);
Blueprint graph: SaveToSlot with Result to String and Print String nodes

5.2 Quick Save / Quick Load

One-button save/load to a dedicated slot (default: slot 1000). Perfect for F5/F9 keybinds.

C++
SaveSys->QuickSave();   // Saves to QuickSaveSlotIndex (default: 1000)
SaveSys->QuickLoad();   // Loads from the same slot
Blueprint
// Nodes: "SmartSave - Quick Save" and "SmartSave - Quick Load"
// No parameters needed — uses the slot configured in Project Settings.

5.3 Auto-Save

Automatic periodic saving with rotating slots. When all auto-save slots are used, the oldest is overwritten.

C++
// 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.

5.4 Async Save & Load (Blueprint)

For large save files or when you need to show a "Saving..." UI without blocking the game.

Blueprint

SmartSave provides latent Blueprint nodes with proper Completed / Failed output execution pins:

Blueprint graph: Async Save To Slot with On Completed and On Failed execution pins
Blueprint Usage
// 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
C++ — Async with Delegates
// 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.

SmartSave in-game Save/Load menu with save slots, thumbnails and action buttons

Show / Hide / Toggle

Blueprint
// 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"
C++
// 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();

Menu Features

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.

7. Slot Management & Metadata

Querying Slots

C++
// 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);

Setting Slot Metadata

Enrich your save slots with game-specific information that appears in the UI:

C++
// 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"));

FSaveSlotInfo Properties

PropertyTypeDescription
SlotIndexint32Slot number (0-based).
SlotNameFStringUser-facing display name.
TimestampFDateTimeWhen the save was created.
PlayTimeFTimespanTotal accumulated play time.
PlayerLevelint32Player progression (game-specific).
PlayerInfoFStringCharacter name/info (game-specific).
MapNameFStringActive level at save time.
UserMetadataTMap<FString, FString>Arbitrary custom key-value pairs.
SaveVersionint32Version for migration system.
bIsAutoSaveboolWhether this is an auto-save.
bIsQuickSaveboolWhether this is the quick-save.
ThumbnailDataTArray<uint8>Raw JPEG screenshot data.

8. Global Custom 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.

C++
// 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.

9. Encryption (Anti-Cheat)

Prevent players from manually editing save files by enabling AES-256 encryption. SmartSave derives a 256-bit key from your passphrase using SHA-256.

Via Project Settings

  1. Go to Project Settings → Plugins → SmartSave → Encryption.
  2. Check "Encryption Enabled".
  3. Set a unique passphrase for your game (replace the default!).

Via Code (Runtime)

For enhanced security, set the passphrase at runtime so it never appears in config files:

C++ — Runtime Encryption
// 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.

10. Backup & Recovery

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.

How It Works

  1. Before overwriting slot_0.smartsav, the existing file is renamed to slot_0.smartsav.bak.
  2. If a .bak already exists, it becomes .bak2, and so on (up to MaxBackupVersions).
  3. On load, if the primary file fails a checksum validation, SmartSave tries .bak, then .bak2, etc.

Configuration

In Project Settings:

11. Save Versioning & Migrations

When you update your game and the save data format changes (new properties, renamed fields, restructured data), the migration system automatically upgrades old save files.

How It Works

  1. Set CurrentSaveVersion in Project Settings (e.g., increment from 1 to 2).
  2. Create a migration class that upgrades saves from version 1 to version 2.
  3. Register it with the subsystem.
  4. When a v1 save is loaded, SmartSave automatically runs the 1→2 migration.
  5. Multiple migrations chain: if save is v1 and game is v3, migrations 1→2 and 2→3 run in sequence.
C++ — Migration Example
// 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);

12. Custom Storage Backends

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.

ISaveStorage Interface

MethodDescription
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.
C++ — Custom Backend Example
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.

13. Events & Delegates

SmartSave provides a complete event system for reacting to save/load operations. All delegates are BlueprintAssignable.

DelegateParametersWhen Fired
OnSaveStartedint32 SlotIndexWhen a save operation begins.
OnSaveCompletedint32 SlotIndexWhen a save succeeds.
OnSaveFailedint32 SlotIndex, FString ErrorWhen a save fails.
OnLoadStartedint32 SlotIndexWhen a load operation begins.
OnLoadCompletedint32 SlotIndexWhen a load succeeds.
OnLoadFailedint32 SlotIndex, FString ErrorWhen a load fails.

Blueprint Example

Blueprint graph: Bind Event to On Save Completed delegate with custom event

C++ Example

C++
// 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
}

14. Performance Profiling

Every save/load operation generates a detailed performance report. Use it to identify slow actors, compression overhead, or I/O bottlenecks.

C++
// 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);

Report Fields

FieldDescription
TotalTimeMsTotal operation time.
SerializationTimeMsTime spent serializing/deserializing properties.
CompressionTimeMsTime spent on compression/decompression.
EncryptionTimeMsTime spent on encryption/decryption.
IOTimeMsTime spent on disk I/O.
ActorCountNumber of actors processed.
TotalDataSizeBytesFinal save file size.
ActorEntries[]Per-actor timing and size (sorted slowest first).

15. API Reference

USaveSubsystem (GameInstance Subsystem)

Access via: GetGameInstance()->GetSubsystem<USaveSubsystem>()

CategoryFunctionReturns
Save/LoadSaveToSlot(SlotIndex, SlotName, bAsync)ESmartSaveResult
LoadFromSlot(SlotIndex, bAsync)ESmartSaveResult
QuickSave()ESmartSaveResult
QuickLoad()ESmartSaveResult
AutoSave()ESmartSaveResult
SlotsGetAllSlots()TArray<FSaveSlotInfo>
GetSlotInfo(SlotIndex)FSaveSlotInfo
DeleteSlot(SlotIndex)bool
DoesSlotExist(SlotIndex)bool
Custom DataSetGlobalCustomData(Key, Value)void
GetGlobalCustomData(Key)FString
RemoveGlobalCustomData(Key)void
ClearGlobalCustomData()void
GetAllGlobalCustomData()TMap<FString, FString>
MetadataSetPlayerLevel(Level)void
SetPlayerInfo(Info)void
SetUserMetadata(Key, Value)void
Auto-SaveStartAutoSave()void
StopAutoSave()void
IsAutoSaveActive()bool
EncryptionSetEncryptionEnabled(bEnable)void
SetEncryptionPassphrase(Passphrase)void
IsEncryptionEnabled()bool
StorageSetStorageBackend(NewStorage)void
MenuGetMenuManager()USmartSaveMenuManager*
StateIsOperationInProgress()bool
GetPlayTime()FTimespan
GetLastProfileReport()FSaveProfileReport

USmartSaveBlueprintLibrary (Static Functions)

Available as Blueprint nodes. Automatically resolves the subsystem from WorldContext.

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

ESmartSaveResult (Return Codes)

ValueMeaning
SuccessOperation completed successfully.
FailedGeneric failure (check logs).
SlotNotFoundRequested slot does not exist.
CorruptedFileSave file failed checksum validation.
VersionMismatchNo migration found for this save version.
OperationInProgressAnother save/load is already running.
StorageErrorDisk I/O error (full disk, permissions, etc.).
InvalidSlotSlot index is out of valid range.

16. Example Content

SmartSave ships with example classes you can use as a reference implementation or for quick testing.

ASmartSaveExampleActor

A simple actor (visible cube) that demonstrates ISaveableInterface with SaveGame properties.

PropertyTypeDefault
Healthfloat100.0
Scoreint320
PlayerNameFString"ExamplePlayer"
ItemCountint320

The actor changes its properties every 2 seconds in Tick, so you can observe save/load restoring state.

ASmartSaveExampleController

A Player Controller with number-key bindings for testing all SmartSave features:

KeyAction
1Save to Slot 0
2Load from Slot 0
3Quick Save
4Quick Load
5Auto Save (manual trigger)
6Delete Slot 0
7List all save slots (Output Log)
8Toggle Save/Load Menu

Setting Up the Example

  1. Create a GameMode Blueprint — In the Content Browser, right-click → Blueprint Class → Game Mode Base. Name it BP_ExampleGameMode.
  2. Set the Player Controller — Open BP_ExampleGameMode, set Player Controller Class to SmartSaveExampleController.
  3. Place the Example Actor — Drag SmartSaveExampleActor into your level.
  4. Set the GameMode — In World Settings, set GameMode Override to BP_ExampleGameMode.
  5. Play! — Press number keys 1–8 to test all features. Watch the Output Log for feedback.
World Settings with BP_ExampleGameMode and SmartSaveExampleActor in viewport

17. FAQ & Troubleshooting

General

Q: Does SmartSave work with Blueprint-only projects?

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.

Q: Does SmartSave persist across level transitions?

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.

Q: Does SmartSave support World Partition and Level Streaming?

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.

Q: What property types are supported?

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.

Q: Can I save runtime-spawned actors?

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.

Troubleshooting

Q: My actor's properties aren't being saved.

A: Check these things:

  1. The actor implements ISaveableInterface OR has a USaveableComponent.
  2. Properties are marked with UPROPERTY(SaveGame) (C++) or the "SaveGame" checkbox (Blueprint).
  3. ShouldSave() returns true (default behavior).
  4. The actor exists in the world when the save operation runs.

Q: Save files are very large.

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.

Q: Load fails with "CorruptedFile".

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.

Q: "VersionMismatch" error on load.

A: You incremented CurrentSaveVersion without registering a migration for the old version. Create and register an ISaveMigration implementation (see Section 11).

18. Changelog

Version 1.0 — Initial Release


SmartSave v1.0  •  © DwarfDiaries  •  Built for Unreal Engine 5.6+