Pull to refresh

Custom structure serialization in Unreal Engine

Level of difficultyMedium
Reading time3 min
Views929
Original author: MMadmer

Introduction

Let’s say you’ve created your own USTRUCT in C++ and now you want to serialize it.

USTRUCT(BlueprintType)
struct FComplexStruct
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FNonSerializableStruct FirstStruct;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FSerializableStruct SecondStruct;
};

Usually, it’s enough to simply mark the desired fields with SaveGame.

USTRUCT(BlueprintType)
struct FComplexStruct
{
    GENERATED_BODY()

    // Doesn't support serialization
    UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame)
    FNonSerializableStruct FirstStruct;

    // Supports serialization
    UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame)
    FSerializableStruct SecondStruct;
};

But here’s the problem — in order for this to work, the fields themselves must support serialization.
Unfortunately, one of our variables doesn’t.

In my case, it’s a struct called FNonSerializableStruct. Because of that, only the second struct gets serialized, even though we marked both with SaveGame.


Solution

So what can we do in this case?
Well, we’ll have to manually serialize our struct.

Thankfully, it’s pretty straightforward.
To do that, we need to add the following code outside the struct, at the global scope of the file:

inline FArchive& operator<<(FArchive& Ar, FComplexStruct& Save)
{
	Save.Serialize(Ar);
	return Ar;
}

template <>
struct TStructOpsTypeTraits<FComplexStruct> : public TStructOpsTypeTraitsBase2<FComplexStruct>
{
	enum
	{
		WithSerializer = true
	};
};

Here, in the templated struct, we’re telling the engine that we want to manually handle serialization for our struct.

The operator<< overload is added just for convenience — so we don’t have to call the Serialize method manually every time we use this struct as a member in another struct or class.

Alright, so what’s next?
We still haven’t actually written our serialization logic.

As I mentioned earlier, we’ll need to add a Serialize method to our struct.
Unreal will call it automatically when needed — thanks to the code we added above.

bool Serialize(FArchive& Ar)
{
	// Serialize in batches as there is no support
    Ar << FirstStruct.Type;
    Ar << FirstStruct.Class;
    Ar << FirstStruct.Object;

    // Serialize the whole, since there is support for it
    Ar << SecondStruct;

	return true;
}
Full code
USTRUCT(BlueprintType)
struct FComplexStruct
{
    GENERATED_BODY()

    // Doesn't support serialization
    UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame)
    FNonSerializableStruct FirstStruct;

    // Supports serialization
    UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame)
    FSerializableStruct SecondStruct;

    bool Serialize(FArchive& Ar)
    {
        // Serialize in batches as there is no support
        Ar << FirstStruct.Type;
    	Ar << FirstStruct.Class;
    	Ar << FirstStruct.Object;

        // Serialize the whole, since there is support for it
    	Ar << SecondStruct;
  
    	return true;
    }
};

inline FArchive& operator<<(FArchive& Ar, FComplexStruct& Save)
{
	Save.Serialize(Ar);
	return Ar;
}

template <>
struct TStructOpsTypeTraits<FComplexStruct> : public TStructOpsTypeTraitsBase2<FComplexStruct>
{
	enum
	{
		WithSerializer = true
	};
};

Improvement

You’ll probably notice that the code for adding serialization support is pretty boilerplate. And you know what that means — we can wrap it in a macro to make adding support for other structs much easier!

#define MAKE_SERIALIZABLE_STRUCT(Name) \
	inline FArchive& operator<<(FArchive& Ar, Name& Save) \
	{ \
		Save.Serialize(Ar); \
		return Ar; \
	} \
	template<> \
	struct TStructOpsTypeTraits<Name> : public TStructOpsTypeTraitsBase2<Name> \
	{ \
		enum  \
		{ \
			WithSerializer = true, \
		}; \
	};

Now we can simply include our macro in a header file and add serialization support to any of our structs in a clean and concise way.

Final code
USTRUCT(BlueprintType)
struct FComplexStruct
{
    GENERATED_BODY()

    // Doesn't support serialization
    UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame)
    FNonSerializableStruct FirstStruct;

    // Supports serialization
    UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame)
    FSerializableStruct SecondStruct;

    bool Serialize(FArchive& Ar)
    {
        // Serialize in batches as there is no support
        Ar << FirstStruct.Type;
    	Ar << FirstStruct.Class;
    	Ar << FirstStruct.Object;

        // Serialize the whole, since there is support for it
    	Ar << SecondStruct;
  
    	return true;
    }
};

MAKE_SERIALIZABLE_STRUCT(FComplexStruct);

Result

Now you can easily add serialization support to your custom structs in Unreal Engine — even if some of their fields don’t support it out of the box.

Tags:
Hubs:
0
Comments0

Articles