직렬화(Serialization)란?

  • 오브젝트나 연결된 오브젝트의 묶음(오브젝트 그래프)을 바이트 스트림으로 변환하는 과정
    • 복잡한 데이터를 일렬로 세우기 때문에 직렬화
  • 거꾸로 복구시키는 과정도 포함해서 의미
    • 시리얼라이제이션(Serialization) : 오브젝트 그래프에서 바이트 스트림으로
    • 디시리얼라이제이션(Deserialization) : 바이트 스트림에서 오브젝트 그래프로
  • 직렬화가 가지는 장점
    • 현재 프로그램의 상태를 저장하고 필요한 때 복원할 수 있다. ( 게임의 저장 )
    • 현재 객체의 정보를 클립보드에 복사해서 다른 프로그램에 전송할 수 있다.
    • 네트워크를 통해 현재 프로그램의 상태를 다른 컴퓨터에 복원할 수 있다. ( 멀티플레이어 게임 )
    • 데이터 압축, 암호화를 통해 데이터를 효율적이고 안전하게 보관할 수도 있음.

직렬화 구현 시 고려할 점

  • 이러한 직렬화를 직접 구현할 경우 다양한 상황을 고려해야 함.
    • 데이터 레이아웃 : 오브젝트가 소유한 다양한 데이터를 변환할 것인가?
    • 이식성 : 서로 다른 시스템에 전송해도 이식될 수 있는가?
    • 버전 관리 : 새로운 기능이 추가될 때 이를 어떻게 확장하고 처리할 것인가?
    • 성능 : 네트웍 비용을 줄이기 위해 어떤 데이터 형식을 사용할 것인가?
    • 보안 : 데이터를 어떻게 안전하게 보호할 것인가?
    • 에러 처리 : 전송 과정에서 문제가 발생할 경우 이를 어떻게 인식하고 처리할 것인가?
  • 이런 상황을 모두 감안해 직렬화 모델을 만드는 것은 쉬운 일이 아님!

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

  • 언리얼 엔진은 이러한 상황을 모두 고려한 직렬화 시스템을 자체적으로 제공하고 있음
  • 직렬화 시스템을 위해 제공하는 클래스 FArchive와 연산자
    • 아카이브 클래스 ( FArchive )
    • Shift(<<) operator
  • 다양한 아카이브 클래스의 제공
    • 메모리 아카이브 ( FMemoryReader, FMemoryWriter )
    • 파일 아카이브 ( FArchiveFileReaderGeneric , FArchiveFileWriterGeneric )
    • 기타 언리얼 오브젝트와 관련된 아카이브 클래스( FArchiveUObject )
  • Json 직렬화 기능 : 별도의 라이브러리를 통해 제공하고 있음

직렬화 FArchive 코드 예시.

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyGameInstance.h"
#include "Student.h"

UMyGameInstance::UMyGameInstance()
{
}

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

	//데이터 생성.
	FStudentData RawDataSource(23, TEXT("wnwkdqls"));

	//경로
	const FString SavedPath = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));

	//테스트 출력.
	UE_LOG(LogTemp, Log, TEXT("저장할 파일 경로: %s"), *SavedPath);


	{
		//파일 이름.
		const FString RawDataFileName(TEXT("RawData.bin"));

		//전체 경로 설정 후 경로로 변경.
		FString RawDataAbsolutePath = FPaths::Combine(SavedPath, RawDataFileName);

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

		//절대 경로로 변경.
		FPaths::MakeStandardFilename(RawDataAbsolutePath);
		UE_LOG(LogTemp, Log, TEXT("변경할 파일 전체 경로: %s"), *RawDataAbsolutePath);

		//구조체 데이터 직렬화.
		//CreateFileWriter 함수는 new로 생성 후 반환하기 때문에 delete로 정리 해야 한다.
		FArchive* RawFileWriteAr = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);//파일 여는 행위(File.Open이랑 같다)

		if (RawFileWriteAr)
		{
			//데이터 넣기.
			//*RawFileWriteAr << RawDataSource.Order;
			//*RawFileWriteAr << RawDataSource.Name;
			*RawFileWriteAr << RawDataSource;

			//아카이브 닫기.
			RawFileWriteAr->Close();

			//메모리 해제.
			delete RawFileWriteAr;
			RawFileWriteAr = nullptr;
		}

		//역 직렬화(읽기).
		FStudentData RawDataDeserialized;
		FArchive* RawFileReaderAr = IFileManager::Get().CreateFileReader(*RawDataAbsolutePath);

		if (RawFileReaderAr)
		{
			//데이터 읽기.
			//*RawFileReaderAr << RawDataDeserialized.Order;
			//*RawFileReaderAr << RawDataDeserialized.Name;
			*RawFileReaderAr << RawDataDeserialized;

			RawFileReaderAr->Close();
			delete RawFileReaderAr;
			RawFileReaderAr = nullptr;

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

	}

	//UObject 직렬화.
	StudentSource = NewObject<UStudent>();
	StudentSource->SetOrder(40);
	StudentSource->SetName(TEXT("wnwkdqls"));

	{
		//파일 이름.
		const FString ObjectDataFileName(TEXT("ObjectData.bin"));

		//파일 경로.
		FString ObjectDataAbsolutePath = FPaths::Combine(*SavedPath, *ObjectDataFileName);

		FPaths::MakeStandardFilename(ObjectDataAbsolutePath);

		//직렬화.
		TArray<uint8> BufferArray;
		FMemoryWriter MemoryWriterAr(BufferArray);
		StudentSource->Serialize(MemoryWriterAr);

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

		//역 직렬화.
		if (TUniquePtr<FArchive> FileReaderAr = TUniquePtr<FArchive>(
			IFileManager::Get().CreateFileReader(*ObjectDataAbsolutePath)))
		{
			TArray<uint8> BufferArrayFromFile;
			*FileReaderAr << BufferArrayFromFile;
			FileReaderAr->Close();

			FMemoryReader MemoryReaderAr(BufferArrayFromFile);

			UStudent* StudentDes = NewObject<UStudent>();
			StudentDes->Serialize(MemoryReaderAr);

			UE_LOG(LogTemp, Log, TEXT("이름: %s, 순번: %d"), *StudentDes->GetName(), StudentDes->GetOrder());
		}
	}
}

