리플렉션 코드 자동 생성
엔진이 런타임에 타입 정보를 알 수 있도록(에디터 노출, 직렬화, 가비지 컬렉션, 블루프린트 연동 등) 헤더의 매크로를 읽어 별도의 C++ 코드를 컴파일 전에 자동 생성합니다. 이 일을 하는 도구가 UHT입니다.
언리얼에서 C++ 코드를 작성하고 “빌드”를 누르면, 평범한 C++ 프로젝트보다 훨씬 많은 일이 자동으로 일어납니다. 이 과정의 핵심에는 두 가지 도구, UBT(UnrealBuildTool) 와 UHT(UnrealHeaderTool) 가 있습니다. 이 문서에서는 일반 C++ 빌드를 먼저 짚고, 언리얼이 그 위에 무엇을 더 얹는지, 그리고 전체 빌드 순서가 어떻게 되는지를 차례대로 살펴봅니다.
먼저 언리얼이 아닌 보통의 C++ 빌드를 한 문단으로 떠올려 봅시다. 일반적인 C++ 빌드는 크게 세 단계로
진행됩니다. 전처리(Preprocess) 단계에서 #include를 펼치고 매크로를 치환해 하나의 큰 번역
단위(translation unit)를 만들고, 컴파일(Compile) 단계에서 각 .cpp를 기계어 오브젝트
파일(.obj/.o)로 변환하며, 마지막 링크(Link) 단계에서 여러 오브젝트 파일과 라이브러리를
묶어 하나의 실행 파일이나 라이브러리를 만듭니다. 즉 “여러 소스 → 오브젝트 → 하나의 바이너리”가
표준 C++ 빌드의 골격입니다.
언리얼은 위 표준 빌드를 그대로 사용하면서, 그 위에 두 가지 층을 추가합니다.
리플렉션 코드 자동 생성
엔진이 런타임에 타입 정보를 알 수 있도록(에디터 노출, 직렬화, 가비지 컬렉션, 블루프린트 연동 등) 헤더의 매크로를 읽어 별도의 C++ 코드를 컴파일 전에 자동 생성합니다. 이 일을 하는 도구가 UHT입니다.
모듈 시스템
코드를 “모듈” 단위로 나누어 의존성과 빌드 설정을 관리하고, 어떤 모듈을 어떤 순서로 컴파일·링크할지 오케스트레이션합니다. 이 일을 하는 도구가 UBT입니다.
정리하면, 표준 C++ 빌드 위에 (리플렉션 코드 생성) 과 (모듈 단위 빌드 관리) 라는 두 층이 더해진 것이 언리얼 빌드 파이프라인입니다.
언리얼 코드는 파일이나 클래스 하나가 아니라 모듈 단위로 빌드되고 로드됩니다. 모듈은 함께 컴파일되어 하나의 바이너리(주로 DLL)로 묶이는 코드 묶음이며, 다른 모듈에 의존성을 선언해 기능을 빌려 씁니다. 엔진 자체도 수많은 모듈로 쪼개져 있고, 우리가 만드는 게임 코드도 하나 이상의 모듈로 구성됩니다.
이 모듈 구조를 결정하는 것은 몇 개의 설정 파일입니다.
.uproject
프로젝트의 진입점 파일. 어떤 모듈과 플러그인이 포함되는지, 엔진 버전은 무엇인지 등 프로젝트 최상위 정보를 담습니다.
(프로젝트명)Target.cs
빌드 타깃을 정의합니다. 같은 코드를 Editor용으로 빌드할지, 독립 실행 Game으로 빌드할지, Server로 빌드할지 등 “무엇을 위한 빌드인가”를 결정합니다.
(모듈명).Build.cs
개별 모듈의 의존성과 빌드 설정을 정의합니다. 이 모듈이 어떤 다른 모듈에 의존하는지, 어떤 옵션으로 컴파일되는지를 적습니다.
아래는 모듈의 의존성을 선언하는 Build.cs의 모습을 단순화한 예시입니다.
using UnrealBuildTool;
public class MyGame : ModuleRules{ public MyGame(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
// 이 모듈이 의존하는 다른 모듈들 PublicDependencyModuleNames.AddRange( new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); }}UBT는 C#으로 작성된 빌드 오케스트레이터입니다. 직접 코드를 컴파일하는 컴파일러가 아니라, “무엇을 어떤 설정으로 빌드할지” 계획을 세우고 실제 도구들을 지휘하는 감독 역할을 합니다.
UBT가 하는 일을 정리하면 다음과 같습니다.
Target.cs와 Build.cs를 읽어, 어떤 모듈과 어떤 소스 파일을 어떤 컴파일 옵션·의존성으로
빌드할지 결정합니다.UHT는 헤더 파일을 파싱해 리플렉션·직렬화용 C++ 코드를 자동 생성하는 도구입니다. 우리가 헤더에
적는 UCLASS, USTRUCT, UENUM, UPROPERTY, UFUNCTION 같은 매크로가 UHT의 입력입니다. UHT는
이 매크로들을 읽어, 엔진이 런타임에 타입을 이해하는 데 필요한 보조 코드를 만들어 냅니다.
UHT가 만들어 내는 대표 산출물은 두 가지입니다.
파일명.generated.h
각 헤더에 대응해 생성되는 헤더. 클래스 본문에 끼워 넣을 리플렉션 보조 선언이 들어 있으며,
GENERATED_BODY()가 이 내용을 펼쳐 줍니다.
파일명.gen.cpp
생성된 리플렉션 정보의 구현부. 타입 등록, 프로퍼티 메타데이터 등 런타임이 사용할 실제 데이터가 여기에 담깁니다.
중요한 점은 UHT가 실제 컴파일 “이전”에 실행된다는 것입니다. 컴파일러가 우리 코드를 읽기 전에
.generated.h와 .gen.cpp가 먼저 만들어져 있어야, 컴파일러가 원본 코드와 생성 코드를 함께 빌드할
수 있습니다. 참고로 UHT는 UE5에서 C++로 재작성되어, 과거보다 더 빠르고 엔진 빌드 과정에
긴밀하게 통합되었습니다.
클래스 본문 안에 적는 GENERATED_BODY() 매크로는, UHT가 생성한 코드를 그 자리에 끼워 넣는
“삽입 지점” 역할을 합니다. 그리고 그 생성 코드는 .generated.h에 정의되어 있습니다. 따라서 한 가지
규칙이 강제됩니다. .generated.h의 include는 해당 헤더에서 가장 마지막 include여야 합니다.
이유는 생성 코드가 현재 헤더의 타입을 기준으로 만들어지기 때문입니다. generated 헤더 뒤에 다른 include가 더 오면, 매크로 확장 순서가 어긋나 컴파일 오류가 납니다. 아래 형태를 항상 지키면 됩니다.
#pragma once
#include "CoreMinimal.h"#include "GameFramework/Actor.h"#include "MyActor.generated.h" // 항상 "마지막" include
UCLASS()class MYGAME_API AMyActor : public AActor{ GENERATED_BODY() // UHT 생성 코드가 여기에 삽입됨
public: AMyActor();};지금까지의 도구들이 실제 빌드에서 어떤 순서로 동작하는지 정리하면 다음과 같습니다.
Target.cs와 Build.cs를 읽어 어떤 타깃·모듈과 어떤 대상 파일을 빌드할지
결정합니다.UCLASS/USTRUCT/UPROPERTY 등 매크로를 분석해
.generated.h와 .gen.cpp 등 리플렉션 코드를 생성합니다..cpp와 UHT가 만든 생성 코드를 함께 컴파일해 오브젝트 파일을
만듭니다.위 파이프라인의 최종 산출물은 결국 모듈 바이너리(DLL/exe) 입니다. 코드를 수정한 뒤 빠르게 반영하는 기능들은 모두 이 산출물을 다루는 방식의 차이입니다.
핫 리로드(Hot Reload)
새 모듈 바이너리를 다시 빌드한 뒤, 실행 중인 에디터가 기존 모듈을 새 바이너리로 교체하는 방식입니다.
라이브 코딩(Live Coding)
실행 중인 프로세스의 코드를 다시 빌드한 변경분으로 패치해, 재시작 없이 반영하는 방식입니다.
즉 두 기능 모두 “UBT/UHT/컴파일러/링커가 만든 모듈 산출물을 어떻게 갱신하느냐”의 문제로 볼 수 있습니다. 핫 리로드와 라이브 코딩의 실제 사용법과 차이는 3-6에서 다루었으니, 그 동작의 바탕이 바로 이 빌드 파이프라인이라는 점을 연결해서 이해하면 됩니다.
UBT
UHT
Module
Build.cs
Target.cs
generated.h
#include 여야 함.GENERATED_BODY