개발일지공부아이디어
    • 4월
    • 3월
    • 2월
    • 1월
  • 17

    26. 4. 17.

  • 16

    26. 4. 16.

  • 15

    26. 4. 15.

  • 14

    26. 4. 14.

  • 10

    26. 4. 10.

  • 09

    26. 4. 9.

  • 08

    26. 4. 8.

로딩 중...

2026. 4. 15.

언리얼 오브젝트 관리 Ⅰ - 직렬화

직렬화란?

  • 오브젝트나 연결된 오브젝트의 묶음(graph)을 바이트 스트림으로 변환하는 과정
  • 양방향을 모두 포함(deserialization)

용례

  • 현재 프로그램의 상태를 저장하고 필요한 때 복원(세이브파일)
  • 현재 객체의 정보를 클립보드에 복사해 다른 프로그램에 전송
  • 네트워크를 통해 현재 프로그램의 상태를 다른 컴퓨터에서 복원(멀티플레이어)
  • 데이터 압축, 암호화를 통해 데이터를 효율적이고 안전하게 보관(DB)

구현 시 고려할 점

  • 데이터 레이아웃: 오브젝트가 소유한 데이터를 얼만큼 변환할지
  • 이식성: 서로 다른 시스템에 전송해도 이식될 수 있는가
  • 버전 관리; 새로운 기능이 추가될 때 어떻게 확장하고 처리할지
  • 성능: 네트워크 비용을 줄이기 위해 어떤 포맷을 사용할지
  • 보안: 어떻게 안전하게 데이터를 보호할지
  • 에러 처리: 전송 과정에서 문제가 발생할 경우 어떻게 처리할지

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

  • 직렬화 시스템을 위해 제공하는 FArchive 클래스와 연산자
    • Shift(<<) operator
  • 다양한 아카이브 클래스 제공
    • 메모리 아카이브(FMemoryReader, FMemoryWriter)
    • 파일 아카이브(FArchiveFileReaderGeneric, FArchiveFileWriterGeneric)
    • 기타 언리얼 오브젝트 관련 아카이브(FArchiveUObject)

일반 오브젝트 쓰기/읽기

// StudentData.h#pragma once#include "CoreMinimal.h"struct FStudentData{	FStudentData() {}	FStudentData(int32 InOrder, const FString& InName) : Order(InOrder), Name(InName) {}	friend FArchive& operator<<(FArchive& Arc, FStudentData& InStudentData)	{		Arc << InStudentData.Order;		Arc << InStudentData.Name;		return Arc;	}	int32 Order = -1;	FString Name = TEXT("홍길동");};

<< 연산자 오버로딩 통해 일반 오브젝트 한꺼번에 아카이브 객체에 직렬화/역직렬화