Json 직렬화

  • Json(JavaScript Object Notation)의 약자
  • 웹 환경에서 서버와 클라이언트 사이에 데이터를 주고받을 때 사용하는 텍스트 기반 데이터 포맷
  • Json 장점
    • 텍스트임에도 데이터 크기가 가벼움.
    • 읽기 편해서 데이터를 보고 이해할 수 있음.
    • 사실 상 웹 통신의 표준으로 널리 사용됨.
  • Json의 단점
    • 지원하는 타입이 몇 가지 안됨. ( 문자, 숫자, 불리언, 널, 배열, 오브젝트만 사용 가능 )
    • 텍스트 형식으로만 사용할 수 있음.
  • 언리얼 엔진의 Json, JsonUtilities 라이브러리 활용

Json 데이터 예시

  • Json 데이터 유형
    • 오브젝트 : {}
      • 오브젝트 내 데이터는 키, 밸류 조합으로 구성됨. 예) { "key" : 10 }
    • 배열 : []
      • 배열 내 데이터는 밸류로만 구성됨. 예) [ "value1", "value2", "value3" ]
    • 이외 데이터
      • 문자열 ( "string" ) , 숫자 ( 10 또는 3.14) , 불리언 ( true 또는 false ) , 널 ( null )로 구성

이미지 예시

언리얼 스마트 포인터 라이브러리 개요

  • 일반 C++ 오브젝트의 포인터 문제를 해결해주는 언리얼 엔진의 라이브러리
  • TUniquePtr(유니크포인터) : 지정한 곳에서만 메모리를 관리하는 포인터.
    • 특정 오브젝트에게 명확하게 포인터 해지 권한을 주고 싶은 경우.
    • delete 구문 없이 함수 실행 후 자동으로 소멸시키고 싶을 때
  • TSharedPtr(공유포인터) : 더 이상 사용되지 않으면 자동으로 메모리를 해지하는 포인터
    • 여러 로직에서 할당된 오브젝트가 공유해서 사용되는 경우
    • 다른 함수로부터 할당된 오브젝트를 Out으로 받는 경우.
    • Null 일 수 있음.
  • TSharedRef(공유레퍼런스) : 공유포인터와 동일하지만, 유효한 객체를 항상 보장받는 레퍼런스
    • 여러 로직에서 할당된 오브젝트가 공유해서 사용되는 경우
    • Not Null을 보장받으며 오브젝트를 편리하게 사용하고 싶은 경우

Json 직렬화 코드 예시

해당 UnrealSerialization.Build.cs 빌트 코드에서 Json 모듈을 추가해 준다.(추가 안하면 빌드에러가 뜸)

//Json 직렬화.
const FString JsonDataFileName(TEXT("StudentJsonData.json"));

//경로 설정.
FString JsonDataAbsolutepath = FPaths::Combine(*SavedPath, *JsonDataFileName);

FPaths::MakeStandardFilename(JsonDataAbsolutepath);

//Json 객체 생성.
TSharedRef<FJsonObject> JsonObjectSource = MakeShared<FJsonObject>();

//UObjcect -> Json 객체 변환.
FJsonObjectConverter::UStructToJsonObject(
	StudentSource->GetClass(),
	StudentSource, 
	JsonObjectSource);


//Json 객체 -> Json문자열.
FString JsonString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriterAr = TJsonWriterFactory<TCHAR>::Create(&JsonString);

if (FJsonSerializer::Serialize(JsonObjectSource, JsonWriterAr))
{
	//파일에 저장.
	FFileHelper::SaveStringToFile(JsonString, *JsonDataAbsolutepath);

}

//Json Read.
FString JsonFromFile;
FFileHelper::LoadFileToString(JsonFromFile, *JsonDataAbsolutepath);

//Json String-> Json Object.
TSharedRef<TJsonReader<TCHAR>> JsonReaderAr = TJsonReaderFactory<TCHAR>::Create(JsonFromFile);

TSharedPtr<FJsonObject> jsonObjectResult;
if (FJsonSerializer::Deserialize(JsonReaderAr, jsonObjectResult))
{
	//Json Object -> UObject.
	UStudent* JsonStudent = NewObject<UStudent>();
	if (FJsonObjectConverter::JsonObjectToUStruct(
		jsonObjectResult.ToSharedRef(),
		JsonStudent->GetClass(),
		JsonStudent
	))
	{
		// Test Print.
		UE_LOG(LogTemp, Log, TEXT("[JsonData] 이름: %s, 순번: %d"),
			*JsonStudent->GetName(), JsonStudent->GetOrder())
	}
}

'언리얼 엔진 공부 > 언리얼C++' 카테고리의 다른 글

언리얼 엔진 게임 제작 기초  (0) 2025.04.10
어설션(Assertion)  (0) 2025.04.08
언리얼C++델리게이트(Delegate)  (0) 2025.04.03
컴포지션  (0) 2025.04.03
인터페이스  (1) 2025.04.02

+ Recent posts