đŸ—ș Savior Plugin Documentation

Savior is a C++ tool designed to extend Unreal's save system, providing a more powerful serialization framework for complex Unreal projects. Savior is a custom serialization system built from scratch with efficiency in mind, together with a focus in productivity and ease-of-use in Unreal. This documentation summarizes most common doubts of new users, most common mistakes, and the best solutions to achieve developer's goals.

Savior

Features

Productivity
Savior eliminates micro-management of individual properties, becoming a valuable time saver for small teams.
Marking a property with Unreal's 'Save Game' exposes property to the save system, no mirror property required.
A rich library of helper functions brings free customization of the save process within blueprints, no coding.
Deleting or adding new blueprint properties will not corrupt existing save files; Contrary to other save systems.
Built-in versioning system helps patching live games, without causing players to lose existing progress.
Performance
Savior abuses Unreal's multi-threading capabilities. Saving data is absurdly fast, only limited by target hardware.
Blueprint properties are read directly from target, mirror property in slots not required, increasing performance.
Actor's location, rotation, scale, velocity, mesh, materials; Are all recorded from multi-threaded algorithms.
Utility
Savior is based on slots to persist data.
A full HUD system ships with the plugin, making easy to implement loading screens, slot selection screens.
Data loading progress is automatically calculated, generating feedback progress bars without developer efforts.

Contents

Savior API
Installation
How to Setup a Slot
How to Save & Load
How to Setup Pickups
How to Setup Procedural Actors
Understanding SGUID
Tips & Tricks
Savior in C++
Specifications
Extras FAQ


Quick Guides

Installation

Open the Epic Games Launcher and install the Savior plugin from Marketplace, or the FAB store. You must have a valid Epic games account to purchase any products from Unreal Marketplace.

How to Setup a Slot

Create a Slot Asset on Asset Browser (right-click), look for HKH >> New Slot menu; You can also create a new slot at the root Content folder by opening the File main menu and clicking New Slot... under the [HKH] Savior sub-category.

1
1.1

When opening the Asset you can quickly adjust default Properties such as Default Player Name, Levels Thumbnails, etc:

2

How to Save Load

All you have to do is right click any graph on any of your Blueprints and search from one of main nodes in “Savior” section. These main Save/Load functions automatically creates a runtime instance of a Slot object for you
 So you don’t have to instantiate anything, just reference the Slot Asset and let the node work:

3

From any UMG Widget you simply setup your “On Button Clicked” events to call one of these “Savior 2” functions. It’s THAT simple, everything in scope marked ‘SaveGame’ tag will save or load:

4

Manual Slot Instances Management

If you desire to perform custom Save/Load operations and not necessarily just fire a "Save Game World" to save everything, you can do so by building a process starting from the New Slot Instance Node. Example:

SS1 SS2


How to Setup Pickups

Any Actor you wish to remember it was destroyed and should not respawn on Level load, you have to add a “Destroyed” Boolean Property to it. And mark it ‘Save Game’ tag as well:

5

The Property must be a Boolean named “Destroyed”, case sensitive. The Property must be marked ‘Save Game’ tag.

Then in your Blueprint Graph, create a new Function calling it whatever you’d like, this Function will be a substitute of “Destroy Actor” node for the Game. Inside this Graph Function set the value of “Destroyed” to true, but don’t destroy this Actor before you save the Game, maybe hide it instead:

6

Having that Boolean “Destroyed” Property set to TRUE will tell the Plugin that this Actor must destroy itself once the Level was loaded, making it be gone the next time a Player visits that Level


To do that, when you want a Pickup to be destroyed, simply call your newly created Function that hides the Actor and sets “Destroyed” to True instead of destroying the Actor with a Destroy node:

7

Once the Game is saved, the Plugin will Destroy the Actor after it’s “Destroyed” Property has been recorded, so the Actor won’t be left there consuming memory.

! WARNING: If you overwrite the data existing in a slot file, the 'Destroyed' record will be erased as well. So, if your design involves persistently destroyed enties, you most likely want to LOAD a slot from file before saving again! This way your destroyed actors records will never be erased whenever your slot files are updated with new data.


How to Setup Procedural Actors

An Actor, or Component, you are spawning at Runtime will be saved as usual. However loading them back is a complex task because we cannot control whatever ID the internal engine will assign to a runtime spawned Object. Often the new Object’s ID will be random internal pointer that used to reference another object; to overcome this obstacle to Save & Load “Procedural Actor” properly, your Procedural Class is required to implement three things:

