본문 바로가기
Unreal/[서적] 언리얼5 이득우님 인프런1

Ch 13. 직렬화

by GameStudy 2023. 5. 13.

13.1 언리얼 엔진의 직렬화

13.1-1 직렬화란

  - 오브젝트나 연결된 오브젝트의 묶음(오브젝트 그래프)을 바이트 스트림으로 변환하는 과정

    복잡한 데이터를 일렬로 세우기 때문에 직렬화라고 부름.

  - 거꾸로 복구시키는 과정도 포함해서 의미함.

    시리얼라이제이션: 오브젝트 그래프에서 바이트 스트림으로

    디시리얼라이제이션: 바이트 스트림에서 오브젝트 그래프로

  - 직렬화가 가지는 장점

    현재 프로그램의 상태를 저장하고 필요할 때 복원할 수 있음.(게임의 저장)

    현재 개체의 정보를 클립보드에 복사해서 다른 프로그램에 전송할 수 있음.

    네트워크를 통해 현재 프로그램의 상태를 다른 컴퓨터에 복원할 수 있음.(멀티플레이)

    데이터 압축, 암호화를 통해 데이터를 효율적이고 안전하게 보관할 수 있음.

 

13.1-2 직렬화 구현시 고려할 점

  - 데이터 레이아웃: 오브젝트가 소유한 다양한 데이터를 어떻게 변환할 것인가

  - 이식성: 서로 다른 시스템에 전송해도 이식될 수 있게끔 할것인가(.ex 리틀엔디안/빅엔디안)

  - 버전 관리: 새로운 기능이 추가될 때 이를 어떻게 확장하고 처리할 것인가(기존의 직렬화가 무효화됨)

  - 성능: 네트워크 비용을 줄이기 위해 어떤 데이터 형식을 사용할 것인가(회전을 모두 다 보낼 필욘 없음. 양자화)

  - 보안: 데이터를 어떻게 안전하게 보호할 것인가

  - 에러 처리: 전송 과정에서 문제가 발생할 경우 이를 어떻게 인식하고 처리할 것인

 

13.1-3 언리얼 엔진의 직렬화 시스템

  - 직렬화 시스템을 위해 언리얼에서 제공하는 클래스 FArchive와 << 연산자

  - 다양한 아카이브 클래스의 제공

    메모리 아카이브(FMemoryReader, FMemoryWriter)

    파일 아카이브(FArchiveFileReaderGeneric, FArchiveFileWriterGeneric)

    기타 언리얼 오브젝트와 관련된 아카이브 클래스(FArchiveUObject)

    Json 직렬화 기능도 별도의 라이브러리로 제공하고 있음.

 

13.1-4 직렬화 시스템 실습

  - 새 프로젝트 > "UnrealSerialization" 생성

  - 새 C++ 클래스 > GameInstance 부모 클래스 > "MyGameInstance"

    새 C++ 클래스 > Object 부모 클래스 > "Student"

<hide/>

// Student.h

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Student.generated.h"

UCLASS()
class UNREALSERIALIZATION_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:
	UPROPERTY()
	int32 Order;

	UPROPERTY()
	FString Name;
};
<hide/>

// Student.cpp

#include "Student.h"

UStudent::UStudent()
{
	Order = -1;
	Name = TEXT("홍길동");
}

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

	Ar << Order;
	Ar << Name;
}
<hide/>

// MyGameInstance.h

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "Engine/StreamableManager.h"
#include "MyGameInstance.generated.h"

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("홍길동");
};

UCLASS()
class UNREALSERIALIZATION_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
	
public:
	UMyGameInstance();

	virtual void Init() override;
    
private:
	UPROPERTY()
	TObjectPtr<class UStudent> StudentSrc;

	FStreamableManager StreamableManager;
	TSharedPtr<FStreamableHandle> Handle;
};
<hide/>

// MyGameInstance.cpp

#include "MyGameInstance.h"
#include "Student.h"
#include "JsonObjectConverter.h"
#include "UObject/SavePackage.h"

void PrintStudentInfo(const UStudent* InStudent, const FString& InTag)
{
	UE_LOG(LogTemp, Log, TEXT("[%s] 이름 %s 순번 %d"), *InTag, *InStudent->GetName(), InStudent->GetOrder());
}

