[PLUGIN] Savior

Works perfect, thanks for the quick update!

Hi, I have a weird issue. After loading, a pointer from a saved object (GameMode) to another saved object (DeliveryPackage) is null. I spawn 3 DeliveryPackages and save a reference to the last one. If I use “Resolved Name to GUID”, on load it offsets the GUID I need by 1. If I use “Static Name to GUID”, on load the GUIDs for the 3 packages are applied in reverse order.

  • My GameMode implements the SAVIOR_Serializable interface and has a GUID called SGUID. It gets the same GUID after each load.
  • GameMode has a DebugLastPackage UPROPERTY marked as SaveGame.
  • DebugLastPackage is a pointer to a DeliveryPackage and is set when I spawn it with a key. 3 DeliveryPackages are spawned and I save a reference to the last one.
  • DeliveryPackage implements SAVIOR_Serializable and SAVIOR_Procedural. it has a GUID called SGUID, which is set in construction script. It is saved and loaded into the world correctly.
  • Enabling debug logs, I can see that GameMode is serialized and saves the reference to the DeliveryPackage in DebugLastPackage. Its GUID matches the one I expect. The package is saved as well.
  • If I use “Static Name to GUID”, On load, I can see that GameMode and DebugLastPackage are deserialized. The logs show the correct GUID. However because I’m also printing the GUIDs in the construction script, I can see that it’s the first spawned DeliveryPackage that has this GUID, not the last. The pointer is still null.
  • If I use “Resolved Name to GUID”, when I restore the third package, its GUID is off by 1. The memory address option doesn’t work either.

Here are the logs of spawning, saving and loading using “Static Name to GUID”:

# me spawning 3 DeliveryPackages and printing their GUIDs
[2024.08.31-03.27.54:259][765]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_0] 000002390000020A0000023000000200
[2024.08.31-03.27.54:260][765]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_1] 000002BF0000020E00000232000001A2
[2024.08.31-03.27.54:260][765]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_2] 000002C9000001F700000209000001F3

# serializing the 3 DeliveryPackages
[2024.08.31-03.28.40:535][538]SaviorLog: {S}:: Serialized Actor :: /BP_ISDeliveryPackage_C PersistentLevel.BP_ISDeliveryPackage_000002C9000001F700000209000001F3
[2024.08.31-03.28.40:535][538]SaviorLog: {S}:: SAVED DATA for PersistentLevel_BP_ISDeliveryPackage_000002C9000001F700000209000001F3 :: {"bCanBeDamaged":true}
[2024.08.31-03.28.40:535][538]SaviorLog: {S}:: Serialized Actor :: /BP_ISDeliveryPackage_C PersistentLevel.BP_ISDeliveryPackage_000002BF0000020E00000232000001A2
[2024.08.31-03.28.40:535][538]SaviorLog: {S}:: SAVED DATA for PersistentLevel_BP_ISDeliveryPackage_000002BF0000020E00000232000001A2 :: {"bCanBeDamaged":true}
[2024.08.31-03.28.40:535][538]SaviorLog: {S}:: Serialized Actor :: /BP_ISDeliveryPackage_C PersistentLevel.BP_ISDeliveryPackage_000002390000020A0000023000000200
[2024.08.31-03.28.40:535][538]SaviorLog: {S}:: SAVED DATA for PersistentLevel_BP_ISDeliveryPackage_000002390000020A0000023000000200 :: {"bCanBeDamaged":true}

# serializing the gamemode and the DebugLastPackage pointer
[2024.08.31-03.28.40:537][539]SaviorLog: {S}:: Serialized Actor :: /BP_ISGameMode_C PersistentLevel.BP_ISGameMode_00000266000001A6000001DD00000175
[2024.08.31-03.28.40:537][539]SaviorLog: {S}:: SAVED DATA for PersistentLevel_BP_ISGameMode_00000266000001A6000001DD00000175 :: {"Cash":20,"DebugLastPackage":{"FullName":"/BP_ISDeliveryPackage_C PersistentLevel.BP_ISDeliveryPackage_000002C9000001F700000209000001F3","ClassPath":"/Game/IS/Delivery/BP_ISDeliveryPackage.Default__BP_ISDeliveryPackage_C"},"Inventory":{"FullName":"/BP_ISGameMode_C PersistentLevel.BP_ISGameMode_00000266000001A6000001DD00000175_Inventory","ClassPath":"/Script/Angelscript.Default__ISContainerC"},"bCanBeDamaged":false}

# deserializing the gamemode and the DebugLastPackage pointer
[2024.08.31-03.30.52:755][539]SaviorLog: {S}:: UNPACKED DATA for BP_ISGameMode_00000266000001A6000001DD00000175 :: {"Cash":20,"DebugLastPackage":{"FullName":"/BP_ISDeliveryPackage_C PersistentLevel.BP_ISDeliveryPackage_000002C9000001F700000209000001F3","ClassPath":"/Game/IS/Delivery/BP_ISDeliveryPackage.Default__BP_ISDeliveryPackage_C"},"Inventory":{"FullName":"/BP_ISGameMode_C PersistentLevel.BP_ISGameMode_00000266000001A6000001DD00000175_Inventory","ClassPath":"/Script/Angelscript.Default__ISContainerC"},"bCanBeDamaged":false}
[2024.08.31-03.30.52:755][539]SaviorLog: {S}:: Deserialized :: BP_ISGameMode_00000266000001A6000001DD00000175

# deserializing the 3 DeliveryPackages and printing their GUIDs. Note that the printed GUIDs are reversed compared to what they're deserializing:
[2024.08.31-03.30.52:759][539]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_0] 000002390000020A0000023000000200
[2024.08.31-03.30.52:760][539]SaviorLog: {S}:: UNPACKED DATA for BP_ISDeliveryPackage_000002C9000001F700000209000001F3 :: {"bCanBeDamaged":true}
[2024.08.31-03.30.52:760][539]SaviorLog: {S}:: Deserialized :: BP_ISDeliveryPackage_000002C9000001F700000209000001F3
[2024.08.31-03.30.52:820][539]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_1] 000002BF0000020E00000232000001A2
[2024.08.31-03.30.52:820][539]SaviorLog: {S}:: UNPACKED DATA for BP_ISDeliveryPackage_000002BF0000020E00000232000001A2 :: {"bCanBeDamaged":true}
[2024.08.31-03.30.52:820][539]SaviorLog: {S}:: Deserialized :: BP_ISDeliveryPackage_000002BF0000020E00000232000001A2
[2024.08.31-03.30.52:882][539]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_2] 000002C9000001F700000209000001F3
[2024.08.31-03.30.52:882][539]SaviorLog: {S}:: UNPACKED DATA for BP_ISDeliveryPackage_000002390000020A0000023000000200 :: {"bCanBeDamaged":true}
[2024.08.31-03.30.52:882][539]SaviorLog: {S}:: Deserialized :: BP_ISDeliveryPackage_000002390000020A0000023000000200

Here are the logs of spawning, saving and loading using “Resolved Name to GUID”:

# me spawning 3 DeliveryPackages and printing their GUIDs
[2024.08.31-03.33.44:632][ 43]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_0] 000002390000020A0000023000000200
[2024.08.31-03.33.44:632][ 43]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_1] 000002BF0000020E00000232000001A2
[2024.08.31-03.33.44:632][ 43]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_2] 000002C9000001F700000209000001F3

# serializing the 3 DeliveryPackages
[2024.08.31-03.34.23:113][698]SaviorLog: {S}:: Serialized Actor :: /BP_ISDeliveryPackage_C PersistentLevel.BP_ISDeliveryPackage_000002C9000001F700000209000001F3
[2024.08.31-03.34.23:113][698]SaviorLog: {S}:: SAVED DATA for PersistentLevel_BP_ISDeliveryPackage_000002C9000001F700000209000001F3 :: {"bCanBeDamaged":true}
[2024.08.31-03.34.23:113][698]SaviorLog: {S}:: Serialized Actor :: /BP_ISDeliveryPackage_C PersistentLevel.BP_ISDeliveryPackage_000002BF0000020E00000232000001A2
[2024.08.31-03.34.23:113][698]SaviorLog: {S}:: SAVED DATA for PersistentLevel_BP_ISDeliveryPackage_000002BF0000020E00000232000001A2 :: {"bCanBeDamaged":true}
[2024.08.31-03.34.23:113][698]SaviorLog: {S}:: Serialized Actor :: /BP_ISDeliveryPackage_C PersistentLevel.BP_ISDeliveryPackage_000002390000020A0000023000000200
[2024.08.31-03.34.23:113][698]SaviorLog: {S}:: SAVED DATA for PersistentLevel_BP_ISDeliveryPackage_000002390000020A0000023000000200 :: {"bCanBeDamaged":true}

