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:

  1. Get Unique Save Identifier
  2. Should Save
  3. On Save
  4. Is Ready To Save
  5. On Post Save
  6. On Load
  7. Is Ready To Load
  8. On Post Load
  9. On Load Failed
  10. Get Load Priority
  11. Get Save Model Class
  12. 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.


This site uses Just the Docs, a documentation theme for Jekyll.