Implement the “SAVIOR_Procedural” Function Interface. Include a “Guid” Property to its Variables List, named “SGUID”. In its Construction Script, call a special node called “Make SGUID”. Those three simple steps above will guarantee your Procedural Class will be loaded correctly from Slot’s Data without mismatching Data with another instance of your Class also spawned in Runtime, turning them into Absolutely Identifiable Procedural Objects.

8

First, within desired Procedural Actor’s Blueprint, we have to implement our “Procedural” Function Interface:

9

Once that is done, we now have to create a “Guid” Property for the Procedural Class and name it “SGUID”:

10

The Property must be a Guid Struct named “SGUID”. Savior will ‘read’ the Property and expects it to be this type, otherwise it will be ignored. The Property must be marked ‘Save Game’ tag to be visible to the Auto-Save System.

That been done, only step left now is making sure SGUID’s value is persistent and unique. That would be a headache for you to do, so instead of trying to control its behavior, there’s a node that can do that for you within a Construction Script


Add the “Make SGUID” node to your Construction Script Graph and assign its output value to the “SGUID” Property:

11

Do NOT use a default “New Guid” node! The Guids created by that aren’t persistent, it would break our logic.

And it’s done, your ‘Procedural Class’ is ready to be freely spawned in Runtime and be automatically respawned with it’s correct Property’s values restored once the Game is reloaded from a Slot.

Understanding SGUID

There are three types of Actors in any Unreal Engine world:

For each type of Actor above, you have to understand how Unreal Engine identifies these instances. When you place any amount of instantiated Actors in the Level, Unreal Editor automatically resolves instance ID, these IDs created are the same at runtime.
When you have a Game Mode that is spawned at runtime, and then spawns an instance of a Character, new IDs are created, but you usually treat these Actors as singleton Actors. That means, if Savior knows the class of your Game Mode, it doesn't have to care if that Character is Character_C_0 or Character_C_55.
It knows that it's the Player's Character. However when you spawn at runtime any amount of instances of any arbitrary class, the same process of resolving instance IDs is performed by the engine; and Savior cannot assume it knows which Actor is which, because every time the application launches, different IDs will be applied.

So, in order to identify Actors properly, Savior requires a SGUID property in the base class of the Actors you are spawning. Example of a SGUID value, a property of type Guid (or FGuid in C++):

1D371B88-46704C84-75D4E493-62CCF89A

Savior uses these structs, a composition of various integer values, to properly identify Actor instances, regardless of instance ID these Actors were given by the engine. Because Unreal Engine by default applies ID shuffling to Actors when you package a Shipping Build, having a SGUID value is very important for Savior to be sure it knows who is who when saving and loading data.

So, you have very simple choices to deal with this...

  1. Create a value for your SGUID property manually, taking pen note of who is who in your Level.
  2. Use Create Once Node for Actors you keep in a list.
  3. Use Make SGUID Node for Actors you spawn at runtime, but expect them to act as a Singleton entity.

Example:

This Player Character is spawned at runtime... Here its ID has a suffix " _C_0 ", but that can be changed by the engine.

12

So, to counter this inconvenience, I add to Blueprint a SGUID property and use in its Construction Script a Make SGUID node.

13

! IMPORTANT:

Since Fortnite cheaters appeared, Epic Games is encrypting ID of some core actors, like the main playable character. But they only do this at runtime (shipping package), this is hardcoded into the Unreal Engine.

You won’t notice that until you package for shipping. In shipping mode Epic seem to add memory address encoded into the internal name of the actor
 and that is going to change every time you launch the game (to difficult the work of memory scanners used by cheaters).

So, for a main character, I simply will NOT use the SGUID making nodes in constructor


I just leave it as a 0000-0000-0000 guid instead.


Tips n Tricks

! One Template Class for multiple slot instances

14


Savior in C++

If you have native C++ classes you desire to implement Savior's API directly, follow these steps:

Build.cs:

You must add Savior3 module to your Build.cs list of dependencies >>

PublicDependencyModuleNames.AddRange(new string[]
{
    "Core", "CoreUObject", "Engine", "InputCore", "Savior"
});

C++:

Your class definition have to include Savior hears >>

#include "Savior.h"
#include "SaviorMetaData.h"

Your class should implement one or both of Savior's event interfaces >>

UCLASS()
class MYGAME_API ALoot : public AActor, public ISAVIOR_Serializable, public ISAVIOR_Procedural
{
    // ...
}

Override Interface functions >>

UFUNCTION() virtual void OnLoaded_Implementation(const FSlotMeta &MetaData) override;

Include and construct SGUID Property >>

UPROPERTY() FGuid SGUID;
AMyActor::AMyActor()
{
    SGUID = USavior3::MakeActorGUID( this , EGuidGeneratorMode::ResolvedNameToGUID );
}

