Make Save System

Hi i want to create a save system in C++, i use the USaveGame Class to create a class that holds all the variables that i want to save, it works fine but just for Strings, Ints ,… i wan to know how can i save entire Objects/Actors using this method (for saving Inventory Items and more)

Thanks

wrote a really interesting wiki tutorial for saving from UE4.

I would suggest starting there, but you can also use the archives. All of that works with serialization.

Yeah i tried that, but i dont know how to serialize an entire Actor object like a AEnemy

I’ve been away from UE4 for a couple weeks now, and going back to thinking in code is making my brain ache. I’ll get back into the grove, but if everyone would permit me to ask some potentially obvious questions…

So, savegame methods have been on my mind for a while. I see several issues, not only do I need triggers for a player manually hitting a save button, but I need automatic saves going on plus on a mobile platform I need to take great care to trigger a save event when the app is closed or switched. A user having to redo their progress because of a phone call interrupting will just not do. What would the options be in this case? Say I’d need to save player locations and data (health, inventory, etc.), AI status including location and AI status data, map data either for procedural spawn objects or a resource that was used (like a health pickup with no respawn that was used). Then there’s overall data to save like best scores that appear through all games and not necessarily saved in the same slot.

Is there an idea of the best way to perform this, or is it a case of “whichever way works is the best way?” Should I put functions into the savegame class to iterate through all objects, grabbing data and saving to slot? Put a custom trigger into each class that saves it’s own data to the slot, all at the same time like a herd of cats trying to get their face into the food bowl at once? Is there a UE4 function that gets triggered when the game is put into the background, and every object in the game knows to keep looking at this state…or should I just keep saving the game state every 10 seconds and figure that’s good enough?

Perhaps there are so many ways to do this, I don’t even know where to start. Maybe even over complicating the issue, but would love to hear some thoughts about a good process. Thanks in advance!

Edit: Or, should everytime any variable changes should I just save that value? Like on every tick a player and AI position gets saved. Each change in health, every inventory change, etc. Maybe this isn’t as much as a massive I/O overhead as I imagine it to be?

I have yet to see an instance where serializing the whole Actor is necessary. Typically, the elements in the PlayerState or GameState, give or take, will be enough to be able to recreate a given character. The Actor comes with a ton of baggage and things like timers and such wont likely survive deserialization, as its only ever going to be the data that moves between instances. The tutorial wrote basically just asks for you to stick an object in and pull an object out, so unless you can provide me with more clarity in your goals I don’t think I can be much more help. =\

Cool, lets do this!

As above, just trigger it when you want. You can attach the behavior to a volume, an actor and a console command for good measure, all vectors executing the same code.

As my earlier comment was suggesting, you stick the important things into the player state already, so you can serialize it almost straight away. You may want to, however, cache the state in another object that would serve as an intermediary, allowing you some control. AI probably shouldn’t have its status cached, but thats a ball of twine you will have to unravel. The rest of what you are describing here is almost all game events, which are something you will have to make a decision about, but you can cache their position, orientation, timeSinceCreation or whatever in the exact same way you cache values in the context of the playerState.

Yea, don’t do that. Mobile is fragile enough that saving out every object in the world is not likely to be of much benefit, let alone doing it every few seconds. As above, you can set up trigger points to cause the UI to update with a rotating candy bar while you save out the state.

You really dont want to be writing to disc every time you get a tick, replication update or any other context like this. Really if you think your mobile game needs that sort of granularity… I’d rethink the design.

Hi there Bob_Gneu! Great to having you around!

Yes I agree with Bob, you really should not be serializing an entire actor, why would you need to?

You really only need the special properties that you create that are different for your different items, and are the core values that you are watching as part of your gameplay mechanics.

Personally I save mesh and material asset references to hard disk via FName.

I’ve publically posted how you can do this here!

Wiki Tutorial on Dynamic Load Object

You simply save the asset path to hard disk and then can load it at runtime!

Please note you should be dynamically obtaining the asset reference, for use with a packaged game, not relying on the editor’s path.


Alternatively you make blueprints of your different items with their various settings, and save only the values that change as the item interacts with the player and the game world.

For that you'd use my SpawnBP node, and then apply your saved properties/variables that you serialized to hard disk.

**Wiki tutorial on Spawning BP in C++**
https://wiki.unrealengine.com/Templates_in_C%2B%2B#Spawn_Actor_From_Blueprint

