묻공러
언리얼 오브젝트 기능 - Serialization
묻공러
묻지마공부
묻공러
전체
오늘
어제
  • 분류 전체보기 (494) N
    • C (54)
      • [코드조선] C 핵심 (35)
      • [언어본색] C 기초 (19)
    • C++ (72)
      • [루키스] C++ (9)
      • [루키스] 콜백함수 (6)
      • [루키스] STL (8)
      • [루키스] Modern C++ (11)
      • [노코프] C++ (10)
      • [노코프] Tips (16)
      • [일지] C++ (12)
    • C# (7) N
      • [루키스] C# (7) N
      • [루키스] 자료구조 (0)
      • [루키스] 실전 문법 (0)
    • 자료구조 & 알고리즘 (50)
      • [코드조선] C 자료구조 & 알고리즘 (6)
      • [합격자되기] C++ 코딩테스트 (12)
      • [루키스] C++ 자료구조 & 알고리즘 (32)
    • CS (69)
      • [널널한 개발자] CS 개론 (19)
      • [혼자 공부하는] 컴퓨터 구조 (16)
      • [혼자 공부하는] 운영체제 (18)
      • [널널한 개발자] 네트워크 (16)
    • 게임 그래픽스 (46)
      • [전북대] OpenGL (25)
      • [일지] DirectX (21)
    • 게임 엔진 (124)
      • [코드조선] 언리얼 (53)
      • [코드조선] 언리얼 데디서버 (8)
      • [일지] 언리얼 (59)
      • [일지] 언리얼 (2) (3)
      • 유니티 (1)
    • 게임 서버 (17)
    • 게임 수학 & 물리 (19)
      • 게임 수학 (12)
      • 게임 물리 (7)
    • GIT & GITHUB (4)
    • 영어 (18)
      • [The Outfit] 대본 공부 (11)
      • the others (7)
    • 그 외 (14)
      • In (5)
      • Out (5)
      • Review (4)

인기 글

최근 글

hELLO · Designed By 정상우.
게임 엔진/[코드조선] 언리얼

언리얼 오브젝트 기능 - Serialization

2024. 5. 10. 10:30

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
언리얼 오브젝트 기능 - 델리게이트  (1) 2024.05.10
언리얼 오브젝트 기능 - Smart Pointer  (0) 2024.05.09
언리얼 오브젝트 기능 - C++ 로우 포인터 문제 해결  (0) 2024.05.09
언리얼 오브젝트 기능 - Garbage Collection  (0) 2024.05.09
'게임 엔진/[코드조선] 언리얼' 카테고리의 다른 글
  • 언리얼 문자열 - TCHAR, FString
  • 언리얼 오브젝트 기능 - 델리게이트
  • 언리얼 오브젝트 기능 - Smart Pointer
  • 언리얼 오브젝트 기능 - C++ 로우 포인터 문제 해결
묻공러
묻공러
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.