언리얼 엔진은 거대한 단일 코드 덩어리가 아니라 모듈 단위로 나뉘어 빌드되고 로드되는 구조다.
모듈은 Build.cs를 통해 의존성을 정의하고, UBT는 이 정보를 기반으로 빌드 순서를 결정한다.
UHT는 UCLASS, UPROPERTY, UFUNCTION 같은 언리얼 매크로를 분석해 리플렉션 코드를 생성한다.


모듈 시스템

구분 역할
Runtime 게임 실행에 필요한 코드
Editor 에디터 전용 코드
Developer 개발 도구, 프로파일링, 테스트 코드
ThirdParty 외부 라이브러리
Programs UBT, UHT, UnrealPak 같은 도구 프로그램

모듈은 언리얼 코드의 기본 빌딩 블록이다.

개념 설명
Module 코드의 기본 단위. Build.cs 하나를 가진다
Plugin 하나 이상의 모듈을 담는 컨테이너
Project 게임 자체. .uproject로 정의된다

모듈은 PublicPrivate 폴더로 나누어 접근 범위를 관리한다.

폴더 의미
Public 외부 모듈에서 include 가능
Private 해당 모듈 내부에서만 사용

Runtime / Editor Module

모듈은 타입에 따라 패키징 포함 여부가 달라진다.

타입 용도 패키징 포함
Runtime 실제 게임 실행 코드 포함
Editor 에디터 확장 코드 제외
Developer 개발 빌드용 도구 코드 Shipping 제외

중요한 규칙은 다음과 같다.

관계 가능 여부
Editor → Runtime 참조 가능
Runtime → Editor 참조 불가능

Runtime 모듈에서 에디터 전용 코드를 써야 한다면 #if WITH_EDITOR로 감싸야 한다.

#if WITH_EDITOR
// 에디터 전용 코드
#endif

Loading Phase

LoadingPhase는 모듈이 언제 로드될지 결정한다.

Phase 용도
EarliestPossible 가장 빠른 시점
PostConfigInit 설정 시스템 초기화 직후
PreDefault 기본 로드보다 먼저
Default 일반적인 게임플레이 모듈
PostEngineInit 엔진 초기화 후
None 자동 로드하지 않음

일반적인 게임 모듈은 대부분 Default를 사용한다.


MODULE_API 매크로

다른 모듈에서 접근해야 하는 클래스에는 MODULE_API 매크로가 필요하다.

UCLASS()
class MYGAME_API AMyCharacter : public ACharacter
{
    GENERATED_BODY()
};
빌드 방식 역할
Modular dllexport / dllimport 역할
Monolithic 빈 매크로로 처리

MYGAME_API가 없으면 다른 모듈에서 클래스를 참조할 때 링크 에러가 발생할 수 있다.


Plugin 구조

플러그인은 .uplugin 파일로 정의되고, 내부에 여러 모듈을 포함할 수 있다.

{
  "Modules": [
    {
      "Name": "MyPlugin",
      "Type": "Runtime",
      "LoadingPhase": "Default"
    },
    {
      "Name": "MyPluginEditor",
      "Type": "Editor",
      "LoadingPhase": "Default"
    }
  ]
}

플러그인 구조의 핵심 규칙은 다음과 같다.

규칙 설명
모듈당 Build.cs 하나 모듈명.Build.cs 필요
최소 cpp 필요 모듈로 인식되려면 최소 하나의 cpp 필요
IMPLEMENT_MODULE 필요 모듈 등록 매크로 필요
Runtime / Editor 분리 게임 코드와 에디터 코드를 분리해야 함
IMPLEMENT_MODULE(FMyPluginModule, MyPlugin)

Build.cs

모듈의 빌드 설정을 정의하는 C# 파일

public class MyGame : ModuleRules
{
    public MyGame(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
        bEnforceIWYU = true;

        PublicDependencyModuleNames.AddRange(new string[]
        {
            "Core",
            "CoreUObject",
            "Engine"
        });

        PrivateDependencyModuleNames.AddRange(new string[]
        {
            "Slate",
            "SlateCore"
        });
    }
}

주요 설정

설정 의미
PCHUsage Precompiled Header 사용 방식
bEnforceIWYU 필요한 헤더만 include하도록 강제
PublicDependencyModuleNames Public 헤더에서 필요한 모듈
PrivateDependencyModuleNames cpp 내부에서만 필요한 모듈

Public Dependency와 Private Dependency

PublicDependencyModuleNamesPrivateDependencyModuleNames의 차이는 의존성 전파 여부다.

구분 의미 전파 여부
Public Dependency Public 헤더에서 사용하는 모듈 전파됨
Private Dependency cpp 내부에서만 사용하는 모듈 전파되지 않음

Public 헤더에서 UGameplayAbility를 사용한다면 GameplayAbilities는 Public Dependency에 있어야 한다.

// MyAbility.h
#include "Abilities/GameplayAbility.h"

UCLASS()
class UMyAbility : public UGameplayAbility
{
    GENERATED_BODY()
};
PublicDependencyModuleNames.Add("GameplayAbilities");

반대로 cpp에서만 UMG를 사용한다면 Private Dependency로 충분하다.

PrivateDependencyModuleNames.Add("UMG");

UBT 빌드 흐름

UBT는 빌드 시작 시 모듈 정보를 읽고 의존성 그래프를 만든다.

flowchart LR
    A[uproject / uplugin 스캔] --> B[Build.cs 파싱]
    B --> C[모듈 의존성 그래프 생성]
    C --> D[순환 의존성 검사]
    D --> E[빌드 순서 결정]
    E --> F[UHT 실행]
    F --> G[C++ 컴파일]
    G --> H[링킹]

UBT는 모듈 간 의존성을 그래프로 만들고, 위상 정렬을 통해 빌드 순서를 결정한다.

순서 모듈
1 Core
2 CoreUObject
3 Engine
4 GameplayAbilities
5 MyGame

순환 의존성이 있으면 빌드가 실패한다.

문제 해결
ModuleA → ModuleB → ModuleA 공통 모듈 분리
서로 직접 참조 인터페이스 모듈 도입
구조가 꼬임 의존성 방향 재설계

UHT와 리플렉션

언리얼 리플렉션 코드를 생성하는 도구
UHT가 처리하는 대상

매크로 의미
UCLASS UObject 기반 클래스
USTRUCT 리플렉션 가능한 구조체
UENUM 리플렉션 가능한 enum
UPROPERTY 리플렉션 프로퍼티
UFUNCTION 리플렉션 함수
UINTERFACE 언리얼 인터페이스

UHT가 처리하려면 헤더에 .generated.h가 있어야 하며, 이 include는 반드시 마지막에 위치해야 한다.

#pragma once

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

UCLASS()
class MYGAME_API AMyActor : public AActor
{
    GENERATED_BODY()
};

generated.h와 gen.cpp

UHT는 Intermediate 폴더에 리플렉션 코드를 생성한다.

파일 역할
.generated.h 클래스 선언 보조 코드 생성
.gen.cpp 리플렉션 정보의 실제 구현 생성

generated.h는 다음 기능을 제공한다.

기능 설명
StaticClass() 클래스의 UClass 반환
GetClass() 인스턴스의 클래스 정보 조회
네이티브 함수 등록 UFUNCTION을 리플렉션 시스템에 등록
직렬화 지원 저장, 로드, 복제 지원
메타데이터 연결 클래스, 프로퍼티, 함수 정보 등록

.gen.cpp는 실제로 UClass, FProperty, UFunction 정보를 등록한다.

이 덕분에 다음 기능이 가능해진다.

기능 설명
에디터 디테일 패널 UPROPERTY 정보를 읽어 UI 생성
블루프린트 노드 UFUNCTION 정보를 읽어 노드 생성
GC UPROPERTY로 표시된 UObject 참조 추적
네트워크 복제 복제 대상 프로퍼티 확인
동적 함수 호출 ProcessEvent()로 함수 호출

UCLASS가 인식되지 않습니다, generated.h를 찾을 수 없습니다 같은 에러는 보통 아래 원인 중 하나다.

원인 해결
Build.cs 의존성 누락 필요한 모듈 추가
.generated.h include 누락 헤더 마지막에 include
GENERATED_BODY() 누락 클래스 본문에 추가
include 순서 문제 .generated.h를 마지막에 배치
플러그인 비활성화 .uproject에서 플러그인 활성화

정적 리플렉션

빌드 시점에 리플렉션 코드를 생성하는 정적 리플렉션 방식
정적 리플렉션의 장점은 다음과 같다.

장점 설명
빠른 런타임 조회 메타데이터가 C++ 코드로 생성됨
컴파일 타임 검증 잘못된 리플렉션 사용은 빌드 에러로 잡힘
에디터 통합 디테일 패널, 블루프린트 노드 생성 가능
GC 통합 UObject 참조 추적 가능
네트워크 복제 통합 Replication 시스템과 연결 가능

GC와 UPROPERTY

언리얼 GC는 UPROPERTY로 표시된 UObject* 참조를 추적한다.

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

public:
    UPROPERTY()
    TObjectPtr<UMaterial> CachedMaterial;

    UMaterial* RawMaterial;
};
변수 GC 추적
CachedMaterial 추적됨
RawMaterial 추적 안 됨

따라서 UObject 멤버는 기본적으로 UPROPERTY()를 붙이는 것이 안전하다.

UPROPERTY()
TObjectPtr<UStaticMeshComponent> MeshComponent;

임시 값이지만 GC 추적은 필요하고 저장은 원하지 않는다면 Transient를 사용할 수 있다.

UPROPERTY(Transient)
TObjectPtr<UMaterial> TempMaterial;

UPROPERTY Specifier

UPROPERTY 지정자는 크게 에디터 노출, 블루프린트 노출, 복제, 직렬화, 메모리 관리로 나눌 수 있다.

분류 주요 지정자
에디터 노출 EditAnywhere, EditDefaultsOnly, VisibleAnywhere
블루프린트 노출 BlueprintReadOnly, BlueprintReadWrite
복제 Replicated, ReplicatedUsing
직렬화 Transient, SaveGame, SkipSerialization
메모리 Instanced, Export, NoClear
UPROPERTY(EditAnywhere, BlueprintReadWrite, Replicated)
float Health;

EditAnywhere, BlueprintReadWrite, Replicated 같은 정보는 런타임 플래그로 저장된다.

meta=(DisplayName, ClampMin, ToolTip) 같은 메타데이터는 에디터 전용이며, Shipping 빌드에서는 제거될 수 있다.

UPROPERTY(EditAnywhere, meta=(ClampMin="0", ClampMax="100"))
float Health;

런타임 메타데이터 접근

모든 UCLASS는 런타임에 UClass를 통해 클래스 정보를 조회할 수 있다.

UClass* MyClass = AMyCharacter::StaticClass();

FString ClassName = MyClass->GetName();
UClass* SuperClass = MyClass->GetSuperClass();

bool bIsCharacter = MyClass->IsChildOf(ACharacter::StaticClass());

프로퍼티도 순회할 수 있다.

for (TFieldIterator<FProperty> It(MyClass); It; ++It)
{
    FProperty* Prop = *It;
    UE_LOG(LogTemp, Log, TEXT("Property: %s"), *Prop->GetName());
}

함수는 UFunction으로 찾고, ProcessEvent()로 동적 호출할 수 있다.

UFunction* Func = MyClass->FindFunctionByName(TEXT("TakeDamage"));

if (Func)
{
    struct FParams
    {
        float Amount;
    };

    FParams Params{25.0f};
    MyCharacter->ProcessEvent(Func, &Params);
}

이 구조가 블루프린트 호출, 콘솔 명령어, RPC, 에디터 기능의 기반이 된다.


RepNotify

ReplicatedUsing은 특정 프로퍼티가 클라이언트에 복제되었을 때 콜백 함수를 호출한다.

UPROPERTY(ReplicatedUsing=OnRep_Health)
float Health;

UFUNCTION()
void OnRep_Health();

흐름은 다음과 같다.

flowchart LR
    A[서버에서 값 변경] --> B[NetDriver가 변경 감지]
    B --> C[프로퍼티 직렬화]
    C --> D[클라이언트 전송]
    D --> E[클라이언트에서 값 적용]
    E --> F[OnRep 함수 호출]

OnRep 함수의 시그니처는 정확해야 한다.

형태 가능 여부
void OnRep_Health() 가능
void OnRep_Health(float OldHealth) 가능
void OnRep_Health(int32 WrongType) 불가
void OnRep_Health() const 불가

블루프린트 노출 원리

블루프린트 노드는 UFUNCTION의 리플렉션 정보를 읽어서 생성된다.

UFUNCTION(BlueprintCallable, Category="Combat")
float ApplyDamage(float BaseDamage, AActor* DamageCauser);

블루프린트 시스템은 다음 정보를 읽는다.

정보 사용처
함수 이름 노드 이름
Category 노드 분류
파라미터 타입 입력 핀 생성
반환 타입 출력 핀 생성
Meta 태그 표시 이름, 툴팁, 검색 키워드

Meta 태그로 노드 표시 방식을 조정할 수 있다.

UFUNCTION(BlueprintCallable, Category="Combat",
    meta=(DisplayName="데미지 적용", Keywords="hurt attack damage"))
void ApplyDamage(float Amount);

런타임 로딩 흐름

언리얼 실행 시 모듈은 대략 다음 순서로 로드된다.

flowchart LR
    A[Core] --> B[CoreUObject]
    B --> C[Engine]
    C --> D[Plugin Modules]
    D --> E[Project Module]
    E --> F[Editor Modules]

모듈은 IModuleInterface를 구현하고, StartupModule()ShutdownModule()에서 초기화와 정리를 수행할 수 있다.

class FMyGameModule : public IModuleInterface
{
public:
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;
};

게임 모듈은 보통 다음 매크로로 등록한다.

IMPLEMENT_PRIMARY_GAME_MODULE(FMyGameModule, MyGame, "MyGame");

일반 모듈은 다음을 사용한다.

IMPLEMENT_MODULE(FMyPluginModule, MyPlugin)

StaticClass와 UClass 생성

StaticClass()는 해당 클래스의 UClass를 반환한다.

UClass* Class = AMyCharacter::StaticClass();

언리얼은 모든 UClass를 시작 시점에 전부 만드는 것이 아니라, 필요할 때 생성하는 Lazy Initialization 방식을 사용한다.

장점 설명
시작 시간 절약 사용하지 않는 클래스는 늦게 초기화
메모리 절약 필요한 클래스만 초기화

.gen.cppZ_Construct_UClass_XXX() 함수가 실제 UClass를 만든다.

이 과정에서 부모 클래스, 프로퍼티, 함수, 메타데이터, 인터페이스 정보가 연결된다.


CDO

CDO는 Class Default Object의 약자로, 각 UCLASS마다 하나씩 존재하는 기본 템플릿 객체다.

특징 설명
클래스당 하나 모든 UCLASS는 CDO를 가진다
기본값 저장 프로퍼티 기본값을 보관
복사 원본 새 인스턴스 생성 시 기준이 됨
Reset 기준 에디터의 Reset to Default 기준
BP 기본값 블루프린트 기본값 편집은 CDO 편집과 연결됨

CDO는 다음처럼 접근할 수 있다.

AMyCharacter* CDO = GetMutableDefault<AMyCharacter>();
const AMyCharacter* ConstCDO = GetDefault<AMyCharacter>();

생성자는 CDO를 초기화할 때 호출된다.
따라서 생성자에서는 GetWorld()GetOwner()를 사용하는 것이 안전하지 않다.

AMyCharacter::AMyCharacter()
{
    Health = 100.0f;

    MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
}

월드가 필요한 초기화는 BeginPlay()에서 처리하는 것이 맞다.


Blueprint 클래스 로딩

블루프린트 클래스의 실제 타입은 UBlueprintGeneratedClass다.

블루프린트 에셋과 실제 생성 클래스는 다르다.

구분 의미
UBlueprint 에디터에서 편집하는 에셋
UBlueprintGeneratedClass 실제 런타임에서 사용하는 클래스

블루프린트 클래스를 직접 로드할 때는 _C 접미사가 붙은 경로를 사용한다.

UClass* BPClass = LoadClass<AActor>(
    nullptr,
    TEXT("/Game/Blueprints/BP_MyCharacter.BP_MyCharacter_C")
);

_C가 붙은 경로는 GeneratedClass를 의미한다.


최종 요약

언리얼의 모듈, 빌드, 리플렉션 구조는 다음 흐름으로 연결된다.

flowchart LR
    A[Module] --> B[Build.cs]
    B --> C[UBT]
    C --> D[UHT]
    D --> E[generated.h / gen.cpp]
    E --> F[Reflection]
    F --> G[Editor / Blueprint / GC / Replication]

핵심은 다음과 같다.

개념 핵심
Module 언리얼 코드의 기본 단위
Build.cs 모듈의 빌드 설정과 의존성 정의
UBT 모듈 그래프를 만들고 빌드 순서 결정
UHT 언리얼 매크로를 파싱해 리플렉션 코드 생성
generated.h / gen.cpp UClass, FProperty, UFunction 등록 코드
Reflection 에디터, 블루프린트, GC, 네트워크 복제의 기반
CDO 클래스 기본값을 가진 템플릿 객체
BlueprintGeneratedClass 블루프린트의 실제 런타임 클래스

언리얼 C++은 일반 C++ 코드 위에 UBT, UHT, 리플렉션, CDO, 모듈 시스템이 얹혀 있는 구조다.

Build.cs 의존성, generated.h, UPROPERTY, UFUNCTION, MODULE_API, CDO 생성 시점까지 함께 이해해야 안정적으로 언리얼 프로젝트를 설계할 수 있다.

반응형

'Unreal Engine' 카테고리의 다른 글

Unreal Engine Tick 최적화  (0) 2026.07.06
UE 애니메이션 리타게팅  (0) 2026.07.03
UE MVVM Plugin  (0) 2026.07.01
Asset Manager + PrimaryAsset  (0) 2026.06.30
언리얼 레퍼런스  (0) 2026.06.29