리플렉션 / UBT / UHT / CDO 정리

2026. 4. 15. 16:03Unreal

리플렉션

리플렉션은 영어로 "반사, 투영" 이라는 뜻이다.

언리얼 엔진 내에서는 프로그램이 실행 중에 자기 자신의 구조(클래스, 변수, 함수 등)을 스스로 인지하고 조작하는 능력을 말한다.

 

이 시스템이 없다면 에디터 내에서 클래스, 변수, 함수 등을 인지할 수 없다.

 

리플렉션의 역할

런타임 타입 정보 확인

게임 내 특정 객체가 어떤 클래스인지, 어떤 클래스를 상속 받았는지를 확인한다.

 

변수 및 함수 조작 (에디터 연동)

UPROPERTY(), UFUNCTION(), UCLASS() 등 리플렉션 함수를 사용하면 에디터 내에서 보이게 되어 코드를 수정하지 않고 간단하게 수치만 변경해서 테스트 해볼 수 있다.

 

가비지 컬렉션

UObject를 가리키고 있는 변수를 리플렉션 시스템을 통해서 엔진에게 전달해주기 때문에, 아무도 참조하지 않는 객체를 찾아내 안전하게 메모리에서 지울 수 있다.


UBT (언리얼 빌더 툴)

UBT는 프로젝트 전체의 빌드 환경을 세팅하는 관리자이다. 본격적인 컴파일이 시작되기 전에 가장 먼저 실행되며 다음과 같은 일을 한다.

  • 솔루션 탐색기의 .Target.cs / Build.cs 파일을 읽어 어떤 모듈과 플러그인이 필요한지 파악한다.
  • Windows인지 Mac인지 실행 환경에 맞게 빌드 설정 변환
  • 헤더 파일이 변경되었는지 감지하면 UHT를 호출한다.

UBT는 헤더를 스캔해서 하나 이상의 리플렉션 타입이 포함된 헤더를 가진 모듈을 기록해두고, 그 헤더가 마지막 컴파일 이후 변경된 경우 UHT를 호출해 리플렉션 데이터를 수집하고 업데이트 한다.

 

헤더를 고치면 빌드가 오래걸리는 이유는

더보기

cpp 파일만 수정 > UBT가 헤더 변경 없음을 확인 > 라이브 코딩으로 빠르게 종료

 

헤더 파일 수정 > UBT가 변경 감지 > UHT 재실행 > 전체 리빌드 시작

헤더를 고치면 UHT가 다시 전체 분석을 해야 하기 때문에 빌드 시간이 크게 늘어난다.

 

단순한 값 변경은 그래서 .cpp 안에서 하는 것이 효율이 좋다.


UHT (언리얼 헤더 툴)

UHT는 C++ 컴파일러가 일을 시작하기 전에 먼저 실행되는 코드 분석 및 생성 도구이다.

언리얼 전용 매크로들을 읽고, 엔진이 각 클래스를 인식할 수 있도록 필요한 코드를 자동으로 만들어 준다.

 

코드 컴파일은 두 단계로 이루어 지는데 UBT가 UHT를 호출하면 UHT는 C++ 헤더에서 언리얼 관련 클래스 메타데이터를 파싱하고, UObject 관련 기능을 구현하기 위한 커스텀 코드를 작성한다. 그 다음 UBT가 C++ 컴파일러를 호출해 결과물을 컴파일 한다.

 

UPROPERTY(EditAnywhere, Category = "Stats")

이 코드의 괄호 안의 옵션들이 메타데이터이다. UHT는 이것을 수집해서 엔진 에디터에 등록해주는데, 이게 리플렉션이다.

 

UHT는 분석을 마치면 다음 두 가지 파일을 생성한다.

더보기

MyActor.generated.h              클래스의 정체성과 관계를 기록한 코드

MyActor.gen.cpp                    리플렉션, 직렬화, 가비지 컬렉션 지원 코드

UHT는 UCLASS() 및 GENERATED_BODY() 매크로를 위해 Example.generated.h와 Example.gen.cpp라는 두 파일을 생성한다. 이것이 언리얼이 리플렉션과 가비지 컬렉션을 언어적으로 지원하지 않는 C++에서 구현하는 방식이다.

 

직렬화, 네트워크 리플리케이션, 에디터 통합도 생성된 코드로 구현한다.