UMyGameInstance::UMyGameInstance()
{
}

void UMyGameInstance::Init()
{
	Super::Init();

	FStudentData RawDataSrc(16, TEXT("이득우"));

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

	{
		const FString RawDataFileName(TEXT("RawData.bin"));
		FString RawDataAbosolutePath = FPaths::Combine(*SavedDir, *RawDataFileName);
		UE_LOG(LogTemp, Log, TEXT("저장할 파일 전체 경로 : %s"), *RawDataAbosolutePath);
		FPaths::MakeStandardFilename(RawDataAbosolutePath);
		UE_LOG(LogTemp, Log, TEXT("변경할 파일 전체 경로 : %s"), *RawDataAbosolutePath);

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

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

			UE_LOG(LogTemp, Log, TEXT("[RawData] 이름 %s 순번 %d"), *RawDataDest.Name, RawDataDest.Order);
		}
	}

	StudentSrc = NewObject<UStudent>();
	StudentSrc->SetName(TEXT("이득우"));
	StudentSrc->SetOrder(59);

	{
		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"));
	}

}

 

13.1-5 Json 직렬화

  - 웹 환경에서 서버와 클라이언트 사이에 데이터를 주고 받을 때 사용하는 텍스트 기반 데이터 포맷

  - Json 장점

    텍스트임에도 데이터 크기가 가벼움.

    읽기 편해서 데이터를 보고 이해할 수 있음.

    사실상 웹 통신의 표준으로 널리 사용됨.

  - Json 단점

    지원하는 타입이 몇 가지 안됨.(문자, 숫자, 불리언, 널, 배열, 오브젝트만 가능)

    텍스트 형식으로만 사용할 수 있어서 극도의 효율 추구는 불가능.

  - 언리얼 엔진의 Json, JsonUtilities 라이브러리 활용

 

13.1-6 Json 데이터 유형

  - 오브젝트 {}

    오브젝트 내 데이터는 키-벨류 조합으로 구성됨. ex) { "key" : 10 }

  - 배열 []

    배열 내 데이터는 벨류로만 구성됨. ex) [ "value1", "value2", "value3" ]

  - 이외 데이터

    문자열("string"), 숫자(10, 3.141592), 불리언(true, false), 널(null)로 구성.

 

13.1-7 언리얼 스마트 포인터 라이브러리

  - TUniquePtr: 지정된 곳에서만 메모리를 관리하는 포인터

    특정 오브젝트에게 명확하게 포인터 해지 권한을 주고 싶은 경우에 사용.

    delete 구문 없이 함수 실행 후 자동으로 소멸시키고 싶을때 사용함.

  - TSharedPtr: 더이상 사용되지 않으면 자동으로 메모리를 해지하는 포인터

    여러 로직에서 할당된 오브젝트가 공유해서 사용되는 경우.

    다른 함수로부터 할당된 오브젝트를 Out으로 받는 경우.

    nullptr일 수도 있기에 주의.

  - TSharedRef: 공유 포인터와 동일하지만, 유효한 개체를 항상 보장받는 레퍼런스

    여러 로직에서 할당된 오브젝트가 공유해서 사용되는 경우.

    Not null을 보장받으며, 오브젝트를 편리하게 사용하고 싶은 경우.

 

 

13.1-8 Json 직렬화 실습

<hide/>

// MyGameInstance.cpp

#include "MyGameInstance.h"
#include "Student.h"
#include "JsonObjectConverter.h"
#include "UObject/SavePackage.h"

...

void UMyGameInstance::Init()
{
	...

	{
		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"));
			}
		}
	}

}

 

 

 

 

 

'Unreal > [서적] 언리얼5 이득우님 인프런1' 카테고리의 다른 글

Ch 15. 언리얼 빌드 시스템  (0) 2023.05.13
Ch 14. 패키지  (0) 2023.05.13
Ch 12. 언리얼 엔진의 메모리 관리  (0) 2023.05.13
Ch 11. 구조체와 Map  (0) 2023.05.13
Ch 10. Array and Set  (0) 2023.05.12

댓글