게임 엔진/Unreal

[Unreal] [Example] 객체 직렬화 방법

AlgorFati 2020. 6. 25. 23:02

Serialization (Memory Archive)

언리얼에서 제공하는 기본적인 메모리 직렬화 객체 FMemoryArchive를 활용한 예제이다.

언리얼에서는 메모리 읽기, 쓰기 기능을 지원하기 위해 FMemoryArchive를 상속하여 FMemoryReader, FMemoryWriter 형태로 제공한다.

#include "Serialization/MemoryWriter.h"
#include "Serialization/MemoryReader.h"

void SerializationExamples::BaseExample()
{
	UE_LOG(LogTemp, Log, TEXT("BaseExample  Begin"));

	// write data to memory
	TArray<uint8> BufferArray;
	FMemoryWriter Writer(BufferArray);

	{
		uint32 A = 15;
		uint32 B = 75;
		FString C = TEXT("blabla");

		Writer << A;
		Writer << B;
		Writer << C;
		UE_LOG(LogTemp, Log, TEXT("BufferArray Num : %d"), BufferArray.Num());
	}

	// read data from memory
	FMemoryReader Reader(BufferArray);

	{
		uint32 A;
		uint32 B;
		FString C;

		Reader << A;
		Reader << B;
		Reader << C;

		UE_LOG(LogTemp, Log, TEXT("A : %u  B : %u  C : %s"), A, B, *C);
	}


	UE_LOG(LogTemp, Log, TEXT("BaseExample  End"));
}

 

Struct Serialization

구조체도 직렬화 로직을 정의해 준다면 같은 형태로 직렬화할 수 있다.

 

struct SerializationExampleData
{
	uint32 A;
	uint32 B;
	FString C;

	// for read / write serialization for this struct
	friend FArchive& operator<<(FArchive& Ar, SerializationExampleData& Data)
	{
		Ar << Data.A;
		Ar << Data.B;
		Ar << Data.C;
		return Ar;
	}
};

 

void SerializationExamples::StructExample()
{
	UE_LOG(LogTemp, Log, TEXT("StructSerializeExample  Begin"));

	TArray<uint8> BufferArray;
	FMemoryWriter Writer(BufferArray);

	{
		SerializationExampleData Data;
		Data.A = 99;
		Data.B = 13;
		Data.C = TEXT("blabla");

		Writer << Data;
		UE_LOG(LogTemp, Log, TEXT("BufferArray Num : %d"), BufferArray.Num());
	}


	FMemoryReader Reader(BufferArray);

	{
		SerializationExampleData Data;
		Reader << Data;
		UE_LOG(LogTemp, Log, TEXT("Data A : %u  Data B : %u  Data C : %s"), Data.A, Data.B, *Data.C);
	}


	{
		// need to seek to use again
		Reader.Seek(0);
		SerializationExampleData Data;
		Reader << Data;
		UE_LOG(LogTemp, Log, TEXT("Data A : %u  Data B : %u  Data C : %s"), Data.A, Data.B, *Data.C);
	}


	{
		// this will not work properly
		SerializationExampleData Data;
		Reader << Data;
		UE_LOG(LogTemp, Log, TEXT("Data A : %u  Data B : %u  Data C : %s  (Error Case)"), Data.A, Data.B, *Data.C);
	}

	{
		// once reader offset overflowed, it doesn't work properly even if seek offset
		Reader.Seek(0);
		SerializationExampleData Data;
		Reader << Data;
		UE_LOG(LogTemp, Log, TEXT("Data A : %u  Data B : %u  Data C : %s  (Error Case)"), Data.A, Data.B, *Data.C);
	}

	// safe example - just create archive object in brace
	{
		FMemoryReader MemReader(BufferArray);
		SerializationExampleData Data;
		MemReader << Data;
		UE_LOG(LogTemp, Log, TEXT("Data A : %u  Data B : %u  Data C : %s"), Data.A, Data.B, *Data.C);
	}

	UE_LOG(LogTemp, Log, TEXT("StructSerializeExample  End"));
}

 

Object Serialization

객체 직렬화 예제이다.

UENUM(BlueprintType)
enum class EType : uint8
{
	Type1,
	Type2
};

UCLASS()
class UNREALEXAMPLES_API USerializationExampleObject : public UObject
{
	GENERATED_BODY()

public:
	USerializationExampleObject() {}


	UPROPERTY(EditAnywhere, Category = "Voice Characteristics")
		EType Language;

	UPROPERTY(EditAnywhere, Category = "Conversion")
		FString SaveAssetTo;

	// 참조 형태의 변수는 Serialize 후 파일 Read/Write 과정에 문제가 생길 수 있다.
	// 따로 경로만 저장하는 형태로 개발하거나,
	// Config에 저장하는 형태로 개발하는것이 좋다.
	//UPROPERTY(EditAnywhere, Category = "Conversion")
	//	USkeleton* TargetSkeleton;

	UPROPERTY(EditAnywhere, Category = "Conversion")
		int32 ImportAudioNumber;

