UObject::Serialize
- Jul 20, 2024
- 3 min read
The Serialize function from the UObject class is a key feature in the engine that surprisingly few people know about. It isn't talked about much despite how powerful it is for game design. More specifically saving a game smartly.
Serialize is a very simple function that performs two basic tasks.
It takes properties from a UObject and writes them into a TArray<uint8> AKA a byte array.
Or it takes a byte array and uses it to set values on a UObject.
This is how Unreal saves all of it's data. Things are stored in UObjects and serialized both when saving and loading them. The same function is used for both!
Archives
Archives are how Serialize determins what to do. There are two major versions. A Writer and Reader. It's easiest to think of these in relation to the byte array.
A Writer will write data to the byte array from the UObject.
A Reader will read data from the byte array into the UObject.
There is a third class named an ArchiveProxy. This class is an archive in itself, but it has settings for saving differently. In specific to my usual use cases, there is a bArIsSaveGame. But there are dozens of other settings in this class.
Preparing to use Serialize
Prep for use is rather simple. All you really need is to define your own Proxy archive.
struct FAuthaerSaveGameArchive : public FObjectAndNameAsStringProxyArchive
{
FAuthaerSaveGameArchive(FArchive& InInnerArchive) : FObjectAndNameAsStringProxyArchive(InInnerArchive, true)
{
ArIsSaveGame = true;
ArNoDelta = true;
}
};The code above is a simple definition for a SaveGame usable Archive Proxy.
This is all you really need to have. to have an object write itself into a byte array.
Using Serialize
Use is just as simple. Lets start with reading data from an object into the array.
Lets make a function which takes in an Object, and a byte array ref. The intention being to read the data from the object and populate the array of bytes. The thing calling this function can do whatever it likes with the bytes after that.
void UAuthaerSaveGame::SaveData(UObject* Object, TArray<uint8>& Data)
{
if (Object == nullptr) return;
FMemoryWriter MemoryWriter = FMemoryWriter(Data, true);
FAuthaerSaveGameArchive SaveArchive = FAuthaerSaveGameArchive(MemoryWriter);
Object->Serialize(SaveArchive);
}We check if the object is valid of course. Then we create a FMemoryWriter. Remember that this to the byte array. And we want to write to the byte array. Note that this writer takes in the byte array.
We create a proxy archive by passing out writer archive to it. We now have a proxy archive made from our writer archive that has the byte array in it.
The last line is just simply calling Serialize on the object we're reading from, and passing in the archive proxy we made that has our array.
This function ends, and our Data array now has data from the SaveGame marked properties of the Object. Note that this is a base UObject class, therefore it can save your base Objects, ActorComponents, and even Actors.
Moving on to loading now. It is a nearly identical function with a couple of changes.
void UAuthaerSaveGame::LoadData(UObject* Object, const TArray<uint8>& Data)
{
if (Object == nullptr) return;
FMemoryReader MemoryReader(Data, true);
FAuthaerSaveGameArchive Ar(MemoryReader);
Object->Serialize(Ar);
}Comparing to the above function which saves an object you will notice only two differences. The array is const this time, because we don't have any intention of altering it here. And the proxy is taking in a MemoryReader this time.
Again, the Archives relate to the byte array. So a MemoryReader is going to read from the byte array.
Then we call Serialize with that proxy on the object. This populates the object's properties from the byte array.
Notes
The above two functions can very easily be made BlueprintCallable in like a BlueprintFunctionLibrary. Blueprint supports both TArray<uint8> as well as UObject. So with those two basic functions you could quite easily make a mostly blueprint savable game. Though I would also recommend making some form of function set that can spawn actors and create actor components and uobjects with stable names as well if that is your intentions.
It's also worth noting that I didn't bring up naming in this article like I do in my more saving specific saves. The object's name doesn't matter for serialize. You could copy one object into a dozen others with different names if you wanted as all it does is simply finds properties by name, and sets their values from a string.



Comments