Serialization in Unreal Engine 5

Serialization in Unreal Engine 5 is the process of converting an object or data structure into a stream of bytes that can be saved to disk, sent over a network, or stored in memory.

FArchive is the base class for all serialization classes in Unreal Engine. It provides a common interface for reading and writing data from streams. FObjectAndNameAsStringProxyArchive is a derived class that adds support for serializing objects by name instead of by pointer, which can be useful for debugging.

FMemoryWriter and FMemoryReader are subclasses of FArchive that are used for reading and writing to memory. FMemoryWriter provides a buffer that can be used to write data to memory, and FMemoryReader provides a way to read that data back out.

Serializing a custom struct

To serialize a custom struct, you can define an operator<< function for that struct that takes an FArchive object as an argument. The operator<< function should then use the FArchive object to serialize the struct’s data to the stream. For example:

struct FMyCustomStruct
{
    int32 MyInt;
    FString MyString;

    friend FArchive& operator<<(FArchive& Ar, FMyCustomStruct& MyStruct)
    {
        Ar << MyStruct.MyInt;
        Ar << MyStruct.MyString;
        return Ar;
    }
};

Note that the operator<< function is used both for serialization (writing data to a stream) and deserialization (reading data from a stream). When defining an operator<< function for a custom struct, you can use the IsLoading (or IsSaving) provided by the FArchive class to conditionally execute code that is specific to deserialization. For example, you might use IsLoading to allocate memory for a dynamic array only during deserialization.

Saving to disk

To save the serialized data to disk, you can use FFileHelper::SaveArrayToFile, which takes a TArray<uint8> buffer and a file path as arguments. The buffer should contain the serialized data that you want to save to disk. For example:

FMyCustomStruct MyStruct;
MyStruct.MyInt = 123;
MyStruct.MyString = "Hello, world!";

TArray<uint8> Buffer;
FMemoryWriter Writer(Buffer);
Writer << MyStruct;

FFileHelper::SaveArrayToFile(Buffer, *FilePath);

In this example, FMemoryWriter is used to serialize the custom struct to memory, and then the serialized data is saved to disk using FFileHelper::SaveArrayToFile.

To read back the serialized data from a file, you can use FFileHelper::LoadFileToArray to load the data from the file into a TArray<uint8> buffer. Once you have the buffer, you can create an FMemoryReader object and use it to deserialize the data back into your custom struct.

FMyCustomStruct MyStruct;

TArray<uint8> Buffer;
FFileHelper::LoadFileToArray(Buffer, *FilePath);

FMemoryReader Reader(Buffer);
Reader << MyStruct;

More complexe example

// Header: Equipment.h

// Custom struct
USTRUCT(BlueprintType)
struct FEquipmentSpec
{
	GENERATED_BODY()

public:
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	TSubclassOf<AEquipmentBase> Equipment;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	FName BoneAttachmentName;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	EEquipmentSlot Slot = EEquipmentSlot::Primary; // Custom enum
};

// Custom serialization operator
FArchive& operator<<(FArchive& Archive, FEquipmentSpec& EquipmentSpec);


// Implementation: Equipment.cpp

// Custom serialization operator
FArchive& operator<<(FArchive& Archive, FEquipmentSpec& EquipmentSpec)
{
	Archive << EquipmentSpec.Equipment;
	Archive << EquipmentSpec.Slot;
	Archive << EquipmentSpec.BoneAttachmentName;
	return Archive;
}

// Serialize a dummy FEquipmentSpec to an array of byte
TArray<uint8> Equipment::SerializeEquipmentSpec()
{
	// ByteData is the translated data that could be stored in a file
	TArray<uint8> ByteData;
	// Write operator
	FMemoryWriter MemWriter(ByteData);
	
	// Example struct
	FEquipmentSpec Spec;
	Spec.Equipment = ARifleWeapon::StaticClass();
	Spec.Slot = EEquipmentSlot::Primary;
	Spec.BoneAttachmentName = FName("WeaponBoneName");

	FObjectAndNameAsStringProxyArchive Ar(MemWriter, true);
	Ar << Spec;
	
	return ByteData
}

// Serialize a dummy FEquipmentSpec to a file
void Equipment::SerializeEquipmentSpecToFile()
{
	// Example struct
	FEquipmentSpec Spec;
	Spec.Equipment = ARifleWeapon::StaticClass();
	Spec.Slot = EEquipmentSlot::Primary;
	Spec.BoneAttachmentName = FName("WeaponBoneName");

	FArchive* FileWriter = IFileManager::Get().CreateFileWriter(TEXT("MyEquipmentSaveFile.save"));
	FObjectAndNameAsStringProxyArchive Ar(*FileWriter, true);
	Ar << Spec;
	
	// Do write operations
	Ar.Close();
}

The result:

Comments

Leave a Reply

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