# serializing the gamemode and the DebugLastPackage pointer
[2024.08.31-03.34.23:117][699]SaviorLog: {S}:: Serialized Actor :: /BP_ISGameMode_C PersistentLevel.BP_ISGameMode_00000266000001A6000001DD00000175
[2024.08.31-03.34.23:117][699]SaviorLog: {S}:: SAVED DATA for PersistentLevel_BP_ISGameMode_00000266000001A6000001DD00000175 :: {"Cash":20,"DebugLastPackage":{"FullName":"/BP_ISDeliveryPackage_C PersistentLevel.BP_ISDeliveryPackage_000002C9000001F700000209000001F3","ClassPath":"/Game/IS/Delivery/BP_ISDeliveryPackage.Default__BP_ISDeliveryPackage_C"},"Inventory":{"FullName":"/BP_ISGameMode_C PersistentLevel.BP_ISGameMode_00000266000001A6000001DD00000175_Inventory","ClassPath":"/Script/Angelscript.Default__ISContainerC"},"bCanBeDamaged":false}

# deserializing the gamemode and the DebugLastPackage pointer
[2024.08.31-03.36.26:118][369]SaviorLog: {S}:: UNPACKED DATA for BP_ISGameMode_00000266000001A6000001DD00000175 :: {"Cash":20,"DebugLastPackage":{"FullName":"/BP_ISDeliveryPackage_C PersistentLevel.BP_ISDeliveryPackage_000002C9000001F700000209000001F3","ClassPath":"/Game/IS/Delivery/BP_ISDeliveryPackage.Default__BP_ISDeliveryPackage_C"},"Inventory":{"FullName":"/BP_ISGameMode_C PersistentLevel.BP_ISGameMode_00000266000001A6000001DD00000175_Inventory","ClassPath":"/Script/Angelscript.Default__ISContainerC"},"bCanBeDamaged":false}
[2024.08.31-03.36.26:118][369]SaviorLog: {S}:: Deserialized :: BP_ISGameMode_00000266000001A6000001DD00000175

# deserializing the 3 DeliveryPackages and printing their GUIDs. Note that the last printed GUID is off by one, and the IDs are reversed as well
[2024.08.31-03.36.26:123][369]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_0] 000002390000020A0000023000000200
[2024.08.31-03.36.26:124][369]SaviorLog: {S}:: UNPACKED DATA for BP_ISDeliveryPackage_000002C9000001F700000209000001F3 :: {"bCanBeDamaged":true}
[2024.08.31-03.36.26:124][369]SaviorLog: {S}:: Deserialized :: BP_ISDeliveryPackage_000002C9000001F700000209000001F3
[2024.08.31-03.36.26:186][369]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_1] 000002BF0000020E00000232000001A2
[2024.08.31-03.36.26:186][369]SaviorLog: {S}:: UNPACKED DATA for BP_ISDeliveryPackage_000002BF0000020E00000232000001A2 :: {"bCanBeDamaged":true}
[2024.08.31-03.36.26:186][369]SaviorLog: {S}:: Deserialized :: BP_ISDeliveryPackage_000002BF0000020E00000232000001A2
[2024.08.31-03.36.26:247][369]LogBlueprintUserMessages: [BP_ISDeliveryPackage_C_2] 000002CA000001F80000020A000001F4
[2024.08.31-03.36.26:249][369]SaviorLog: {S}:: UNPACKED DATA for BP_ISDeliveryPackage_000002390000020A0000023000000200 :: {"bCanBeDamaged":true}
[2024.08.31-03.36.26:249][369]SaviorLog: {S}:: Deserialized :: BP_ISDeliveryPackage_000002390000020A0000023000000200

Am I using this wrong or is it a bug?

We can’t control the order in which Unreal spawns objects.
Even if the array is in correct order, you can never guarantee that object A is already valid while spawning B or C.

This is a very long forum topic, but some developer came across this issue before, what they have done was using the “On Spawned” event of the object which then it injects itself to the reference pointing to it (by comparing its own SGUID value) then after all loading is done, the correct inventory object is correctly restored to the variable pointing to it.

The plugin already does this, but again, when ‘myPointer = B’ is applied by the plugin, there is no guarantee that object B have already spawned (thus it can come as NULL value ).

This is why some devs use the “On Spawned” event in the object itself to fix it.

Ah okay, thanks. So is the issue that GameMode is being loaded/spawned first, and its pointers populated first, before the DeliveryPackages are spawned?

Is it possible to change it to a two-pass restore, where first all the actors being loaded are spawned, then second all their pointers are updated?

EDIT: I just tested this by calling the “Load GameMode” function after the initial “Load World” - it works! It restores the pointer. I now just need to test one of the other functions that loads all actor properties without loading the world, and I can hopefully use that as the “second pass” to restore pointers.

