본문 바로가기

언리얼 엔진 리플렉션 시스템 Unreal Engine Reflection System

@iamrain2025. 10. 23. 10:14

1. 리플렉션(Reflection)이란?

리플렉션은 컴퓨터 프로그래밍 용어로, 프로그램이 실행 시간에 자기 자신을 검사(Introspection)하고 수정(Modification)할 수 있는 능력을 의미한다. 즉, 코드의 구조, 타입, 변수, 메소드 등의 메타데이터에 접근하여 동적으로 객체를 생성하거나, 함수를 호출하거나, 변수 값을 변경하는 등의 작업을 수행할 수 있다.

C++는 네이티브하게 제한적인 RTTI(Run-Time Type Information)만 지원할 뿐, C#이나 Java와 같은 완전한 리플렉션 시스템을 갖추고 있지 않다. 언리얼 엔진은 이러한 한계를 극복하고 엔진의 핵심 기능들을 구현하기 위해 자체적인 리플렉션 시스템을 구축했으며, 이를 프로퍼티 시스템(Property System)이라고도 부른다.

2. 언리얼 리플렉션 시스템의 핵심: UHT와 매크로

언리얼 리플렉션 시스템의 마법은 언리얼 헤더 툴(Unreal Header Tool, UHT)이라는 코드 생성기에서 시작된다. 개발자가 C++ 헤더 파일(.h)에 다음과 같은 특정 매크로를 사용하여 클래스, 구조체, 변수, 함수 등을 선언하면, 빌드 과정에서 UHT가 이 파일들을 먼저 파싱한다.

  • UCLASS(): 클래스를 리플렉션 시스템에 등록한다. UObject를 상속받는 클래스에만 사용할 수 있다.
  • USTRUCT(): 구조체를 리플렉션 시스템에 등록한다.
  • UPROPERTY(): 멤버 변수를 리플렉션 시스템에 등록한다. (프로퍼티)
  • UFUNCTION(): 멤버 함수를 리플렉션 시스템에 등록한다.
  • UENUM(): 열거형을 리플렉션 시스템에 등록한다.

UHT는 이 매크로들과 그 안의 지정자(Specifiers)들을 분석하여, 리플렉션에 필요한 메타데이터를 담은 C++ 코드를 자동으로 생성한다. 이 생성된 코드는 *.generated.h*.gen.cpp라는 두 가지 파일로 만들어지며, Intermediate 폴더에 저장된다.

  • MyClass.h -> (UHT) -> MyClass.generated.h, MyClass.gen.cpp

이후 일반 C++ 컴파일러가 원본 코드와 UHT가 생성한 코드를 함께 컴파일하여 최종 실행 파일을 만들어낸다. 이 과정을 통해 C++ 네이티브 코드 내에서 완벽한 리플렉션 기능이 동작할 수 있게 된다.

3. 동작 계층 구조

  1. 사용자 코드 계층 (User Code Layer)
    • 개발자가 .h 파일에 UCLASS, UPROPERTY 등의 매크로를 사용하여 게임플레이 코드를 작성한다.
  2. 빌드 시스템 계층 (Build System Layer)
    • Unreal Build Tool (UBT)이 빌드를 시작하면, C++ 컴파일러를 직접 호출하기 전에 Unreal Header Tool (UHT)을 먼저 실행한다.
    • UHT는 프로젝트 내의 모든 헤더 파일을 스캔하여 리플렉션 매크로를 찾고, 해당 정보를 기반으로 C++ 코드를 생성한다. (.generated.h, .gen.cpp)
  3. 컴파일 계층 (Compilation Layer)
    • UBT는 원본 소스 코드와 UHT가 생성한 소스 코드를 모두 C++ 컴파일러에 전달하여 컴파일한다.
    • GENERATED_BODY()GENERATED_UCLASS_BODY() 매크로가 .generated.h 파일에 정의된 실제 코드로 치환되면서, 리플렉션 시스템과 상호작용하는 데 필요한 함수들이 클래스에 주입된다.
  4. 런타임 계층 (Runtime Layer)
    • 게임이 실행되면, UHT가 생성한 코드에 의해 각종 메타데이터 객체들이 메모리에 로드되고 초기화된다.
    • UClass: UCLASS에 대한 정보를 담는 객체. 클래스의 모든 프로퍼티, 함수, 부모 클래스 정보 등을 알 수 있다.
    • FProperty: UPROPERTY로 선언된 변수 하나하나에 대한 정보를 담는 객체. 변수의 타입, 크기, 오프셋, 지정자 등을 알 수 있다.
    • UFunction: UFUNCTION에 대한 정보를 담는 객체.
    • 엔진의 다른 시스템(가비지 컬렉터, 에디터, 블루프린트 VM 등)은 이 런타임 메타데이터에 접근하여 동적인 작업을 수행한다.

4. 리플렉션 시스템의 내부 구조와 구현

리플렉션 데이터의 최상위 기본 클래스는 FField다. UStruct (그리고 그 자식인 UClassUFunction)와 FProperty, UEnum 등이 모두 FField를 상속받는다. 이들은 단일 연결 리스트(Singly Linked List)로 서로 연결되어, 특정 UClass에 속한 모든 프로퍼티나 함수를 순회하는 것이 가능하다.

  • UClass 객체는 내부에 FProperty 객체들의 연결 리스트를 가리키는 포인터(PropertyLink)와 UFunction 객체들의 연결 리스트를 가리키는 포인터(FunctionLink)를 가진다.
  • FProperty 객체는 다음 FProperty를 가리키는 Next 포인터를 가지고 있다.

