1258 words
6 minutes
[UE5/System] 언리얼 엔진 데이터 관리 및 시스템 구조

언리얼 엔진의 데이터 저장 방식부터 모듈 시스템까지 묶어서 정리하였다.


1. 직렬화 (Serialization)#

언리얼에서 UObject의 데이터를 저장하고 불러들이는 기본 원리이다.

1.1 직렬화와 언리얼의 방식#

  • 직렬화(Serialization): 메모리 상의 오브젝트를 디스크 저장이나 네트워크 전송이 가능한 바이트 스트림(Byte Stream)으로 변환하는 과정이다. 역직렬화(Deserialization)는 그 반대다.
  • 언리얼의 구현: 언리얼은 FArchive 클래스와 시프트 연산자(<<)를 재정의하여 사용한다.
  • 저장과 로딩 모두 Ar << Data 하나의 코드로 통일하여 작성한다. (아카이브 내부 상태인 IsLoading(), IsSaving()이 방향을 결정함)

1.2 실습: 일반 C++ 구조체 직렬화#

가장 기초적인 단계로 일반 구조체를 파일로 입출력하는 방법이다.

struct FStudentData
{
    int32 Order = -1;
    FString Name = TEXT("기본이름");

    // 직렬화를 위한 연산자 오버로딩 (FArchive 통로 약속)
    friend FArchive& operator<<(FArchive& Ar, FStudentData& InData)
    {
        Ar << InData.Order;
        Ar << InData.Name;
        return Ar;
    }
};

// 파일 저장 예시
const FString RawDataAbsolutePath = FPaths::Combine(*SavedDir, TEXT("RawData.bin"));
FStudentData RawDataSrc(16, TEXT("홍길동"));

FArchive* FileWriterAr = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);
if (FileWriterAr)
{
    *FileWriterAr << RawDataSrc; // 오버로딩된 operator<< 호출
    FileWriterAr->Close();
    delete FileWriterAr;
}

1.3 실습: UObject 직렬화 (2단계 방식)#

UObject는 자체적으로 Serialize 함수를 가진다. 보통 객체를 메모리 버퍼로 먼저 변환한 뒤, 그 버퍼를 파일로 저장하는 2단계 방식을 주로 사용한다.

// 1. 객체를 메모리(Buffer)에 쓰기
TArray<uint8> BufferArray;
FMemoryWriter MemoryWriterAr(BufferArray); 
StudentSrc->Serialize(MemoryWriterAr); 

// 2. 메모리를 파일에 쓰기 (TUniquePtr 활용으로 자동 메모리 해제)
if (TUniquePtr<FArchive> FileWriterAr = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*ObjectPath)))
{
    *FileWriterAr << BufferArray; 
    FileWriterAr->Close();
}

JSON 직렬화 팁 JSON 포맷을 사용하려면 Build.cs에 반드시 "Json", "JsonUtilities" 모듈을 추가해야 한다.

  • 흐름: [UObject] <-> (Converter) <-> [FJsonObject] <-> (Serializer) <-> [String]


2. 패키지(Package)와 에셋(Asset)#

직렬화된 데이터를 에디터가 인식할 수 있는 .uasset 형태로 관리하는 구조이다.

2.1 패키지와 에셋의 관계#

  • 패키지 (UPackage): 여러 언리얼 오브젝트를 포장하는 최상위 오브젝트이다.
  • 에셋 (Asset): 패키 내부에 포함된 오브젝트 중 에디터 콘텐츠 브라우저에 노출되는 메인 오브젝트이다.
  • 일반적으로 1개의 패키지는 1개의 에셋을 가진다.

2.2 오브젝트 패스와 에셋 로딩 전략#

에셋은 메모리 관리를 위해 고유한 경로인 오브젝트 패스 (Object Path)로 관리된다.

  • 형식: 패키지명.에셋명 (예: /Game/Student.TopStudent)

메모리 부하를 줄이기 위해 필요한 시점에 로딩하는 전략을 사용한다.

  1. 생성자 로딩 (ConstructorHelpers): 클래스 생성자에서 미리 로드. (에러 시 크래시 주의)
  2. 동기 로딩 (LoadObject): 런타임에 즉시 로드. (프레임 드랍 유발 가능)
  3. 비동기 로딩 (StreamableManager): 백그라운드에서 로딩 후 콜백 실행. (대규모 게임 필수)
// 비동기 로딩 예시
FStreamableManager StreamableManager;
const FString Path = TEXT("/Game/Student.TopStudent");

StreamableManager.RequestAsyncLoad(Path, [&]() 
{
    // 로딩 완료 후 실행될 람다
});

3. 모듈(Module)과 플러그인(Plugin)#

저장된 데이터와 작성된 코드들이 실제로 어떻게 엔진에서 뭉쳐서 돌아가는지에 대한 구조이다.

3.1 C++ 프로젝트와 모듈#

언리얼 엔진의 모든 소스 코드는 모듈(Module) 단위로 구성된다.

  • 블루프린트 프로젝트는 엔진 기본 모듈만 사용하지만, C++ 프로젝트는 개발자가 직접 작성한 커스텀 C++ 모듈을 엔진에 추가하여 작동하는 원리이다.
  • 컴파일 결과물은 에디터용 빌드 시 DLL 동적 라이브러리 형태로 생성된다.

3.2 UBT (Unreal Build Tool)#

언리얼은 멀티 플랫폼 빌드를 위해 Visual Studio에 종속되지 않고 자체적인 UBT (C# 프로그램)를 사용한다.

이를 위해 두 가지 중요한 C# 설정 파일이 필요하다.

  • .Target.cs (타겟 설정): 게임/에디터 등 전체 솔루션의 빌드 환경 설정.
  • .Build.cs (모듈 설정): 특정 모듈의 의존성(위의 JSON 모듈 추가 등) 설정.

[UE 5.7 기준 Editor 타겟 파일 예시]

using UnrealBuildTool;
using System.Collections.Generic;

public class UnrealBuildSystemEditorTarget : TargetRules
{
    public UnrealBuildSystemEditorTarget(TargetInfo Target) : base(Target)
    {
        Type = TargetType.Editor;
        DefaultBuildSettings = BuildSettingsVersion.V6;
        IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
        CppStandard = CppStandardVersion.Cpp20; // C++20 지원
        
        bOverrideBuildEnvironment = true; 
        ExtraModuleNames.AddRange(new string[] { "UnrealBuildSystem" });
    }
}

요약 (System Architecture Flow)#

  1. 코드 (Module): .Build.cs.Target.cs를 통해 C++ 코드를 UBT가 DLL 모듈로 컴파일한다.
  2. 데이터 생성 (Serialization): 컴파일된 코드가 실행되며 FArchive를 통해 데이터를 직렬화한다.
  3. 포장 및 관리 (Package & Asset): 직렬화된 데이터는 UPackage로 묶여 콘텐츠 브라우저에 .uasset으로 노출되며, 오브젝트 패스를 통해 효율적으로 로드된다.