직렬화

Lee Raccoon·2024년 7월 17일
0

언리얼 공부

목록 보기
9/11
post-thumbnail

직렬화

  • 오브젝트나 연결된 오브젝트의 묶음을 바이트 스트림으로 변환하는 과정이다.

장점 및 활용

  • 현재 프로그램 상태를 저장하고 필요할 때 복원 가능 (게임 저장)
  • 현재 객체의 정보를 클립보드에 복사해서 다른 프로그램에 전송 가능
  • 네트워크를 통해 현재 프로그램의 상태를 다른 컴퓨터에 복원 가능 (멀티플레이)
  • 데이터 압축, 암호화를 통해 데이터를 효율적이고 안전하게 보관 가능

언리얼의 직렬화 시스템

언리얼은 직렬화 시스템을 위해 자체적으로
FArchive 클래스와 연산자들을 제공하고 있다.

다양한 아카이브 클래스로써

  • 메모리 아카이브 (FMemoryReader FMemoryWriter)
  • 파일 아카이브 (FArchiveFileReaderGeneric, FArchiveFileWriterGeneric)
  • 기타 언리얼 오브젝트와 관련된 아카이브 클래스 (FArchiveUObject)

등을 제공하고 있으며 Json 형태의 직렬화는 별도의 라이브러리를 통해 제공하고 있다.

아카이브 예제

//언리얼 오브젝트
class SERIALIZATIONTEST_API UStudent : public UObject
{
	GENERATED_BODY()
	
public:
	UStudent();

	int32 GetOrder() const { return Order; }
	void SetOrder(int32 InOrder) { Order = InOrder; }

	const FString& GetName() const { return Name; }
	void SetName(const FString& InName) { Name = InName; }

	virtual void Serialize(FArchive& Ar) override;


private:
	int32 Order;

	FString Name;
};

UStudent::UStudent()
{
	Order = -1;
	Name = TEXT("학생이름 기본값");
}

void UStudent::Serialize(FArchive& Ar)
{
	Super::Serialize(Ar);

	Ar << Order;
	Ar << Name;
}

//cpp 구조체
struct FStudentData
{
	FStudentData() {};
	FStudentData(int32 InOrder, const FString& InName) : Order(InOrder), Name(InName) {}

	friend FArchive& operator<<(FArchive& Ar, FStudentData& InStudentData)
	{
		Ar << InStudentData.Order;
		Ar << InStudentData.Name;
		return Ar;
	}

	int32 Order = -1;
	FString Name = TEXT("홍길동");
};

