서론
언리얼 엔진(UE)에서 C++을 사용해 게임을 개발할 때, 데이터의 저장과 불러오기를 효율적으로 처리하기 위해 직렬화(Serialization)가 필요합니다. 직렬화는 객체의 상태를 저장하여 나중에 복원할 수 있도록 하는 과정입니다. 이 포스트에서는 언리얼 엔진에서 사용하는 직렬화에 대해서 알아보려고 합니다.
직렬화
우선 언리얼 엔진의 직렬화를 알아보기에 앞서서 직렬화에 대한 개념을 간단하게 짚고 넘어가려고 합니다.
직렬화는 객체의 상태를 바이트 스트림으로 변환하여 저장하거나 전송할 수 있도록 하는 과정입니다.
이를 통해 데이터를 파일에 저장하거나 네트워크를 통해 전송한 후 동일한 객체로 복원할 수 있습니다.
- 데이터 -> 바이트 스트림 변환 과정을 Serialization,
- 바이트 스트림 -> 데이터 변환 과정을 DeSerialization 이라고 정의합니다.
- 보통 Serialization과 DeSerialization을 모두 포함해서 직렬화라고 부릅니다.
직렬화 구현 시 고려할 점.
- 데이터 레이아웃 : 오브젝트가 소유한 다양한 데이터를 변환하는 방법
- 이식성 : 서로 다른 시스템에 전송해도 이식할 수 있는 방법
- 버전 관리 : 새로운 기능이 추가될 때 기능을 어떻게 확장하고 처리할 것인지에 대한 방법
- 성능 : 네트워크 비용을 줄이기 위해 어떤 데이터 형식을 사용할 것인지에 대한 방법
- 보안 : 데이터를 어떻게 안전하게 보호할 것인지에 대한 방법
- 에러 처리 : 전송 과정에서 문제가 발생할 경우 이를 어떻게 인식하고 처리할 것인지에 대한 방법
언리얼 엔진의 직렬화 시스템
- 언리얼 엔진은 이러한 상황을 모두 고려한 직렬화 시스템을 자체적으로 제공합니다.
- 직렬화 시스템을 위해서 제공하는 클래스 FArchive, 연산자가 존재합니다.
- Archive class : FArchive
- Shift(<<) operator
- 다양한 아카이브 클래스의 제공합니다.
- 메모리 아카이브 (FMemoryReader, FMemoryWriter)
- 파일 아카이브(FArchiveFileReaderGeneric, FArchiveFileWriterGeneric)
- 기타 언리얼 오브젝트와 관련된 아카이브 클래스(FArchiveUObject)
- Json 직렬화 기능 : 별도의 라이브러리를 통해 제공합니다.
- 사용하기 위해서는 Json, JsonUtilites 라이브러리를 활용합니다.
Json 직렬화 추가
<ProjectName>.Build.cs 파일에서 PublicDependencyModuleNames.AddRange에 Json, JsonUtilites를 추가합니다.
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class UnrealSerialization : ModuleRules
{
public UnrealSerialization(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Json", "JsonUtilities" });
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
Json 데이터 유형
- 오브젝트 : {}
- 오브젝트 내 데이터는 키, 밸류 조합으로 구성됩니다. 예) {”key”:10}
- 배열 : []
- 배열 내 데이터는 밸류로만 구성됩니다.. 예) [”value1”, “value2”, “value3”]
- 이외 데이터
- 문자열 (”string”), 숫자 (10 또는 3.14), 불리언 ( true 또는 false ), 널 ( null ) 로 구성됩니다.
실습
데이터 구조체에 직렬화 방식 정의
// MyGameInstance.h
struct FMyData
{
public:
// 직렬화 함수
friend FArchive& operator<<(FArchive& Ar, MyStruct& InMyData)
{
Ar << InMyData.MyInt;
Ar << InMyData.MyString;
return Ar;
}
private:
int32 MyInt;
FString MyString;
};
// ... 생략
// MyData.h
// ... 생략
UCLASS()
class UNREALSERIALIZATION_API UMyData : public UObject
{
GENERATED_BODY()
public:
UStudent();
int32 GetMyInt() const { return MyInt; }
void SetMyInt(int32 InMyInt) { MyInt = InMyInt; }
const FString& GetMyString() const { return MyString; }
void SetMyString(const FString& InMyString) { MyString = InMyString; }
virtual void Serialize(FArchive& Ar) override;
private:
UPROPERTY()
int32 MyInt;
UPROPERTY()
FString MyString;
};
FMyData 구조체에 FArchive의 operator <<를 정의함으로서 FMyData 구조체에 직렬화 방식을 정의합니다.
파일에 데이터 쓰기 후 읽기
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameInstance.h"
#include "MyData.h"
UMyGameInstance::UMyGameInstance()
{
}
void UMyGameInstance::Init()
{
Super::Init();
FStudentData RawDataSrc(16, TEXT("Test"));
const FString SavedDir = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));
{
const FString RawDataFileName(TEXT("RawData.bin"));
FString RawDataAbsolutePath = FPaths::Combine(*SavedDir, *RawDataFileName);
FPaths::MakeStandardFilename(RawDataAbsolutePath);
FArchive* RawFileWriterAr = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);
if (nullptr != RawFileWriterAr )
{
*RawFileWriterAr << RawDataSrc;
RawFileWriterAr->Close();
delete RawFileWriterAr;
RawFileWriterAr = nullptr;
}
FMyData RawDataDest;
FArchive* RawFileReaderAr = IFileManager::Get().CreateFileReader(*RawDataAbsolutePath);
if (nullptr != RawFileReaderAr)
{
*RawFileReaderAr << RawDataDest;
RawFileReaderAr->Close();
delete RawFileReaderAr;
RawFileReaderAr = nullptr;
}
}
}
메모리에 파일 내용 불러오기
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameInstance.h"
#include "MyData.h"
UMyGameInstance::UMyGameInstance()
{
}
void UMyGameInstance::Init()
{
Super::Init();
MyDataSrc = NewObject<UMyData>();
MyDataSrc->SetMyString(TEXT("데이터"));
MyDataSrc->SetMyInt(59);
{
const FString ObjectDataFileName(TEXT("ObjectData.bin"));
FString ObjectDataAbsolutePath = FPaths::Combine(*SavedDir, *ObjectDataFileName);
FPaths::MakeStandardFilename(ObjectDataAbsolutePath);
// 객체 직렬화 (Serialization)
TArray<uint8> BufferArray;
FMemoryWriter MemoryWriterAr(BufferArray);
MyDataSrc->Serialize(MemoryWriterAr);
// BufferArray -> 파일로 내용 전달
if (TUniquePtr<FArchive> FileWriterAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*ObjectDataAbsolutePath)))
{
*FileWriterAr << BufferArray;
FileWriterAr->Close();
}
// FileReaderAr로 내용을 읽은 뒤 BufferArrayFromFile 로 내용 전달
TArray<uint8> BufferArrayFromFile;
if (TUniquePtr<FArchive> FileReaderAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileReader(*ObjectDataAbsolutePath)))
{
*FileReaderAr << BufferArrayFromFile;
FileReaderAr->Close();
}
// MemoryReader를 통해 Buffer의 내용을 이용하여 Object 생성. (DeSerialization)
FMemoryReader MemoryReaderAr(BufferArrayFromFile);
UMyData* MyDataDest = NewObject<UMyData>();
MyDataDest->Serialize(MemoryReaderAr);
}
}
참조
이 글은 이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++를 수강하고 작성한 글입니다.
'Unreal' 카테고리의 다른 글
UE 액터 컴포넌트 (1) | 2024.06.08 |
---|---|
UE C++ 빌드 시스템 (0) | 2024.06.01 |
UE C++ 메모리 관리 (0) | 2024.05.19 |
UE C++ Design - Interface (0) | 2024.04.27 |
UE C++ 오브젝트 (1) | 2024.04.13 |