Full Example:

.H

#pragma once

#include "Savior3.h"
#include "SaviorMetaData.h"
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Loot.generated.h"

UCLASS()
class MYGAME_API ALoot : public AActor, public ISAVIOR_Serializable, public ISAVIOR_Procedural
{
    GENERATED_BODY()
    
public:	
    // Sets default values for this actor's properties
    ALoot();

    UPROPERTY(EditDefaultsOnly, Category = Mesh)
        class USkeletalMeshComponent* Mesh;
    UPROPERTY(EditDefaultsOnly, Category = Mesh)
        class USkeletalMeshComponent* Outline;

    /** Returns MeshSK subobject **/
    UFUNCTION(BlueprintCallable)
        class USkeletalMeshComponent* GetSKMesh() const { return Mesh; }

    //image to display in Inventory
    UPROPERTY(EditAnywhere, Category = "Inventory")
        class UTexture* InventoryImage;
    
    UPROPERTY(EditAnywhere, Category = "Inventory")
        FName ItemName;
    UFUNCTION(BlueprintCallable)
        class UTexture* GetImage() { if (InventoryImage != nullptr) return InventoryImage; else return nullptr; }
    UFUNCTION(BlueprintCallable)
        FName GetItemName() { return ItemName; }

    UFUNCTION(BlueprintCallable)
        void DecrementAmnt() { --Amnt; }

    class UMaterialInstanceDynamic* MatOutline;
    class UMaterialInstanceDynamic* MatMesh;
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:	
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    UFUNCTION(BlueprintCallable)
        virtual void BeginView();
    UFUNCTION(BlueprintCallable)
        void EndView();

    virtual void Equip();
    virtual void UnEquip();

    UFUNCTION()
        void PickedUp();
    UFUNCTION()
        void Dropped(FVector DropLoc);

    UFUNCTION(BlueprintImplementableEvent)
        void DroppedBP();


    //For Inventory/UI purposes. Determines where in inventory and how much space each takes up.
    UPROPERTY(EditAnywhere)
        int32 XPos;
    UPROPERTY(EditAnywhere)
        int32 YPos;
    UPROPERTY(BlueprintReadWrite, Category = "Inventory")
        int32 XFill;
    UPROPERTY(BlueprintReadWrite, Category = "Inventory")
        int32 YFill;
    UPROPERTY(EditAnywhere)
        int32 Rarity;

    //for loading
    UPROPERTY(SaveGame)
        FGuid SGUID;
    UPROPERTY(EditAnywhere, SaveGame)
        bool bIsOwned = false;
    UPROPERTY(EditAnywhere, SaveGame)
        int32 HotBarIndex = 5; //0-4 index, 5 is not on hotbaar
    UPROPERTY(EditAnywhere, SaveGame)
        bool bIsEquipped = false; 
    UPROPERTY(EditAnywhere, SaveGame)
        FIntPoint SavedPosition;
    UFUNCTION()
        virtual void OnLoaded_Implementation(const FSlotMeta &MetaData) override;

    //logic for stackable items such as arrows/bombs/healing item
    UPROPERTY(BlueprintReadWrite)
        bool bIsStackable = false;
    UPROPERTY(BlueprintReadOnly)
        int32 Amnt = 1;

private:

    float OutlineScale = .5f;

};

.CPP

#include "Loot.h"
#include "Engine/Texture.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Materials/MaterialInterface.h"
#include "Components/SkeletalMeshComponent.h"

// Sets default values
ALoot::ALoot()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    SGUID = USavior3::MakeActorGUID(this,EGuidGeneratorMode::ResolvedNameToGUID);

    Mesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mesh"));
    Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    Mesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
    Mesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);
    Mesh->SetSimulatePhysics(false);

    SetRootComponent(Mesh);

    Outline = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Outline"));
    Outline->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    Outline->SetupAttachment(Mesh, FName("None"));

    InventoryImage = CreateDefaultSubobject<UTexture>(TEXT("Inventory Image")); 
}

// Called when the game starts or when spawned
void ALoot::BeginPlay()
{
    Super::BeginPlay();
    Outline->SetMasterPoseComponent(Mesh);	
    
    MatOutline = UMaterialInstanceDynamic::Create(Outline->GetMaterial(0), this);
    MatMesh = UMaterialInstanceDynamic::Create(Mesh->GetMaterial(0), this);
    Outline->SetMaterial(0, MatOutline);
    Mesh->SetMaterial(0, MatMesh);

    EndView();
}

// Called every frame
void ALoot::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