	UPROPERTY(EditAnywhere, Category = "Conversion")
		bool bInsertPlaySoundNotify;

	UPROPERTY(EditAnywhere, Category = "FilesToDump")
		bool bPhoneme;

	UPROPERTY(EditAnywhere, Category = "FilesToDump")
		bool bAnimClip;

	UPROPERTY(EditAnywhere, Category = "FilesToDump")
		bool bConversionLog;
};

 

void SerializationExamples::ObjectExample()
{
	UE_LOG(LogTemp, Log, TEXT("ObjectExample  Begin"));
	TArray<uint8> BufferArray;

	// write
	{
		FMemoryWriter Writer(BufferArray);

		USerializationExampleObject* Obj = NewObject<USerializationExampleObject>();
		Obj->Language = EType::Type2;
		Obj->SaveAssetTo = TEXT("Maybe/Some/Directory/Path/Here");
		Obj->ImportAudioNumber = 33;
		Obj->bInsertPlaySoundNotify = true;
		Obj->bPhoneme = false;
		Obj->bAnimClip = true;
		Obj->bConversionLog = false;

		Obj->Serialize(Writer);
		UE_LOG(LogTemp, Log, TEXT("BufferArray Num : %d"), BufferArray.Num());
	}

	// read
	{
		FMemoryReader Reader(BufferArray);
		USerializationExampleObject* Obj = NewObject<USerializationExampleObject>();

		Obj->Serialize(Reader);

		UE_LOG(LogTemp, Log, TEXT("Readed Data : %d  %s  %d  %d  %d  %d  %d"),
			Obj->Language,
			*Obj->SaveAssetTo,
			Obj->ImportAudioNumber,
			Obj->bInsertPlaySoundNotify,
			Obj->bPhoneme,
			Obj->bAnimClip,
			Obj->bConversionLog);
	}


	UE_LOG(LogTemp, Log, TEXT("ObjectExample  End"));
}

 

File Serialization

파일 직렬화 예제이다.

oid SerializationExamples::FileExample()
{
	UE_LOG(LogTemp, Log, TEXT("FileExample  Begin"));

	FString FullPath = FString::Printf(TEXT("%s%s"), *FPaths::ProjectSavedDir(), TEXT("save.txt"));

	// write
	FArchive* FileWriter = IFileManager::Get().CreateFileWriter(*FullPath);
	if (FileWriter)
	{
		FString T1 = FString(TEXT("AAA"));
		FString T2 = FString(TEXT("BBB"));
		*FileWriter << T1;
		*FileWriter << T2;
		FileWriter->Close();
		delete FileWriter;
		FileWriter = NULL;
	}

	// read (can be used with TSharedPtr, which deletes memory by-self)
	TSharedPtr<FArchive> FileReader = MakeShareable(IFileManager::Get().CreateFileReader(*FullPath));
	if (FileReader.IsValid())
	{
		FString Temp1;
		FString Temp2;

		*FileReader.Get() << Temp1;
		*FileReader.Get() << Temp2;

		FileReader->Close();
		UE_LOG(LogTemp, Log, TEXT("%s %s"), *Temp1, *Temp2);
	}

	UE_LOG(LogTemp, Log, TEXT("FileExample  End"));
}

 

메모리 데이터를 파일에 직렬화할 수도 있다.

void SerializationExamples::MemoryAndFileExample()
{
	UE_LOG(LogTemp, Log, TEXT("MemoryAndFileExample  Begin"));

	FString FullPath = FString::Printf(TEXT("%s%s"), *FPaths::ProjectSavedDir(), TEXT("save.txt"));

	// write (data -> memory -> file)
	TSharedPtr<FArchive> FileWriter = MakeShareable(IFileManager::Get().CreateFileWriter(*FullPath));
	if (FileWriter.IsValid())
	{
		TArray<uint8> Buffer;
		FMemoryWriter MemoryWriter(Buffer);

		SerializationExampleData Data;
		Data.A = 99;
		Data.B = 13;
		Data.C = TEXT("blabla");

		MemoryWriter << Data;

		*FileWriter << Buffer;
		FileWriter->Close();
	}


	// read (file -> memory -> data)
	TSharedPtr<FArchive> FileReader = MakeShareable(IFileManager::Get().CreateFileReader(*FullPath));
	if (FileReader.IsValid())
	{
		TArray<uint8> Buffer;
		*FileReader << Buffer;

		FMemoryReader MemoryReader(Buffer);

		SerializationExampleData Data;
		MemoryReader << Data;
		FileReader->Close();


		UE_LOG(LogTemp, Log, TEXT("Data A : %u  Data B : %u  Data C : %s"), Data.A, Data.B, *Data.C);
	}

	UE_LOG(LogTemp, Log, TEXT("MemoryAndFileExample  End"));
}

 

Github

https://github.com/insooneelife/UnrealExamples/blob/master/Source/UnrealExamples/Public/Serialization/SerializationExamples.h