이러한 구조 덕분에 런타임에 For(TFieldIterator<FProperty> It(MyClass); It; ++It) 와 같은 코드를 통해 클래스의 모든 프로퍼티를 순회하며 정보를 얻거나 값을 조작할 수 있다.

5. 주요 활용 사례 및 비교

기능 설명 C++ RTTI와의 비교
에디터 상세 패널(Details Panel) UPROPERTY로 노출된 변수들을 자동으로 에디터 UI에 표시하고 편집할 수 있게 합니다. UHT가 생성한 메타데이터를 읽어 어떤 타입의 변수인지, 편집 가능한지 등을 파악하여 적절한 UI 위젯을 생성합니다. RTTI는 타입 이름 정도만 알 수 있을 뿐, 변수 이름이나 편집 가능 여부 같은 상세한 메타데이터를 제공하지 않아 이런 기능 구현이 불가능합니다.
가비지 컬렉션(Garbage Collection) UPROPERTY로 선언된 UObject 포인터들을 추적하여 객체 간의 참조 관계를 파악합니다. 더 이상 어떤 UPROPERTY에 의해서도 참조되지 않는 UObject는 가비지로 간주되어 메모리에서 해제됩니다. RTTI는 객체 참조 관계를 추적하는 기능이 전혀 없습니다.
직렬화(Serialization) UPROPERTY로 지정된 변수들의 값을 디스크에 저장(Saving)하거나 디스크에서 읽어(Loading) 객체의 상태를 복원합니다. 리플렉션 시스템이 각 프로퍼티의 메모리 위치와 타입을 알기 때문에 가능합니다. RTTI로는 이 기능 구현이 불가능합니다.
블루프린트 연동(Blueprint Integration) UFUNCTION, UPROPERTYBlueprintCallable, BlueprintReadOnly 등의 지정자를 붙이면 C++ 코드의 함수와 변수를 블루프린트 비주얼 스크립팅에서 노드로 사용하거나 직접 읽고 쓸 수 있습니다. RTTI로는 불가능합니다.
네트워크 리플리케이션(Network Replication) UPROPERTYReplicated 또는 ReplicatedUsing 지정자를 붙이면, 서버에서 변경된 변수의 값이 클라이언트로 자동으로 동기화됩니다. 리플렉션 데이터는 어떤 프로퍼티를 얼마나 자주, 누구에게 보낼지 결정하는 데 사용됩니다. RTTI로는 불가능합니다.

장점:

  • 강력한 툴링 지원: 에디터, 블루프린트 등과의 완벽한 통합을 가능하게 함.
  • 자동화: GC, 직렬화, 네트워크 등 복잡한 작업을 자동화하여 개발 생산성을 크게 향상시킴.
  • 유연성: 런타임에 객체 타입을 동적으로 다룰 수 있어 유연한 코드 작성이 가능함.

단점:

  • 빌드 시간 증가: UHT가 동작하는 과정 때문에 순수 C++ 프로젝트보다 빌드 시간이 김.
  • 약간의 런타임 오버헤드: 리플렉션 데이터를 저장하기 위한 메모리와 데이터 조회에 드는 CPU 비용이 존재함. (하지만 매우 최적화되어 있음)
  • 복잡성: 내부 동작을 이해하기 전까지는 매크로가 마법처럼 느껴질 수 있음.

6. 요약

 언리얼 엔진의 리플렉션 시스템은 C++에는 본래 없는 리플렉션 기능을 제공하여, 프로그램이 실행 중에 자신의 코드 구조를 분석하고 수정할 수 있게 해주는 강력한 시스템입니다. 흔히 '프로퍼티 시스템'이라고도 불립니다.

 이 시스템의 핵심은 '언리얼 헤더 툴(UHT)'이라는 코드 생성기에 있습니다. 개발자가 헤더 파일에 UCLASS, UPROPERTY, UFUNCTION 같은 매크로를 사용하면, 빌드 시 UHT가 이를 먼저 분석해서 리플렉션 정보를 담은 C++ 코드를 자동으로 생성해줍니다. 이 생성된 코드가 원본 코드와 함께 컴파일되면서, 런타임에 클래스의 변수나 함수 정보를 동적으로 조회하고 조작하는 것이 가능해집니다.

 이러한 리플렉션 시스템 덕분에 언리얼 엔진은 매우 강력한 기능들을 자동화할 수 있습니다. 예를 들어, 에디터의 디테일 패널에 변수를 자동으로 노출시키거나, 가비지 컬렉션이 객체 참조를 추적하고, 게임 상태를 저장하고 로드하는 직렬화, 그리고 서버와 클라이언트 간의 변수를 동기화하는 네트워크 리플리케이션 등이 모두 리플렉션 시스템을 기반으로 동작합니다.

'Unreal' 카테고리의 다른 글

Replication Condition  (0) 2025.10.24
언리얼 엔진 리플리케이션 Replication  (1) 2025.10.24
UE5 Cast의 내부 동작 원리  (0) 2025.10.22
CDO (Class Default Object)  (0) 2025.10.21
UObject  (0) 2025.10.21
iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차