The real usefulness of using my C++ binary save system is that you can save literally any data you want, including your own custom C++ data types!

Again my favorite example is how I save in-game screenshots as arrays of FColor to binary file, and then load them as UTexture2D to give every save game in Solus a related screenshot!

:slight_smile:

Thank you Bob_Gneu! Indeed I don’t intend to save everything, just the current state of player and enemy AI at any given moment. Imagine a 2D version of Asteriods for the sake of an example, I have player ship location, rotation and just to make it more interesting a health bar. There can be anywhere from 5 to 20 asteriods onscreen, each with a velocity and a size (small, medium, large). So one way to handle this would be to simply save at the beginning of each level cleared and figure that if the player is interrupted during a session, they simply need to start over.

But what if we want to save the exact state of the game at any moment. So, if I’m reading you right, a possibility here would be to create a separate cache class, maybe that keeps all this information in either real time or say every few seconds. Maybe include a multi-dimensional array for active AI on screen with size and velocity. Player data, etc. Then when a save event is triggered this data in the class gets dumped to a save slot. So, the question here would become one of testing the performance of the target platform to see where a breakpoint would be in performance? As in, 20 asteriods on screen works just fine like this, but 2,000 pushes the hardware over the edge, so if we really wanted a 2,000 asteroids style game we need to rethink real time saving. Sound about right?

@, man you’ve got some excellent stuff. You know, on a side note, I was also looking at GameInstance and getting all sorts of confused, then BAM you post something on the forum about it. Course I’m looking at it and realizing, “Wow, it’s that easy? Seriously?” That was like, perfect timing for my awkward dive back into UE4.

But anyway, I am looking at your binary save system, which is most excellent for sure. So thinking about the importance of reading and writing in the proper order. Does this method handle dynamic arrays? For a basic loadgame system, I figure that I will keep an array of AI, similar to the example above, call the array from the class and cycle through each AI enemy in the list, spawning each into the game world on each loop. Would the binary save system have difficulty here?

I can’t think of a situation where you would need to cache the AI, especially if you are handling things with checkpoints. Just reload, reset and rerun the AI routines. I suppose it could be useful if you had a group of bad guys that had evolved to a certain location, but even then id only cache their position, orientation, inventory ids (maybe ammo) and AI state. Nothing else is really needed.

I realize you were throwing out numbers but, in any situation where you are dealing with 2000+ entities you wont likely want to cache all of that. I could understand an argument being made for it, but really if you have that many physics driven entities its not likely to be made better with a save game. If they are AI driven that’s an entirely different problem, and one that is likely to run your users machine into the ground. Especially on mobile you are likely looking at a few dozen entities, on top of the typical controllers and such.

Try not to get too bogged down in any of this stuff. Make a game thats fun and see what you need instead of trying to over think the saving system.

Haha that’s great to hear Tim!

Think I will go bump that thread so more people see it :slight_smile:

And yes you can serialize Dynamic Arrays very very easily using my save system, you just overload the operator<< and the dynamic array code of UE4 will use your custom overload for each entry!

it works great!

then you can just serialize the array itself!

archive << array of custom type that has a operator<< defined


**Operator Overloads Wiki**

Here again is my wiki on using operator overloads in UE4!

**Epic Wiki Link**
https://wiki.unrealengine.com/Operator_Overloads#FORCEINLINE

I’ve started exploring this topic as well, having started with the most basic C++ documentation at first and moving onto exploring 's compressed binary save system. However, I’m currently having problems getting a custom ALevelScriptActor subclass to compile (the idea is to implement compressed binary saving directly into each level through the level blueprint by subclassing my level blueprint to a custom C++ class that can save the positions and other relevant data of all actors, pickups, etc present in the level). The root of my troubles appears to lie in a faulty understanding of operator overloading.

Here is AutoSavingLevelScriptActor.h:





#pragma once

#include "Engine/LevelScriptActor.h"
#include "AutoSavingLevelScriptActor.generated.h"

/**
 *
 */

class UCustomSaveGame;

//Make as many Unique Overloads as you want!
FORCEINLINE FArchive& operator<<(FArchive &Ar, UCustomSaveGame* SaveGameData )
{
    if(!SaveGameData) return;
    //~
    
    Ar << SaveGameData->NumItemsCollected;  //int32
    Ar << SaveGameData->PlayerLocation;  //FVector
    Ar << SaveGameData->ArrayOfRotationsOfTheStars; //TArray<FRotator>
}

