Saving and loading actor data in Unreal Engine 4

For a game idea that I was thinking of I needed a saving/loading system that would store all actors that are in a world. I thought of how games like in Roller Coaster Tycoon or the Sims would do this. I quickly came to the conclusion that it would be best to store all data from all objects in a world so you are able to recreate the world in the same state. For this issue I found an easy solution that is both extendable and dynamic.

The solution

I came up with this approach after reading a blog post from user “CyberblastStudios” at the gamedev.net forum about complex saving and loading techniques in UE4. You can create an interface that can be implemented by an actor to mark actors saveable. The save script gets the actors that implement this interface from the world, serializes their data and stores the data into an actor record. This actor record is a struct that stores the fundamental data that is used to instantiate the actor. These actor records are all saved in an array which is stored in the overarching save game object that is serialized and stored on the user’s disk. When the actors are restored, they are injected with the serialized data. An event is fired from the interface after the injection which allows you to put the actor back in its correct state by using the variables retrieved from the binary data.

 

Saving Actors

Making actors saveable

To mark objects in the world as saveable, I created an implementable interface in C++ that can be implemented in Blueprint or C++. The interface I created implements 2 events that will trigger when data is saved or loaded so behaviour can be programmed accordingly.

To allow for variables to be serialized, you can mark them with the “SaveGame” tag in the UProperty macro. Or in Blueprint, tick the “SaveGame” box under the variable’s advanced settings. Only variables marked like this will be serialized when the serialize function is called on the actor.

Saving actor records

I created a struct that holds basic information that is required to spawn an instance of the actor. This struct also holds the serialized data that make up all the saved variables of the actor. I also overloaded the << operator so you can easily archive the record in a binary array. If you want to know more about saving binary data, go and read this tutorial from user “Rama” at the Unreal tutorials page.

Next to the actor records I also create an overarching struct that acts as a save game. The save game contains the array with all the actor records and other relative game data.

 Iterating the saveable actors

To get a list of all actors in the world that implement the interface, you can use a function in the UGameplayStatistics class. We can use the resulting list to iterate over all the saveable actors and convert them into actor records.

Serializing the data

To serialize the actor data to a binary array which can be stored in the actor’s record, we create a struct that inherits from FObjectAndNameAsStringProxyArchive. This class will serialize your objects and prefix the binary data with a string. We do this to ensure that the data won’t become currupted if fields are added or removed.

Now we create a memory writer that writes the bytestream to the actor record’s ActorData array. We use the memory writer in the archive struct from above and pass the archive to the actor’s Serialize function. Afterwards the record is added to an array of records that will later be added to the final save game. If everything is done, the saved event is executed on the actor. Now we can repeat this for all other to-save actors.

After all the actor records are saved, we can create the final save game.

Saving data to file

After we have created the final save file and serialized it, we can store the byte array to a file.

 

Loading Actors

Loading the data from file

To load the binary data

Extracting data from binary

Spawning the actor

Now we iterate over all the actor records in the save file and instantiate them accordingly. During the process we also inject the data and execute the loaded event on the actor.

 

References:

Evanger, J. (2017, January 17). Omplex Saving and Loading Techniques in Unreal Engine 4. Retrieved January 19, 2017, from https://www.gamedev.net/topic/685514-complex-saving-and-loading-techniques-in-unreal-engine-4/

Rama. (n.d.). Interfaces in C. Retrieved January 19, 2017, from https://wiki.unrealengine.com/Interfaces_in_C%2B%2B

W, J. (2014, December 23). UE4ActorSaveLoad. Retrieved January 19, 2017, from https://github.com/shinaka/UE4ActorSaveLoad

Rama. (n.d.). Save System, Read & Write Any Data to Compressed Binary Files. Retrieved January 19, 2017, from https://wiki.unrealengine.com/Save_System,_Read_%26_Write_Any_Data_to_Compressed_Binary_Files

4 Comments

  1. Peter

    Working great for me except one thing, UE is unable to serialize asset references like TAssetPtr and similar. How would one approach to modify this to use FArchiveUObject instead FArchive as that is error message I do get to use FArchiveUObject instead. It might be trivial but I’m not sure how to do it exactly.

      1. Peter

        I did try it and it does not work way I expected… :) Maybe I’m just way to new to C++ to get full grasp of what is going on there. Essentially I would benefit from being able to store that so if you can provide example based on your post code that would be awesome. For now I did solve it in crude way that defy using interfaces by overriding Serialize method on actor and inside converting that info to string path and manually injecting it to archive. I’m not fully happy with that solution as it does defy interface use. If you would have time to extend article/snippet with version that does support TAssetPtr I would be very thankful to you. I’m using your example to store user created track layout and restore it afterwards. Would like to apply the same principle to player vehicle but I need it to be elegant as I do delayed spawn base class and pass in data asset so it can configure itself properly… Thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *