Object Graph
하나의 개체는 다른 개체를 속성으로 들고 있을 수 있고
이러한 관계를 오브젝트 그래프라고 표현한다

직렬화
오브젝트 그래프를 바이트 스트림으로 변환하는 과정이다
복잡한 데이터를 일렬로 세우기 때문에 직렬화라고 부른다
Serialization: 오브젝트 그래프에서 바이트 스트림으로의 변환
Deserialization: 바이트 스트림에서 오브젝트 그래프로의 변환


직렬화의 활용
현재 게임의 상태를 저장하고 다시 복원할 때 주로 사용된다
블루프린트 코드를 복사해서 다른 곳에서 붙여 넣기도 가능하고
캐릭터의 위치를 네트워크상으로 다른 컴퓨터에서 재현할 때도 사용된다
언리얼 엔진의 직렬화 시스템
직렬화 시스템을 위해서는
언리얼에서 제공하는 클래스 FArchive와 << 연산자를 활용하면 된다
다양한 아카이브 클래스가 제공되고
메모리 아카이브(FMemoryReader, FMemoryWriter),
파일 아카이브(FArchiveFileReaderGeneric, FArchiveFileWriterGeneric),
언리얼 오브젝트와 관련된 아카이브(FArchiveUObject),
Json 직렬화 기능도 별도의 라이브러리로 제공한다
USTRUCT
USTRUCT를 데이터 저장/전송에 사용하는 경우가 많다
주의할 점으로는 제한적인 리플렉션 시스템으로
UPROPERTY()만 가능하고 UFUNCTION()은 불가능하다
또한,
스택 메모리에 저장되기에 힙 메모리 할당을 하는 NewObject API를 사용할 수 없다
USTRUCT를 사용한 예제
// SPigeon.h
...
USTRUCT()
struct FPigeonData
{
GENERATED_BODY()
public:
FPigeonData()
{
}
FPigeonData(const FString& InName, int32 InID)
: Name(InName) // 언리얼 코딩 컨벤션에는 입력 전용 매개변수명에 In-을 붙힘. 출력 전용은 Out-
, ID(InID)
{
}
friend FArchive& operator<<(FArchive& InArchive, FPigeonData& InPigeonData)
{
InArchive << InPigeonData.Name;
InArchive << InPigeonData.ID;
return InArchive;
}
public:
UPROPERTY()
FString Name;
UPROPERTY()
int32 ID;
};
...
class STUDYPROJECT_API USPigeon
...
{
...
public:
...
const FString& GetPigeonName() const { return Name; }
void SetPigeonName(const FString& InName) { Name = InName; }
int32 GetPigeonID() const { return ID; }
void SetPigeonID(const int32& InID) { ID = InID; }
private:
...
UPROPERTY()
int32 ID;
};
// SGameInstance.cpp
#include "Game/SGameInstance.h"
#include "Example/SFlyable.h"
#include "Example/SPigeon.h"
#include "Example/SEagle.h"
USGameInstance::USGameInstance()
{
}
void USGameInstance::Init()
{
Super::Init();
USPigeon* Pigeon76 = NewObject<USPigeon>();
Pigeon76->SetPigeonName(TEXT("Pigeon76"));
Pigeon76->SetPigeonID(76);
UE_LOG(LogTemp, Log, TEXT("[Pigeon76] Name: %s, ID: %d"), *Pigeon76->GetPigeonName(), Pigeon76->GetPigeonID());
const FString SavedDirectoryPath = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));
UE_LOG(LogTemp, Log, TEXT("SavedDirectoryPath: %s"), *SavedDirectoryPath);
const FString SavedFileName(TEXT("SerializedPigeon76Data.bin"));
FString AbsoluteFilePath = FPaths::Combine(*SavedDirectoryPath, *SavedFileName);
UE_LOG(LogTemp, Log, TEXT("RelativeFilePath: %s"), *AbsoluteFilePath);
FPaths::MakeStandardFilename(AbsoluteFilePath);
UE_LOG(LogTemp, Log, TEXT("AbsoluteFilePath: %s"), *AbsoluteFilePath);
FPigeonData SerializedPigeon76Data(Pigeon76->GetPigeonName(), Pigeon76->GetPigeonID());
FArchive* WriterArchive = IFileManager::Get().CreateFileWriter(*AbsoluteFilePath);
if (WriterArchive != nullptr)
{
*WriterArchive << SerializedPigeon76Data;
WriterArchive->Close();
delete WriterArchive;
WriterArchive = nullptr;
}
FPigeonData DeserializedPigeon76Data;
FArchive* ReaderArchive = IFileManager::Get().CreateFileReader(*AbsoluteFilePath);
if (ReaderArchive != nullptr)
{
*ReaderArchive << DeserializedPigeon76Data;
ReaderArchive->Close();
delete ReaderArchive;
ReaderArchive = nullptr;
}
USPigeon* ClonedPigeon76 = NewObject<USPigeon>();
ClonedPigeon76->SetPigeonName(DeserializedPigeon76Data.Name);
ClonedPigeon76->SetPigeonID(DeserializedPigeon76Data.ID);
UE_LOG(LogTemp, Log, TEXT("[ClonedPigeon76] Name: %s, ID: %d"), *ClonedPigeon76->GetPigeonName(), ClonedPigeon76->GetPigeonID());
}
...
Serialize() 함수를 사용한 예제
// SPigeon.h
...
class STUDYPROJECT_API USPigeon
...
{
...
public:
...
virtual void Serialize(FArchive& Ar) override;
private:
...
};
// SPigeon.cpp
...
void USPigeon::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar << Name;
Ar << ID;
}
// SGameInstance.cpp
...
void USGameInstance::Init()
{
Super::Init();
USPigeon* Pigeon76 = NewObject<USPigeon>();
Pigeon76->SetPigeonName(TEXT("Pigeon76"));
Pigeon76->SetPigeonID(76);
UE_LOG(LogTemp, Log, TEXT("[Pigeon76] Name: %s, ID: %d"), *Pigeon76->GetPigeonName(), Pigeon76->GetPigeonID());
const FString SavedDirectoryPath = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));
const FString SavedFileName(TEXT("SerializedPigeon76Data.bin"));
FString AbsoluteFilePath = FPaths::Combine(*SavedDirectoryPath, *SavedFileName);
FPaths::MakeStandardFilename(AbsoluteFilePath);
TArray<uint8> BufferForWriter;
FMemoryWriter MemoryWriterArchive(BufferForWriter);
Pigeon76->Serialize(MemoryWriterArchive);
TUniquePtr<FArchive> WriterArchive = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*AbsoluteFilePath));
if (WriterArchive != nullptr)
{
*WriterArchive << BufferForWriter;
WriterArchive->Close();
WriterArchive = nullptr;
}
TArray<uint8> BufferForReader;
TUniquePtr<FArchive> ReaderArchive = TUniquePtr<FArchive>(IFileManager::Get().CreateFileReader(*AbsoluteFilePath));
if (ReaderArchive != nullptr)
{
*ReaderArchive << BufferForReader;
ReaderArchive->Close();
ReaderArchive = nullptr;
}
FMemoryReader MemoryReaderArchive(BufferForReader);
USPigeon* ClonedPigeon76 = NewObject<USPigeon>();
ClonedPigeon76->Serialize(MemoryReaderArchive);
UE_LOG(LogTemp, Log, TEXT("[ClonedPigeon76] Name: %s, ID: %d"), *ClonedPigeon76->GetPigeonName(), ClonedPigeon76->GetPigeonID());
}
...
Json을 사용한 예제
Json은
웹 환경에서 서버와 클라 사이에 데이터를 주고 받을 때
사용하는 텍스트 기반 데이터 포맷이다
장점:
텍스트 임에도 데이터 크기가 가볍다
읽고 이해하기 쉽고 편하다
웹 통신의 표준으로 사용된다
단점:
지원하는 타입이 적으며
문자, 숫자, 불리언, 널, 배열, 오브젝트만 가능하다
텍스트 형식으로만 사용할 수 있어서 극도의 효율 추구가 불가능하다
Json 데이터 유형으로는
- 오브젝트 {}
오브젝트 내 데이터는 키-벨류 조합으로 구성된다
ex. { ”Key” : 77 }
- 배열 []
배열 내 데이터는 벨류로만 구성된다
ex. [ “Value1”, “Value2”, “Value3” ]
- 이외 데이터
문자열(”String”), 숫자(7, 3.141592), 불리언(true, false), 널(null)로 구성됨
// Json, JsonUtilities 모듈 추가가 필요
// StudyProject.Build.cs
using UnrealBuildTool;
public class StudyProject : ModuleRules
{
public StudyProject(ReadOnlyTargetRules Target) : base(Target)
{
...
PublicDependencyModuleNames.AddRange(new string[]
{
"Core", "CoreUObject", "Engine", "InputCore",
// Json Modules
"Json", "JsonUtilities",
});
...
}
}
// SGameInstance.cpp
...
#include "JsonObjectConverter.h"
#include "UObject/SavePackage.h"
...
void USGameInstance::Init()
{
Super::Init();
USPigeon* Pigeon76 = NewObject<USPigeon>();
Pigeon76->SetPigeonName(TEXT("Pigeon76"));
Pigeon76->SetPigeonID(76);
UE_LOG(LogTemp, Log, TEXT("[Pigeon76] Name: %s, ID: %d"), *Pigeon76->GetPigeonName(), Pigeon76->GetPigeonID());
const FString SavedDirectoryPath = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));
const FString SavedFileName(TEXT("SerializedPigeon76JsonData.txt"));
FString AbsoluteFilePath = FPaths::Combine(*SavedDirectoryPath, *SavedFileName);
FPaths::MakeStandardFilename(AbsoluteFilePath);
TSharedRef<FJsonObject> Pigeon76JsonObject = MakeShared<FJsonObject>();
FJsonObjectConverter::UStructToJsonObject(Pigeon76->GetClass(), Pigeon76, Pigeon76JsonObject);
FString WritedJsonString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriterArchive = TJsonWriterFactory<TCHAR>::Create(&WritedJsonString);
if (FJsonSerializer::Serialize(Pigeon76JsonObject, JsonWriterArchive) == true)
{
FFileHelper::SaveStringToFile(WritedJsonString, *AbsoluteFilePath);
}
FString ReadedJsonString;
FFileHelper::LoadFileToString(ReadedJsonString, *AbsoluteFilePath);
TSharedRef<TJsonReader<TCHAR>> JsonReaderArchive = TJsonReaderFactory<TCHAR>::Create(ReadedJsonString);
USPigeon* ClonedPigeon76 = NewObject<USPigeon>();
TSharedPtr<FJsonObject> ClonedPigeon76JsonObject = nullptr;
if (FJsonSerializer::Deserialize(JsonReaderArchive, ClonedPigeon76JsonObject) == true)
{
if (FJsonObjectConverter::JsonObjectToUStruct(ClonedPigeon76JsonObject.ToSharedRef(), ClonedPigeon76->GetClass(), ClonedPigeon76) == true)
{
UE_LOG(LogTemp, Log, TEXT("[ClonedPigeon76] Name: %s, ID: %d"), *ClonedPigeon76->GetPigeonName(), ClonedPigeon76->GetPigeonID());
}
}
}
...
'게임 엔진 > [코드조선] 언리얼' 카테고리의 다른 글
언리얼 문자열 - TCHAR, FString (0) | 2024.05.10 |
---|---|
언리얼 오브젝트 기능 - 델리게이트 (0) | 2024.05.10 |
언리얼 오브젝트 기능 - Smart Pointer (0) | 2024.05.09 |
언리얼 오브젝트 기능 - C++ 로우 포인터 문제 해결 (0) | 2024.05.09 |
언리얼 오브젝트 기능 - Garbage Collection (0) | 2024.05.09 |