/**
 * 
 */
UCLASS()
class FPSPROJECT_API AAutoSavingLevelScriptActor : public ALevelScriptActor
{
    GENERATED_UCLASS_BODY()
    
    uint32 NumItemsCollected;
    
    TArray<FRotator> ArrayOfRotationsOfTheStars; // Use this for day/night cycle save data
    
    //FArchive is shared base class for FBufferArchive and FMemoryReader
    void SaveLoadData(FArchive& Ar, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray);

    bool SaveGameDataToFile(const FString& FullFilePath, FBufferArchive& ToBinary);
    
    bool SaveGameDataToFileCompressed(const FString& FullFilePath, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray);
    
    bool LoadGameDataFromFileCompressed(const FString& FullFilePath, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray);
    
    UFUNCTION(BlueprintCallable, Category = Data)
    void SaveGame();
    
    UFUNCTION(BlueprintCallable, Category = Data)
    void LoadGame();
};


And here is AutoSavingLevelScriptActor.cpp:





#include "FPSProject.h"
#include "AutoSavingLevelScriptActor.h"


AAutoSavingLevelScriptActor::AAutoSavingLevelScriptActor(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{

}

//SaveLoadData
void AAutoSavingLevelScriptActor::SaveLoadData(FArchive& Ar, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray)
{
    Ar << SaveDataInt32;
    Ar << SaveDataVector;
    Ar << SaveDataRotatorArray;
}

bool AAutoSavingLevelScriptActor::SaveGameDataToFile(const FString& FullFilePath, FBufferArchive& ToBinary)
{
    //note that the supplied FString must be the entire Filepath
    // 	if writing it out yourself in C++ make sure to use the \\
    // 	for example:
    
    // 	FString SavePath = "/MyProject/MySaveDir";
    
    //Step 1: Variable Data -> Binary
    
    //following along from above examples
    // Do this
    FBufferArchive ToBinary;
    SaveLoadData(ToBinary,&NumItemsCollected,&UGameplayStatics::GetPlayerCharacter(GetWorld(),0)->GetActorLocation(),&ArrayOfRotationsOfTheStars);
    //presumed to be global var data,
    //could pass in the data too if you preferred
    
    //No Data
    if(ToBinary.Num() <= 0) return false;
    //~
    
    //Step 2: Binary to Hard Disk
    if (FFileHelper::SaveArrayToFile(ToBinary, * FullFilePath))
    {
        // Free Binary Array
        ToBinary.FlushCache();
        ToBinary.Empty();
        
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("Save Success!"));
        }
        
        //ADebugCameraController::ClientMessage("Save Success!");
        
        return true;
    }
    
    // Free Binary Array
    ToBinary.FlushCache();
    ToBinary.Empty();
    
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("File Could Not Be Saved!"));
    }
    
    //ADebugCameraController::ClientMessage("File Could Not Be Saved!");
    
    return false;
}

//I am using the sample save data from above examples as the data being loaded
bool AAutoSavingLevelScriptActor::LoadGameDataFromFileCompressed(const FString& FullFilePath, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray)
{
    //Load the Compressed data array,
    // 	you do not need to pre-initialize this array,
    //		UE4 C++ is awesome and fills it
    //		with whatever contents of file are,
    //		and however many bytes that is
    TArray<uint8> TheBinaryArray;
    if (!FFileHelper::LoadFileToArray(TheBinaryArray, *FullFilePath))
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("FFILEHELPER:>> Invalid File"));
        }
        
        //ADebugCameraController::ClientMessage("FFILEHELPER:>> Invalid File");
        return false;
        //~~
    }
    
    //Testing
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("Loaded File Size"));
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, FString::FromInt(TheBinaryArray.Num()));
    }
    
    //ADebugCameraController::ClientMessage("Loaded File Size");
    //ADebugCameraController::ClientMessage(FString::FromInt(TheBinaryArray.Num()));
    
    //File Load Error
    if(TheBinaryArray.Num() <= 0) return false;
    
    //~
    //		  Read the Data Retrieved by GFileManager
    //~
    
    FMemoryReader FromBinary = FMemoryReader(TheBinaryArray, true); //true, free data after done
    FromBinary.Seek(0);
    SaveLoadData(&ToBinary,&NumItemsCollected,&UGameplayStatics::GetPlayerCharacter(GetWorld(),0)->GetActorLocation(),&ArrayOfRotationsOfTheStars);
    
    //~
    //								Clean up 
    //~
    FromBinary.FlushCache();
    
    // Empty & Close Buffer 
    TheBinaryArray.Empty();
    TheBinaryArray.Close();
    
    return true;
}

bool AAutoSavingLevelScriptActor::SaveGameDataToFileCompressed(const FString& FullFilePath, int32& SaveDataInt32, FVector& SaveDataVector, TArray<FRotator>& SaveDataRotatorArray)
{
    FBufferArchive ToBinary;
    SaveLoadData(&ToBinary,&NumItemsCollected,&UGameplayStatics::GetPlayerCharacter(GetWorld(),0)->GetActorLocation(),&ArrayOfRotationsOfTheStars);
    
    //Pre Compressed Size
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("~ PreCompressed Size ~"));
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, FString::FromInt(ToBinary.Num()));
    }
    
    //ADebugCameraController::ClientMessage("~ PreCompressed Size ~");
    //ADebugCameraController::ClientMessage(FString::FromInt(ToBinary.Num()));
    
    //
    
    // Compress File
    //tmp compressed data array
    TArray<uint8> CompressedData;
    FArchiveSaveCompressedProxy Compressor =
    FArchiveSaveCompressedProxy(CompressedData, ECompressionFlags::COMPRESS_ZLIB);
    
    //Send entire binary array/archive to compressor
    Compressor << ToBinary;
    
    //send archive serialized data to binary array
    Compressor.Flush();
    
    //
    
    //Compressed Size
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("~ Compressed Size ~"));
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, FString::FromInt(CompressedData.Num()));
    }
    
    //ADebugCameraController::ClientMessage("~ Compressed Size ~");
    //ADebugCameraController::ClientMessage(FString::FromInt(CompressedData.Num()));
    
    
    // if (!GFileManager) return false; // GFileManager no longer exists. TODO: Add an updated check to prevent crash.
    
    //vibes to file, return successful or not
    if (FFileHelper::SaveArrayToFile(CompressedData, * FullFilePath))
    {
        // Free Binary Arrays
        Compressor.FlushCache();
        CompressedData.Empty();
        
        ToBinary.FlushCache();
        ToBinary.Empty();
        
        // Close Buffer
        ToBinary.Close();
        
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("File Save Success!"));
        }
        
        //ADebugCameraController::ClientMessage("File Save Success!");
        
        return true;
        //
    }
    else
    {
        // Free Binary Arrays 
        Compressor.FlushCache();
        CompressedData.Empty();
        
        ToBinary.FlushCache();
        ToBinary.Empty();
        
        // Close Buffer 
        ToBinary.Close();
        
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("File Could Not Be Saved!"));
        }
        
        //ADebugCameraController::ClientMessage("File Could Not Be Saved!");
        
        return false;
        //
    }
}

/*
 * User-side save and load game methods, for F6 and F9 respectively
 */

void AAutoSavingLevelScriptActor::SaveGame()
{
    // Call the appropriate saving method here.
}

void AAutoSavingLevelScriptActor::LoadGame()
{
    // Call the appropriate loading method here.
}


All of the above code is an implementation of code from 's tutorial in the wiki. The problems as things stand now are as follows:

AutoSavingLevelScriptActor.h has an error saying non-void function (null) should return a value, most likely in FORCEINLINE FArchive& operator<<(FArchive &Ar, UCustomSaveGame* SaveGameData ). Said method is taken from the aforementioned wiki tutorial.

Other compiler errors are due to pointer issues that I’m currently working on.

I can make my project compile by commenting out the bulk of my AAutoSavingLevelScriptActorClass, which means I must use my simple save system based on a USaveGame subclass. My main issue is figuring out how to get all of the relevant data to my USaveGame subclass without having to resort to a custom Level Blueprint superclass, since my simple saving system is actuated through code in my ACharacter subclass. All feedback will be much appreciated.

Welcome to the forums, Alonso! :smiley:

What Chance said!

Welcome to the forums!!!

:slight_smile:


**Non Void Function Must Return Value**

Here's the fix for this!

This:



```


//Make as many Unique Overloads as you want!
FORCEINLINE FArchive& operator<<(FArchive &Ar, UCustomSaveGame* SaveGameData )
{
    if(!SaveGameData) return;
    //~
    
    Ar << SaveGameData->NumItemsCollected;  //int32
    Ar << SaveGameData->PlayerLocation;  //FVector
    Ar << SaveGameData->ArrayOfRotationsOfTheStars; //TArray<FRotator>
}


```



Becomes this:



```


//Make as many Unique Overloads as you want!
FORCEINLINE FArchive& operator<<(FArchive &Ar, UCustomSaveGame* SaveGameData )
{
    if(!SaveGameData) return;
    //~
    
    Ar << SaveGameData->NumItemsCollected;  //int32
    Ar << SaveGameData->PlayerLocation;  //FVector
    Ar << SaveGameData->ArrayOfRotationsOfTheStars; //TArray<FRotator>

    return Ar;
}


```

Thank you for the warm welcome guys!

I’ve implemented 's FORCELINE function fix, but it complained about requiring a non-void return type on the first return statement. So I attempted to fix it this way:



//Make as many Unique Overloads as you want!
FORCEINLINE FArchive& operator<<(FArchive &Ar, UCustomSaveGame* SaveGameData )
{
    if(!SaveGameData) return static_cast<FArchive&>(nullptr);
    //~
    
    Ar << SaveGameData->NumItemsCollected;  //int32
    Ar << SaveGameData->PlayerLocation;  //FVector
    Ar << SaveGameData->ArrayOfRotationsOfTheStars; //TArray<FRotator>
    
    return Ar;
}


But the above code still returns the compile error “non-const lvalue reference to type ‘FArchive’ cannot bind to a temporary of type ‘nullptr_t’”. The same is true even if I give it just says if(!SaveGameData) return nullptr;, indicating that I’m mishandling the cast to nullptr in cases where there is no SaveGameData being passed into the operator overload function. The wonders of switching from C# to C++…

It seems that TArray<> doesn’t have a Close() method anymore.



'Close' : is not a member of 'TArray<uint8,FDefaultAllocator>'


Any potential harm here? I would guess not, since an array in something completely unrelated to file descriptors. (I wonder why there was one in the first place though).

Hi Alonzo!

I’m a bit surprised with 's solution as, in C++, operator<< must return a valid object, usually the first parameter of the overloaded function, the object stream. Whether it is used in an Unreal environment or not should not change the rules of C++. :slight_smile: The empty return statement at the beginning is invalid as is yours.

This is how I would do it:



FORCEINLINE FArchive& operator<<(FArchive &Ar, UCustomSaveGame* SaveGameData )
{
    if (SaveGameData)
    {
      
        Ar << SaveGameData->NumItemsCollected;  //int32
        Ar << SaveGameData->PlayerLocation;  //FVector
        Ar << SaveGameData->ArrayOfRotationsOfTheStars; //TArray<FRotator>
    }
    
    return Ar;
}


This permits streams like the one above to been daisy-chained as each << operator feeds on the output of its predecessor. The return value is what permits the archive to work as a stream. The code above is the same as:



Ar << SaveGameData->NumItemsCollected << SaveGameData->PlayerLocation << SaveGameData->ArrayOfRotationsOfTheStars;


So a return value is mandatory. If you test the validity of the pointer and the pointer is NULL, like in the code above, the stream is unaffected; nothing is added to Ar. It is simply passed along the next value to stream.

My problem with this code is that if a test fails then there is no way for a reader function to know whether the content was stored in the first place. I prefer passing objects by reference without any pointer conversion or testing at all.



FORCEINLINE FArchive& operator<<(FArchive &Ar, UCustomSaveGame& SaveGameData)
{
    Ar << SaveGameData.NumItemsCollected;  //int32
    Ar << SaveGameData.PlayerLocation;  //FVector
    Ar << SaveGameData.ArrayOfRotationsOfTheStars; //TArray<FRotator>
 
    return Ar;
}


But do not use **const ** on the reference because Ar does not use const itself and it wouldn’t help the reader function, of course.

Hi guys,

Let’s say, just for the heck of it, I had a good reason to want to serialize my actors to an array of bytes – in parallel to what is already happening (replication). I tried using FObjectWriter but it crashes the engine. It seems like I am interfering with the replication functionality. Is there any way around this?

Edit: I think I’m getting it. Serializing isn’t about replication. So if I serialized a skeletal mesh component, I wouldn’t be storing the bone positions and such. I’d be storing mesh data and more. I just want to get a copy of whatever is being sent to the clients. In particular, I need the data that syncs up animations.

Cheers,