//GameInstance.cpp
void UMyGameInstance::Init()
{
	Super::Init();

	FStudentData RawDataSrc(16, TEXT("너굴"));

	const FString SavedDir = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));
	UE_LOG(LogTemp, Display, TEXT("저장할 파일 폴더 : %s"), *SavedDir);

	const FString RawDataFileName(TEXT("RawData.bin"));
	FString RawDataAbsolutePath = FPaths::Combine(*SavedDir, *RawDataFileName);

	UE_LOG(LogTemp, Display, TEXT("저장할 파일 전체 경로 : %s"), *RawDataAbsolutePath);
	FPaths::MakeStandardFilename(RawDataAbsolutePath);

	UE_LOG(LogTemp, Display, TEXT("저장할 파일 전체 경로 : %s"), *RawDataAbsolutePath);

	FArchive* RawFileWriterAr = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);
	if(RawFileWriterAr != nullptr)
	{
		*RawFileWriterAr << RawDataSrc;
		RawFileWriterAr->Close();
		delete RawFileWriterAr;
		RawFileWriterAr = nullptr;
	}

	FStudentData RawDataDest;
	FArchive* RawFileReaderAr = IFileManager::Get().CreateFileReader(*RawDataAbsolutePath);
	if (RawFileReaderAr != nullptr)
	{
		*RawFileReaderAr << RawDataDest;
		RawFileReaderAr->Close();
		delete(RawFileReaderAr);
		RawFileReaderAr = nullptr;

		UE_LOG(LogTemp, Display, TEXT("[RawData] 이름 %s, 순번 %d"), *RawDataDest.Name, RawDataDest.Order);
	}
    StudentSrc = NewObject<UStudent>();
    StudentSrc->SetName(TEXT("너굴"));
    StudentSrc->SetOrder(14);

    const FString ObjectDataFileName(TEXT("ObjectData.bin"));
    FString ObjectDataAbsolutePath = FPaths::Combine(*SavedDir, *ObjectDataFileName);
    FPaths::MakeStandardFilename(ObjectDataAbsolutePath);

    TArray<uint8> BufferArray;
    FMemoryWriter MemoryWriterAr(BufferArray);
    StudentSrc->Serialize(MemoryWriterAr);

    if(TUniquePtr<FArchive> FileWriterAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*ObjectDataAbsolutePath)))
    {
        *FileWriterAr << BufferArray;
        FileWriterAr->Close();
    }

    TArray<uint8> BufferArrayFromFile;
    if (TUniquePtr<FArchive> FileReaderAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileReader(*ObjectDataAbsolutePath)))
    {
        *FileReaderAr << BufferArrayFromFile;
        FileReaderAr->Close();
    }

    FMemoryReader MemoryReaderAr(BufferArrayFromFile);
    UStudent* StudentDest = NewObject<UStudent>();
    StudentDest->Serialize(MemoryReaderAr);
    PrintStudentInfo(StudentDest, TEXT("ObjectData"));
}
//결과
LogTemp: Display: 저장할 파일 폴더 : ../../../../UnrealProjects/SerializationTest/Saved
LogTemp: Display: 저장할 파일 전체 경로 : ../../../../UnrealProjects/SerializationTest/Saved/RawData.bin
LogTemp: Display: 저장할 파일 전체 경로 : C:/UnrealEngine/UnrealProjects/SerializationTest/Saved/RawData.bin
LogTemp: Display: 저장할 파일 폴더 : ../../../../UnrealProjects/SerializationTest/Saved
LogTemp: Display: 저장할 파일 전체 경로 : ../../../../UnrealProjects/SerializationTest/Saved/RawData.bin
LogTemp: Display: 저장할 파일 전체 경로 : C:/UnrealEngine/UnrealProjects/SerializationTest/Saved/RawData.bin
LogTemp: Display: [RawData] 이름 너굴, 순번 16
LogTemp: Display: [ObjectData] 이름 너굴 순번 14

이렇게 C++ 클래스 구조체와 언리얼 오브젝트를 각각 파일로 저장하고 불러올 수 있게 되었다!

Json 직렬화 예제

StudentSrc = NewObject<UStudent>();
StudentSrc->SetName(TEXT("너굴"));
StudentSrc->SetOrder(14);

const FString JsonDataFileName(TEXT("StudentJsonData.txt"));
FString JsonDataAbsolutePath = FPaths::Combine(*SavedDir, *JsonDataFileName);
FPaths::MakeStandardFilename(JsonDataAbsolutePath);

TSharedRef<FJsonObject> JsonObjectSrc = MakeShared<FJsonObject>();
FJsonObjectConverter::UStructToJsonObject(StudentSrc->GetClass(), StudentSrc, JsonObjectSrc);

FString JsonOutString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriterAr = TJsonWriterFactory<TCHAR>::Create(&JsonOutString);
if (FJsonSerializer::Serialize(JsonObjectSrc, JsonWriterAr))
{
	FFileHelper::SaveStringToFile(JsonOutString, *JsonDataAbsolutePath);
}

FString JsonInString;
FFileHelper::LoadFileToString(JsonInString, *JsonDataAbsolutePath);

TSharedRef<TJsonReader<TCHAR>> JsonReaderAr = TJsonReaderFactory<TCHAR>::Create(JsonInString);

TSharedPtr<FJsonObject> JsonObjectDest;
if (FJsonSerializer::Deserialize(JsonReaderAr, JsonObjectDest))
{
	UStudent* JsonStudentDest = NewObject<UStudent>();
	if (FJsonObjectConverter::JsonObjectToUStruct(JsonObjectDest.ToSharedRef(), JsonStudentDest->GetClass(), JsonStudentDest))
	{
		PrintStudentInfo(JsonStudentDest, TEXT("JsonData"));
	}
}
//결과
LogTemp: Display: [JsonData] 이름 너굴 순번 14

txt 파일로 잘 저장되고 불러와짐을 확인할 수 있다!

직렬화를 수행할 때 만약 언리얼 오브젝트를 직렬화 한다면
그 때 필요한 모든 프로퍼티들이 UPROPERTY 매크로가 잘 붙어 있는지 확인해야함에 주의하자!

안붙이면 언리얼이 인식을 못해버릴 수 있다.

profile
영차 영차

0개의 댓글