1 Like

Yes, exactly.
A dev have split the loading in two steps and he posted his blueprint here in this thread…
But it was some year ago and I honestly can’t find where his posts are :frowning:

I can’t really split it internally in the plugin, because there’s no way to predict what dev is referencing from what.

I just need to run the “restore all properties on actors that exist in the level” process again, after the “Load World” function has spawned them . Like “Load Game Mode” but for all actors in the save file (just restoring their properties without actually spawning them). Is there a function that does that?

EDIT: All good, I just pulled the main loop of the DeserializeLevel() function into my own function to iterate through all objects and call LoadActor/LoadComponent.

Nice work on this plugin btw, the code is nice to follow.

1 Like

Hello :wave:

I want to let you all know that the development of Savior v5 has started.
It will be updated to support major UE5 features now that are more stable.

Its release is planned for Unreal Engine 5.5 onwards, and existing owners of this plugin will not be charged anything extra for the update.
If you are reading this and are thinking about it but haven’t gotten this tool just yet, please note that with major plugin releases, the price always goes up a little bit. So it’s no different this time. The v5 will have its price updated on its release date.

Here are a few things that are currently being worked on:

As before, you can either execute the “Save Game World” function or call any of the runtime nodes separately for custom save processes.

If you have any requests for built-in save data that you need, or good-to-haves, I’m open for suggestions while this is still in the works. Thanks!

1 Like

Hey, Thank you very much for 5.5 support!
If there could be full overview with the newest cool features in the demo also Videotutorials/Screens or comments in the blueprints / code how to proper use Savior for certain examples nad how to build saving pyramid to not get lost and asking milion questions.
Would be Outstanding!
If i can suggest integrations:

  • Gameplay Ability System
  • World partition Demo example
  • 2 different connected Maps example that teleport to other level and all data is paired between.
  • example of blueprint inventory use - draw weapon and saving state that weapon is in the player hand also item is moved to another inventory (equipment) of character.
  • placed item from player inventory into the level (buildable) and harvested actor(foliage) to the player inventory with time loop to respawn (time is also saved) that would be cool for world partition setting :slight_smile:
  • menu and graphics,input options saving example
  • new modular character skeletal Mesh merging(5.5) in equipment example.
    All the best!
1 Like

Built-in support for Attribute Sets in the Gameplay Ability System have been implemented.
The properties have to be marked in the attribute set to be saved, example:

class UMyAbilityAttributeSet : public UAttributeSet
{
	UPROPERTY(Category = "Attributes", BlueprintReadOnly, SaveGame)
	FGameplayAttributeData Health;
	ATTRIBUTE_ACCESSORS(UMyAbilityAttributeSet , Health)
}
1 Like

Hi everyone,

I’m trying to make the plugin work on my project. I’ve set up a BP class to inherit both savior interfaces, created the “Destroyed” bool and the SGUID variable (both flagged as save game). The issue is if I delete / spawn at runtime and save ( save game instance node) and load again, my changes are not saved. Any idea why is not working? I’m using 5.4.

1 Like

Hi.
“Save Game Instance” node will only save properties from the Game Instance blueprint.

If you want to save properties of every actor placed in the 3d world, most likely you want to use “Save/Load Game World” instead (it also executes the Save Game Instance while at it).

1 Like

Thanks! worked like a charm! Just another question. Is it possible to save / load specific variables of the actor or its components?

kind regards,

Alex.

1 Like

You have to take a look at the plugin docs, it’s linked on the store page and contains links for frequent questions.

There you will find info about the “Save Game” tag on variables and how to use actor GUID.

1 Like

Hey,
there is also new Plugin /Demo ive mentioned for skeletal meshes avaiable
Mutable character
https://www.fab.com/pl/listings/209e82f6-ad40-4253-b565-d2f65b12efe7

1 Like

I will take a look at the demo as soon as I can breath a little (end of year rush at work)

1 Like

Any suggestions for saving UObject? When saving an UObject, its properties cannot be saved.

If your UObject instance’s base class is created from a C++ base class,
on its properties you can mark them with the ‘SaveGame’ tag, in the demo project there’s a sample game inventory based on UObjects classes that does that.

Does uobject support saving TInstancedStruct? I tried to save it but failed, but I can save it successfully in the component

Usually the type must be exposed to the reflection system.

Or you would need to build a USTRUCT wrapper for it. (or a manual serialization if you override the UObect’s Serialize method)

After many attempts, I found that the “TInstancedStruct” type can only be serialized successfully in “TArray”. Is this intentional?