Sign in to follow this  
CyberblastStudios

Complex Saving and Loading Techniques in Unreal Engine 4

Recommended Posts

Hey guys! I recently ran into an issue on the game that I am developing at the moment regarding saving and loading in Unreal Engine 4.

Unreal Engine 4 has a built in solution for saving and loading, SaveGame classes. SaveGame classes are a C++ or Blueprint class which the members are serialized and de-serialized for loading and saving. While this is great for simple saving and loading where you would just need to save player position or checkpoint progress, it is not suitable for saving whole actors and lots of them in the level. There is no built in solution for this, so you will need to write a little bit of C++ code to make a saving and loading system that will be able to save the state of many actors in a level.

I used a more or less custom approach to this using Unreal's file system classes and reflection system. Unreal Engine 4 has a reflection system where you can mark C++ members with a UPROPERTY() macro.
UPROPERTY()FVector VarThatShouldBeSaved;
This allows Unreal to hold metadata of your fields in code at run-time, manage pointers, and so on. This will also allow you to specify which members of your UObject based class will be saved and loaded during your save load routine that you write. Now on every member that you want to be saved with the class type, you can put SaveGame into the UPROPERTY() macro.
UPROPERTY(SaveGame)FVector VarThatShouldBeSaved;
This will allow Unreal to see that it should serialize this field when you call the Serialize function on a class of this type.

I mark each actor class type that needs to be serialized by implementing a saveable interface into all the actor classes that will want to be saved and restored in the level.

I made a struct that holds each actors save information like this
USTRUCT()struct FActorSave{GENERATED_USTRUCT_BODY()FName ActorInstanceName;// we can pull the UCLASS from this when we load FString ActorClassName;FTransform ActorInstanceName;// the actual member data that is serialized into a bytestreamTArray<uint8> ActorData;};
To save into a TArray<uint8> you create a struct that inherits from FObjectAndNameAsStringProxyArchive.
struct FActorSaveArchive : public FObjectAndNameAsStringProxyArchive{	FActorSaveArchive (FArchive& InInnerArchive, bool bInLoadIfFindFails)		: FObjectAndNameAsStringProxyArchive(InInnerArchive, bInLoadIfFindFails)	{		ArIsSaveGame = true;		ArNoDelta = true;	}};
This will allow you to serialize members of a class in the file while prefixing the binary data with a string, this helps when you add more or take away saved fields within your class, so your save file data wont be corrupted with new versions of the game!

Then you create a instance of a FMemoryWriter to write your bytestream, and then create an instance of the FActorSaveArchive struct above.
FMemoryWriter ActorWriter = FMemoryWriter(ActorData, true);FActorSaveArchive ActorSerializer(CompWriter, true);
Now you are all set up for saving! Now you can get all actors that implement the Saveable Interface, put some of their common data into a struct of the type FActorSave like the one above, and loop through all those actors and call serialize on them, and pass in your FActorSaveArchive. This will pass in the binary data into the byte array in the FActorSave struct like so:
ActorToSerialize->Serialize(ActorSerializer);
When you have looped through all your actors and saved them all into FActorSave structs. Now you can write all the FActorSaves into the final byte array that will be written to the file. You will need to teach Unreal how to serialize your FActorSave struct. You can do this in two ways. The most efficient way is to use raw binary serialization using an operator overload that takes in a FArchive and a FActorSave. Or you can just mark all your FActorSave fields as UPROPERTY(SaveGame).

Once you have serialized all the FActorSave structs into a byte array, you can go ahead and write this to the file like this:
// SavedByteStream is a TArray<unit8>if (FFileHelper::SaveArrayToFile(SavedByteStream, *FilePath)){	ByteStreamWriter.FlushCache();	SavedByteStream.Empty();	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("succcessful"));	return;}
Then when you load the actors from the disk, you will then reconstruct the FActorSave structs, then you can reconstruct the actors. After spawning the appropriate actors, you can call serialize on them again to inject the data back into their fields. After that, you can notify the actor that it was loaded by calling a function on the interface so the actor can initialize whatever it needs to!

Note that you must save and load all your pieces of data in the same order, otherwise when you load your data, all of your data could be corrupted, be careful!

Rama over at Unreal Engine Wiki has a great post about this as well!

Happy serializing everyone!

Jaden Evanger Edited by CyberblastStudios

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this