void ALoot::BeginView()
{
    MatOutline->SetScalarParameterValue(FName("Glow"), 500.0f);
    MatOutline->SetScalarParameterValue(FName("MaskAmnt"), 1.0f);
    if (Rarity == 1) MatOutline->SetVectorParameterValue(FName("Color"), FColor::Green);
    if (Rarity == 2) MatOutline->SetVectorParameterValue(FName("Color"), FColor::Blue);
    if (Rarity == 3) MatOutline->SetVectorParameterValue(FName("Color"), FColor::Purple);
    if (Rarity == 4) MatOutline->SetVectorParameterValue(FName("Color"), FColor::Red);	
    if (Rarity == 5) MatOutline->SetVectorParameterValue(FName("Color"), FColor::Yellow); 
}

void ALoot::EndView()
{
    MatOutline->SetScalarParameterValue(FName("Glow"), 0.0f);
    MatOutline->SetScalarParameterValue(FName("MaskAmnt"), 0.0f);
}

void ALoot::Equip()
{
    bIsEquipped = true;
    MatMesh->SetScalarParameterValue(FName("OffsetFOV"), 1.0f);
}

void ALoot::UnEquip()
{
    bIsEquipped = false;
}

void ALoot::PickedUp()
{
    bIsOwned = true;
    Mesh->SetSimulatePhysics(false);
    Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    Mesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Overlap);
    Mesh->SetCastShadow(false);
}

void ALoot::Dropped(FVector DropLoc)
{
    bIsOwned = false;
    MatMesh->SetScalarParameterValue(FName("OffsetFOV"), 0.0f);
    Mesh->SetSimulatePhysics(true);
    Mesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    Mesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
    Mesh->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Ignore);
    Mesh->SetCastShadow(true);
    EndView();
    SetActorLocation(DropLoc, true, nullptr, ETeleportType::TeleportPhysics); 

    DroppedBP();
}

void ALoot::OnLoaded_Implementation(const FSlotMeta &MetaData)
{
    //GetCharacterNameRenderer()->SetText(CharacterName);

    LOG_SV(true, ESeverity::Warning,
        FString::Printf(TEXT("OnLoaded()  ==>>  %s  ::  %s"),
            *MetaData.SaveLocation,
            *GetName()
        )
    );

    if ( bIsOwned ) {
        auto Player = GetWorld()->GetFirstPlayerController();
        if (!Player) return;

        // call event...
    }
}

FAQs

How, why my Actor isn't saving?

Chances are you did not setup a SGUID Property properly.

Save World or Game Mode returns Failed result, why?

Check thread state with Get Thread Safety node. If you have another save process running, you have to wait until previous process is complete to start a new one.

I still can't save anything?! Make sure you are creating an instance with New Slot Instance node. Don't try to save data directly into your Slot Class.


Extras

! Savior is one of few save systems in Unreal that supports saving object pointers. And it's the only that actually supports saving a chain of nested UObjects. This is perfect for complex inventory systems based on the UObject class.

! In Unreal Engine 5, Savior has built-in support for recording and restoring states of Chaos Destruction System's fracturing and geometry collections (EXPERIMENTAL).

! In Unreal Engine 5, Savior has partial built-in support for saving and loading the state of Mutable Characters and their parts (EXPERIMENTAL).


Module Specifications


Savior Core – C++ API

The following is technical documentation for the Savior.h header file from the Unreal Engine plugin Savior Auto-Save Plugin by Bruno Xavier Leite.


📩 USaviorSettings – Plugin Configuration Settings

This class holds plugin-level configuration data, categorized under gameplay, performance, and object handling.

đŸ§© UPROPERTY Members

General Settings

Performance

Reflector Scope


đŸ’Ÿ USavior – Core Save/Load Slot Object

This class provides a runtime object that handles saving and loading of game state data.

đŸ§© UPROPERTY Members

Configuration

Load Screen

UI/UX

Events

Misc


đŸ› ïž UFUNCTION Methods Overview

1. Core Slot File I/O

2. Object/Component/Actor Save/Load

3. Game Scope Save/Load

4. Level and Data Layer Serialization

5. Low-Level Record Management

6. GUID and ID Utilities

7. UI/UX Getters and Setters

8. Utility + Meta Methods


✅ Summary

The USavior class exposes a highly granular and flexible API to Unreal Engine’s Blueprint system, empowering developers to implement robust save/load functionality, auto-respawn logic, UI/UX enhancements, and system configuration—entirely through C++ or Blueprints.


Savior API

Asynchronous Methods:

Serializable Interface:

Procedural Interface:

HUD Custom Class:

Slot Methods: