Unreal Header Tool(UHT)는 실제로 뭘 할까?

//
//

🚀 들어가며

이전 글에서는 Unreal Engine이 왜 C++ 기본 RTTI 대신 자체 Reflection 시스템을 사용하는지 살펴봤습니다.

그리고 그 과정에서 자연스럽게 등장했던 것이 바로

  • UCLASS()
  • UPROPERTY()
  • UFUNCTION()
  • GENERATED_BODY()

같은 매크로들입니다.

언리얼을 공부하다보면 이런 생각이 들 수 있습니다.

  • GENERATED_BODY는 도대체 뭘 하는 걸까?
  • 왜 .generated.h 파일이 생길까?
  • UCLASS는 그냥 매크로 아닌가?
  • UPROPERTY는 어떻게 GC와 연결될까?

사실 여기에는 매우 중요한 시스템이 숨어 있습니다.

바로 Unreal Header Tool(UHT)입니다.

UHT는 Unreal Engine Reflection 시스템의 핵심입니다.

이번 글에서는

  • UHT가 왜 필요한지
  • 실제로 어떤 일을 하는지
  • .generated.h 파일은 왜 생기는지
  • UCLASS / UPROPERTY / UFUNCTION이 어떻게 동작하는지

엔진 내부 구조 관점에서 정리해보겠습니다.


🧠 먼저 중요한 사실

언리얼의 Reflection 시스템은👉 C++ 컴파일러만으로는 구현하기 어렵습니다.

왜냐하면 C++는 기본적으로

  • 멤버 변수 목록
  • 함수 목록
  • 메타데이터
  • 에디터 노출 정보

같은 정보를 런타임에 제공하지 않기 때문입니다.

예를 들어

class Player
{
public:
    int HP;
    float Speed;
};

일반 C++에서는 런타임에

HP라는 멤버가 존재하는가?
타입은 int인가?
에디터에 표시 가능한가?

같은 정보를 쉽게 알 수 없습니다.

하지만 언리얼 엔진은 이런 정보가 필요합니다.

왜냐하면 언리얼은

  • 에디터
  • 블루프린트
  • GC
  • 직렬화
  • 네트워크 복제

같은 시스템이 모두 연결되어 있기 때문입니다.

그래서 언리얼은 C++ 컴파일 이전에 헤더를 별도로 분석하는 시스템을 만들었습니다.

그게 바로 UHT(Unreal Header Tool)니다.


//

🎯 UHT는 언제 실행될까?

지금까지 설명한 Unreal Header Tool(UHT)의 전체 흐름을 그림으로 정리해보면 다음과 같습니다.

핵심은 UHT는 C++ 컴파일 이전에 헤더를 분석하고, Reflection 시스템에 필요한 메타데이터와 generated.h 코드를 생성한다는 점입니다.

Unreal Header Tool(UHT)가 UCLASS, UPROPERTY, UFUNCTION을 분석해 Reflection 메타데이터와 generated.h 파일을 생성하는 과정을 설명하는 인포그래픽
Unreal Header Tool(UHT)는 헤더 파일을 분석해 Reflection 메타데이터와 generated.h 코드를 생성하며, 이를 통해 블루프린트, GC, 에디터, 직렬화 시스템과 연결됩니다.

위 구조처럼 Unreal Engine의 Reflection 시스템은 단순 매크로 확장 수준이 아니라, UHT가 생성한 메타데이터를 기반으로 동작합니다.

특히

  • UCLASS / UPROPERTY / UFUNCTION 분석
  • .generated.h 생성
  • GENERATED_BODY 코드 삽입
  • GC / 블루프린트 / 직렬화 연결

과정이 서로 연결되면서 언리얼 엔진의 메타 시스템 전체를 구성하게 됩니다.

 

GENERATED_BODY()를 컴파일러가 처리한다고 생각할 수 있습니다.

하지만 실제 흐름은 조금 다릅니다.

언리얼 빌드 흐름은 대략 이렇습니다.

UHT 실행
↓
.generated.h 생성
↓
C++ 컴파일
↓
엔진 Reflection 시스템 연결

즉, 👉 UHT는 C++ 컴파일 이전 단계에서 실행됩니다.

여기서 중요한 포인트는 “UHT는 “C++ 전체를 컴파일”하는 것이 아니라 언리얼 매크로를 분석하는 전처리 도구에 가깝다”는 점입니다.


🧩 UHT는 실제로 무엇을 분석할까?

예를 들어, 다음과 같은 코드를 생각해봅시다.

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public:

    UPROPERTY(EditAnywhere)
    int HP;

    UFUNCTION(BlueprintCallable)
    void Attack();
};

UHT는 이 헤더를 읽고

  • UCLASS 발견
  • UPROPERTY 발견
  • UFUNCTION 발견

같은 정보를 수집합니다.

그리고 내부적으로

  • 이 클래스는 Reflection 대상이다
  • HP는 int 타입 프로퍼티다
  • Attack은 BlueprintCallable 함수다

같은 메타데이터를 생성합니다.

즉, UHT는 언리얼 전용 메타데이터 생성기라고 볼 수 있습니다.


🎮 GENERATED_BODY()는 실제로 뭘 할까?

GENERATED_BODY()를 단순 매크로처럼 생각할 수 있습니다.

하지만 실제로는 UHT가 생성한 코드가 삽입되는 위치입니다.

GENERATED_BODY()

는 내부적으로

  • StaticClass()
  • Reflection 등록 코드
  • Serializer 코드
  • GC 관련 코드
  • RPC 관련 코드

등이 들어갈 수 있는 자리입니다.


🧠 generated.h는 왜 생길까?

언리얼 프로젝트를 보면 항상 이런 코드가 있습니다.

#include "MyActor.generated.h"

처음 보면 굉장히 이상합니다.

  • generated.h는 누가 만드는 걸까?

정답은

👉 UHT가 생성합니다.

예를 들어

MyActor.h

를 분석하면

MyActor.generated.h

를 자동 생성합니다.

그리고 그 안에는

  • Reflection 등록 코드
  • StaticClass 관련 코드
  • 프로퍼티 메타데이터
  • 함수 메타데이터
  • 직렬화 코드

등이 들어갈 수 있습니다.

.generated.h는 UHT가 생성한 Reflection 연결 코드입니다.


⚠️ 왜 generated.h는 마지막 include여야 할까?

언리얼에서 자주 보는 오류 중 하나입니다.

#include found after .generated.h file

왜 이런 제약이 있을까요?

이유는 generated.h 안에

  • 매크로 확장 코드
  • 타입 선언 보조 코드
  • Reflection 연결 코드

등이 들어가기 때문입니다.

즉, include 순서가 꼬이면 Reflection 코드 구조가 깨질 수 있습니다.

그래서 아래 코드처럼 generated.h를 마지막에 include해야 합니다.

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

🎯 UPROPERTY는 실제로 어떻게 사용될까?

예를 들어, 아래와 같이 변수에 UPROPERTY() 매크로를 추가하면,

UPROPERTY(EditAnywhere, BlueprintReadWrite)
float MoveSpeed;

UHT는 이를 분석해서

  • 변수 이름
  • 타입
  • Blueprint 노출 여부
  • 에디터 수정 가능 여부

같은 메타데이터를 생성합니다.

이 정보는 이후

  • 에디터
  • 블루프린트
  • GC
  • 직렬화

시스템에서 사용됩니다.

즉,

UPROPERTY
↓
UHT 분석
↓
Reflection 메타데이터 생성
↓
엔진 시스템 사용

흐름입니다.


🎯 UFUNCTION은 실제로 어떻게 연결될까?

예를 들어, 함수 선언 위에 UFUNCTION() 매크로를 추가하면,

UFUNCTION(BlueprintCallable)
void Attack();

이 코드 역시 UHT가 분석합니다.

그리고

  • 함수 이름
  • 인자 정보
  • 반환 타입
  • BlueprintCallable 여부

등을 Reflection 시스템에 등록합니다.

그래야 블루프린트에서 “Attack 노드 생성” 같은 것이 가능합니다.

즉 블루프린트 함수 노드는 UHT 메타데이터 기반으로 생성됩니다.


🧩 GC와도 연결된다

이전 글에서 설명했던 GC 구조도 여기와 연결됩니다.

예를 들어,

UPROPERTY()
UObject* Target;

UHT는 이 정보를 Reflection 시스템에 등록합니다.

그리고 GC는

  • 이 클래스에 UObject 포인터가 존재하는지
  • 어떤 프로퍼티를 추적해야 하는지

알 수 있게 됩니다.

UPROPERTY
↓
UHT 분석
↓
Reflection 등록
↓
GC 추적 가능

구조입니다.

그래서 UHT는 GC 시스템과도 직접 연결됩니다.


🧠 Unreal Build Tool(UBT)과는 다르다

여기서 자주 헷갈리는 것이 있습니다.

  • UHT(Unreal Header Tool)
  • UBT(Unreal Build Tool)

둘은 다릅니다.


UHT

역할

  • 헤더 분석
  • Reflection 코드 생성
  • generated.h 생성

UBT

역할

  • 프로젝트 빌드
  • 모듈 관리
  • 컴파일 설정
  • 플랫폼 빌드

즉,

  • UHT = Reflection 코드 생성기
  • UBT = 빌드 시스템

으로 이해하면 됩니다.


🎮 왜 이런 구조를 만들었을까?

사실 핵심은 이것입니다.

언리얼 엔진은 단순 C++ 라이브러리가 아닙니다.

  • 에디터
  • 스크립트 시스템
  • GC
  • 직렬화
  • 네트워크 복제

를 모두 포함한 게임 제작 플랫폼입니다.

그런데 C++ 기본 기능만으로는 엔진이 객체를 충분히 이해하기 어렵습니다.

그래서 언리얼은

  • UCLASS
  • UPROPERTY
  • UFUNCTION

같은 메타 시스템을 만들었고, 그 메타 시스템을 실제 코드로 연결하는 도구가 UHT입니다.


🔥 중요한 포인트

여기서 중요한 점은 UHT는 단순 코드 생성기가 아닙니다.

실제로는 언리얼 Reflection 시스템의 시작점입니다.

왜냐하면

  • 에디터 노출
  • 블루프린트 연결
  • GC 추적
  • 직렬화
  • RPC

전부 UHT가 생성한 메타데이터를 기반으로 동작하기 때문입니다.

즉,

UHT
↓
Reflection 생성
↓
엔진 전체 시스템 연결

구조입니다.


🎯 핵심 정리

  • UHT는 Unreal Header Tool의 약자입니다.
  • UHT는 C++ 컴파일 이전에 헤더를 분석합니다.
  • UCLASS, UPROPERTY, UFUNCTION 등을 분석해 Reflection 메타데이터를 생성합니다.
  • .generated.h 파일은 UHT가 자동 생성합니다.
  • GENERATED_BODY는 UHT 생성 코드가 삽입되는 위치입니다.
  • GC, 블루프린트, 에디터, 직렬화 시스템은 모두 UHT 메타데이터와 연결됩니다.

🧩 마무리

언리얼을 공부하다 보면

  • 왜 generated.h가 생기지?
  • 왜 GENERATED_BODY가 필요하지?
  • 왜 UPROPERTY를 붙여야 하지?

같은 부분이 굉장히 낯설게 느껴질 수 있습니다.

하지만 내부 구조를 이해해보면
언리얼은 C++ 위에 자체 Reflection 기반 객체 시스템을 구축한 엔진이라는 점이 보이기 시작합니다.

그리고 그 중심에는 UHT(Unreal Header Tool)가 있습니다.

즉, UHT는 단순 빌드 도구가 아니라, 언리얼 엔진 전체 메타 시스템을 연결하는 핵심 구성 요소라고 볼 수 있습니다.

👉 게임 엔진 구조를 더 깊이 이해하고 싶다면

아래 강의를 통해 직접 구현해보는 것을 추천드립니다.

C++로 만드는 게임 엔진 프레임워크 강의

👉 C++로 만드는 게임 엔진 프레임워크 강의 바로가기

//
   

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다