Implementing saveable objects
The full contract
IPulseSaveInterface declares 12 functions. Every one is a BlueprintNativeEvent with a default implementation (return EPulseSaveChoice::Default, 0, false, nullptr, or no-op, as documented on each function in PulseSaveInterface.h), so you only need to override the ones that matter for your object.
| Function | When you need to override it |
|---|---|
GetUniqueSaveIdentifier | Almost always — without a stable identifier the object cannot be saved/loaded. |
ShouldSave | To conditionally exclude an object from saving (e.g. temporary/pooled objects). |
OnSave / OnPostLoad | To copy data between the live object and its model (see Core concepts). |
OnLoad / OnPostSave | For less common symmetric hooks (e.g. resetting state before deserializing). |
IsReadyToSave / IsReadyToLoad | Only for asynchronous data (e.g. waiting on a streamed level or async load). |
OnLoadFailed | To react to a failed load (e.g. destroy a partially-spawned actor). |
GetLoadPriority | To control load ordering relative to other objects. |
GetSaveModelClass | To force a specific model class instead of relying on factory resolution |
| (see Model factories and settings). | |
IsPersistent | To keep this object’s record across saves instead of resetting it every save. |
C++ example
#include "Interfaces/PulseSaveInterface.h"
UCLASS()
class AMySaveableActor : public AActor, public IPulseSaveInterface
{
GENERATED_BODY()
public:
virtual FName GetUniqueSaveIdentifier_Implementation() const override
{
return FName(TEXT("MyActor_01"));
}
virtual void OnSave_Implementation(UPulseSaveManager* SaveManager, UPulseSaveModel* SaveModel) override
{
if (UMyActorModel* Model = Cast<UMyActorModel>(SaveModel))
{
Model->Health = Health;
}
}
virtual void OnPostLoad_Implementation(UPulseSaveManager* SaveManager, UPulseSaveModel* SaveModel) override
{
if (const UMyActorModel* Model = Cast<UMyActorModel>(SaveModel))
{
Health = Model->Health;
}
}
};
Blueprint: same contract
In Blueprint classes that implement PulseSaveInterface, only implement the events you need — unlike native C++, Blueprint events are always optional and fall back to the interface’s default when left unimplemented:
- Get Unique Save Identifier
- Should Save
- On Save
- Is Ready To Save
- On Post Save
- On Load
- Is Ready To Load
- On Post Load
- On Load Failed
- Get Load Priority
- Get Save Model Class
- Is Persistent
Custom save model in C++
#include "PulseSaveModel.h"
UCLASS()
class UMyActorModel : public UPulseSaveModel
{
GENERATED_BODY()
public:
// Declared explicitly instead of relying on bSerializeAllSaveGameProperties — see Core concepts.
UPROPERTY(SaveGame)
float Health = 0.f;
virtual bool OnPreSave() override
{
if (!Super::OnPreSave()) return false;
return true;
}
virtual bool OnPreLoad() override
{
if (!Super::OnPreLoad()) return false;
return true;
}
};
Blueprint models derive from UPulseSaveModel too, and can implement Is Ready to Save / Is Ready to Load events.