void UMyGameInstance::Init(){	Super::Init();	FStudentData RawDataSource(23, TEXT("곽민규"));	// 파일로 저장하기 위한 경로 생성	const FString SavePath = FPaths::Combine(FPlatformMisc::ProjectDir(), TEXT("Saved"));	{		// 쓰기		const FString RawDataFileName(TEXT("RawData.bin"));		FString RawDataAbsolutePath = FPaths::Combine(SavePath, RawDataFileName);		UE_LOG(LogTemp, Log, TEXT("저장할 파일 전체 경로: %s"), *RawDataAbsolutePath);		FPaths::MakeStandardFilename(RawDataAbsolutePath);		UE_LOG(LogTemp, Log, TEXT("변경할 파일 전체 경로: %s"), *RawDataAbsolutePath);		FArchive* RawFileWriterArc = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);		if (RawFileWriterArc)		{			*RawFileWriterArc << RawDataSource;			// 쓰기용 아카이브 닫고, 원시포인터이므로 수동 삭제처리			RawFileWriterArc->Close();			delete RawFileWriterArc;			RawFileWriterArc = nullptr;		}		// 읽기		FStudentData RawDataDeserialized;		FArchive* RawFileReaderArc = IFileManager::Get().CreateFileReader(*RawDataAbsolutePath);		if (RawFileReaderArc)		{			*RawFileReaderArc << RawDataDeserialized;			RawFileReaderArc->Close();			delete RawFileReaderArc;			RawFileReaderArc = nullptr;			UE_LOG(LogTemp, Log, TEXT("[RawData] 이름: %s, 순번: %d"), *RawDataDeserialized.Name, RawDataDeserialized.Order);		}	}

FPaths, FArchive, IFileManager 모듈 사용(언리얼 내장이라 include 불필요)

언리얼 오브젝트 쓰기/읽기

// Student.h#pragma once#include "CoreMinimal.h"#include "UObject/NoExportTypes.h"#include "Student.generated.h"/** *  */UCLASS()class UEPART1_API UStudent : public UObject{	GENERATED_BODY()	public:	UStudent();	virtual void Serialize(FArchive& Arc) override;	int32 GetOrder() const { return Order; }	void SetOrder(int32 InOrder) { Order = InOrder; }	FString GetName() const { return Name; }	void SetName(const FString& InName) { Name = InName; }private:	UPROPERTY()	int32 Order;	UPROPERTY()	FString Name;};// Student.cpp#include "Student.h"UStudent::UStudent(){	Order = -1;	Name = TEXT("홍길동");}void UStudent::Serialize(FArchive& Arc){	Super::Serialize(Arc); //UObject native serialize 먼저 실행	Arc << Order;	Arc << Name;}

UObject의 Serialize 함수 오버라이드 후 매개변수 아카이브 객체에 직렬화/역직렬화 추가

StudentSource = NewObject<UStudent>();	StudentSource->SetName(TEXT("곽민규"));	StudentSource->SetOrder(10);	{		// 쓰기		const FString& ObjectDataFileName(TEXT("ObjectData.bin"));		FString ObjectDataAbsolutePath = FPaths::Combine(SavePath, ObjectDataFileName);		FPaths::MakeStandardFilename(ObjectDataAbsolutePath);		TArray<uint8> BufferArray;		FMemoryWriter MemoryWriter(BufferArray);		StudentSource->Serialize(MemoryWriter); // FMemoryWriter도 FArchive를 상속함		// FArchive 객체를 스마트 포인터로 감싸줌으로써 언리얼 엔진이 자동 관리하게 이관		TUniquePtr<FArchive> FileWriteArc = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*ObjectDataAbsolutePath));		if (FileWriteArc)		{			*FileWriteArc << BufferArray;			FileWriteArc->Close();		}		// 읽기		TArray<uint8> BufferArrayFromFile;		TUniquePtr<FArchive> FileReaderArc = TUniquePtr<FArchive>(IFileManager::Get().CreateFileReader(*ObjectDataAbsolutePath));		if (FileReaderArc)		{			*FileReaderArc << BufferArrayFromFile;			FileReaderArc->Close();			// 바이트 스트림을 FMemoryReader로 복원			FMemoryReader MemoryReaderArc(BufferArrayFromFile);			UStudent* StudentDest = NewObject<UStudent>();			StudentDest->Serialize(MemoryReaderArc);			UE_LOG(LogTemp, Log, TEXT("[ObjectData] 이름: %s, 순번: %d"), *StudentDest->GetName(), StudentDest->GetOrder());		}	}}

JSON 직렬화 언리얼 스마트 포인터 라이브러리 개요

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

JSON 모듈을 사용하려면 빌드설정을 바꿔줘야 함

// 프로젝트이름.Build.cs...PublicDependencyModuleNames.AddRange(	new string[] 	{ 		"Core", 		"CoreUObject", 		"Engine",		"InputCore", 		"EnhancedInput", 		"Json",            // 추가 필요		"JsonUtilities"    // 추가 필요	});
// JSON 직렬화{	const FString JSONDataFileName(TEXT("StudentData.json"));	FString JSONDataAbsolutePath = FPaths::Combine(SavePath, JSONDataFileName);	FPaths::MakeStandardFilename(JSONDataAbsolutePath);	TSharedRef<FJsonObject> JSONObjectRef = MakeShared<FJsonObject>();	// Reflection 시스템으로 얻을 수 있는 UClass, 언리얼 오브젝트, JSON Out 객체	FJsonObjectConverter::UStructToJsonObject(StudentSource->StaticClass(), StudentSource, JSONObjectRef);	FString JSONOutString;	TSharedRef<TJsonWriter<TCHAR>> JSONWriterArc = TJsonWriterFactory<TCHAR>::Create(&JSONOutString);	if (FJsonSerializer::Serialize(JSONObjectRef, JSONWriterArc))	{		FFileHelper::SaveStringToFile(JSONOutString, *JSONDataAbsolutePath);	}}
  • FFileHelper::SaveStringToFile(): ANSI/Unicode, 플랫폼 상관없이 안전하게 파일에 저장
// Json 역직렬화FString JsonInString;FFileHelper::LoadFileToString(JsonInString, *JsonDataAbsolutePath);UE_LOG(LogTemp, Log, TEXT("[JSON] %s"), *JsonInString);TSharedRef<TJsonReader<TCHAR>> JsonReaderArc = TJsonReaderFactory<TCHAR>::Create(JsonInString);TSharedPtr<FJsonObject> JsonObjectDest;if (FJsonSerializer::Deserialize(JsonReaderArc, JsonObjectDest)){	UStudent* JsonStudentDest = NewObject<UStudent>();	if (FJsonObjectConverter::JsonObjectToUStruct(JsonObjectDest.ToSharedRef(), JsonStudentDest->GetClass(), JsonStudentDest))	{		UE_LOG(LogTemp, Log, TEXT("[JSONData] 이름: %s, 순번: %d"), *JsonStudentDest->GetName(), JsonStudentDest->GetOrder());	}}

JSON을 문자열로 읽으면(FFileHelper::LoadFileToString) 개행과 들여쓰기 때문에(\n \t) 로그찍으면 잘 안보임

왼쪽 화살표오른쪽 화살표