CDO (클래스 디폴트 오브젝트)

CDO는 언리얼 엔진에서 각 클래스의 원본 역할을 한다.

붕어빵 틀에 비유하면, CDO는 틀 자체이고 게임 중에 스폰되는 액터들은 그 틀로 찍어낸 붕어빵이다.

각 UObject 클래스마다 메모리에 딱 하나씩 존재하며, 엔진 초기화 단계에서 생성자를 단 한 번 호출해 완성된다.

 

생성 시점과 초기화

CDO는 에디터가 켜지거나 게임이 시작되기 훨씬 전인 모듈 로딩 단계에서 만들어진다.

이때 C++ 생성자가 호출되어 CDO를 완성하고, 이후 스폰 시에는 생성자를 다시 실행하지 않는다.

AMyActor::AMyActor()
{
    // 이 코드는 CDO를 만들 때 딱 한 번 실행된다
    // 게임 중 스폰할 때마다 실행되는 게 아님!
    MoveSpeed = 600.0f;
    MaxHealth = 100;
}

그래서 초기화는 반드시 생성자 안에서 해야한다.

헤더에서 값을 직접 바꿔도 작동은 하지만, 헤더 변경이 감지되면 UHT가 재실행되어 전체 리빌드로 이어지기 때문에 cpp에서 초기화하는 관습을 들이는 것이 좋다.

 

복제 방식과 최적화

SpawnActor로 액터를 스폰하면, 엔진은 처음부터 다시 설계하지 않는다. 이미 메모리에 올라온 CDO의 상태를 그대로 복사해서 새 인스턴스를 만든다.

  • CDO가 없을 때 : 스폰마다 생성자 코드를 처음부터 끝까지 다시 실행
  • CDO가 있을 때 : 이미 만들어진 덩어리째 복사

최적화 측면에서도 이점이 크다. 수천개의 총알이 있어도 공통 기본 데이터는 CDO 하나에만 저장되고, 각 인스턴스는 기본값과 달라진 부분만 들고 있으면 된다.

저장하거나 네트워크로 전송할 때도 CDO와의 차이점만 직렬화하므로 파일 용량과 패킷 효율이 모두 개선된다.


정리

리플렉션

  • 프로그램이 실행 중에 자기 자신의 구조(클래스, 변수, 함수 등)를 스스로 인지하고 조작하는 능력
  • 이 시스템이 없으면 에디터에서 클래스, 변수, 함수를 인지할 수 없음
  • 런타임 타입 확인 : 특정 개체가 어떤 클래스인지, 어떤 클래스를 상속받았는지 확인 가능
  • 에디터 연동
  • 가비지 컬렉션 : 리플렉션 시스템을 통해 참조 관계를 파악하고 아무도 참조하지 않는 객체를 안전하게 메모리에서 제거

 

UBT (Unreal Build Tool)

  • 빌드 시작 시 가장 먼저 실행
  • .Target.cs / Build.cs 파일을 읽어서 모듈 / 플러그인 파악
  • 플랫폼환경(윈도우 / 맥)에 맞는 빌드 설정 세팅
  • 헤더 변경 감지 시 UHT 호출 -> 전체 리빌드 트리거

 

UHT (Unreal Header Tool)

  • C++ 컴파일러 실행 전에 먼저 동작
  • UCLASS, UPROPERTY, UFUNCTION 등 언리얼 매크로 파싱
  • 메타데이터 수집 -> 리플렉션 데이터 생성
  • .generated.h / .gen.cpp 파일 자동 생성
  • GENERATED_BODY() 를 통해 매크로 클래스 안에 생성된 코드 삽입
  • 헤더만 바꿔도 UHT가 재실행되므로 빌드 시간이 길어짐

 

CDO (Class Default Object)

  • 각 Object 클래스마다 메모리에 딱 하나 존재하는 원본 객체
  • 생성 시점 : 엔진 초기화 단계, 생성자를 단 한 번 호출해서 완성
  • 역할 : 스폰 시 CDO를 복사해 인스턴스 생성 -> 빠른 초기화
  • 초기화는 반드시 생성자 안에서 해야 CDO에 제대로 반영됨
  • 델타 직렬화 : CDO와의 차이점만 저장 / 전송해서 메모리-네트워크 최적화