How to/Can you store an Object into a SaveGame?

Hi all,

To give context to my situation, I currently have the GameInstance create an “Account” object that stores some loot-based info related to the player. Currently this just consists of the Equipped items as well as the player’s inventory, and some useful functions to add loot into the Account.

I tried saving said “Account” object into a save game, but every time I try to retrieve it upon loading a save game just returns with an invalid object. Is this because it’s being cleaned up by garbage collection upon shutdown?

I was curious if it’s possible to store an Object into a savegame, or is a Struct the most complex thing you can store?

1 Like

I’m pretty sure you can’t save objects. The save game only does variables, including structs.

You will likely want to add a getter and setter for saved data (struct) on your objects. Then you can do this:

UYourSavedGame* YourSavedGame = Cast<UYourSavedGame>(UGameplayStatics::CreateSaveGameObject(UYourSavedGame::StaticClass()));
YourSavedGame->YourObjectsData.Add(YourObject->GetSaveData);
UGameplayStatics::SaveGameToSlot(YourSavedGame, TEXT("SavedGame"), 0);

I am somewhat certain that there is a way to directly convert objects to savegame compatible data just by taking their properties marked as “can be saved”, some property flag thingy, but that would rarely be useful. There are so many things you can not store, or do not want to store, in a property on an object.

You can. But it requires some setup in C++ and Blueprint Implementations.

Start off by making a C++ Blueprint Function Library.

Function Library.h

#pragma once

#include "Kismet/BlueprintFunctionLibrary.h"
#include "ObjectData.h"
#include "SerializationBPLibrary.generated.h"

class FBufferArchive;
class FMemoryReader;

USTRUCT(BlueprintType)
struct FObjectData
{
    GENERATED_BODY()
    
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Serialization")
    TSubclassOf<UObject> ObjectClass;

    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category= "Serialization")
	TArray<uint8> Data;
    
    friend FArchive& operator << (FArchive& Ar, FObjectData& Object)
    {
        Ar << Object.Data;
        return Ar;
    }
};


UCLASS()
class USerializationBPLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_UCLASS_BODY()
    
public:

    UFUNCTION(BlueprintCallable, Category = "Serialization|Saving")
    static bool ObjectSerialize(TArray<uint8>& OutSerializedData, UObject* InObject);

    UFUNCTION(BlueprintCallable, Category = "Serialization|Saving")
    static bool ApplySerialization(const TArray<uint8>& SerializedData, UObject* InObject);
};

FunctionLibrary.cpp

#include "SerializationBPLibrary.h"
#include "Serialization/BufferArchive.h"
#include "Serialization/MemoryWriter.h"
#include "Serialization/MemoryReader.h"
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "Runtime/Engine/Public/EngineGlobals.h"
#include "Misc/FileHelper.h"
 
USerializationBPLibrary::USerializationBPLibrary(const FObjectInitializer &ObjectInitializer)
 : Super(ObjectInitializer)
{
}


bool USerializationBPLibrary::ObjectSerialize(TArray<uint8>& OutSerializedData, UObject* InObject)
{
       if (!IsValid(InObject))
     {
         return false;
     }
 
     FMemoryWriter Writer(OutSerializedData, true);
     FObjectAndNameAsStringProxyArchive Archive(Writer, true);
     Writer.SetIsSaving(true);
     InObject->Serialize(Archive);
     return true;
 }
  
bool USerializationBPLibrary::ApplySerialization(const TArray<uint8>& SerializedData, UObject* InObject)
 {
     if (!InObject || !IsValid(InObject) || SerializedData.Num() <= 0)
     {
         return false;
     }
     
     FMemoryReader ActorReader(SerializedData, true);
     FObjectAndNameAsStringProxyArchive Archive(ActorReader, true);
     ActorReader.SetIsLoading(true);
     
     InObject->Serialize(Archive);

           return true;
}

Then you make a Blueprint Function Library.
You want to add 2 functions.

then

THE NEXT STEP IS VERY IMPORTANT!

Any property you wish to be saved needs to have the “Save Game” flag set to true.

and If you have problems with structures in Objects this wont be enough. You need to go into the actual struct and add the “Save Game” flag there too.

you should then make an array of “Object Data” in the Save Game Object for what you want to Load.
SaveGame

Now you should be able to call the Create Object from Package function and recreate the UObject with the properties it had when it was saved.

It really is all you need. I use it in my game where I have procedurally generated loot, dozens of different Item Classes. Works in packaged games, standalone, pie, and even on a dedicated server.

Something to note though. We wrote a patch tool into our version of this. If you change the variables of the item you saved, applying the serialization will fail. Adding new properties, no problem. Modifying IDs of old properties will not work. So we save our “Build Version” and it patches the serialization to the modified versions of the objects when we need too. This should not be a problem during development, but once you release keep it in mind.

The truth is, structs are very easy to implement and save. However, they have a scaling problem. Nested Structures tend to be unstable in Blueprint specifically, where you should be spending most of your time. If its simple stuff, go for it. If you are making an RPG, structures will not be able to scale with you without turning into a maintenance nightmare where patches break more stuff than they fix. Trust me. We’ve tried.

Hi, thanks for sharing the detailed info. About the nested structs problem in BP, does it apply to C++ defined structs too? I defined all my structs in C++ but used them a lot in BP.

Also I’m considering using UObjects for my save game as they support polymorphism(USTRUCT doesn’t support pointers!) and can change their properties at game time(which UDataAssets can’t do)

C++ structs do not have the same problem. I am not going to pretend to know why but something in unreal does not handle redirectors to structs very well. With C++ those redirectors are not necessary, so I have not experienced any issues in C++.

EDIT: I shouldn’t say Unreal doesn’t handle the redirectors well. What I should say is making changes to structs that are nested inside other structs does not recompile the parent struct, but also does not throw any errors. It will make you think the code is working as intended… Until you go to package your project… You have to go through and edit structs from bottom to top to refresh them. It’s just a pain and C++ is worth it when you can. But if you have parent classes that you reference in the struct and converting to C++ is not feasible, you are stuck with this problem. This is why I recommend making all parent classes C++, even if they are empty.

avoid blueprints as much as you can is all I want to say. Structs are just the 0.001% of how they corrupt all the time. I tried a bunch of ways to redirect structs some time ago before just redoing it in c++. The engine won’t give feedback and will silently screw up.

1 Like