🚀 들어가며
이전 글에서는 UClass가 실제로 어떤 정보를 가지고 있는지 살펴봤습니다.
정리하면 UClass는 단순한 클래스 이름 저장 구조가 아니라, 언리얼 엔진의 Reflection 시스템을 구성하는 핵심 메타데이터 객체입니다.
그리고 이 Reflection 시스템은 다음 흐름을 통해 만들어집니다.
UCLASS / UPROPERTY / UFUNCTION 작성 ↓ UHT(Unreal Header Tool)가 헤더 분석 ↓ .generated.h 파일 생성 ↓ GENERATED_BODY 위치에 생성 코드 연결 ↓ UClass / Reflection 시스템 등록
여기서 자연스럽게 이런 질문이 생깁니다.
- GENERATED_BODY는 실제로 어떤 코드를 생성할까?
언리얼을 처음 공부하면 GENERATED_BODY()는 거의 마법처럼 느껴집니다.
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
};
분명히 우리가 작성한 코드는 몇 줄 되지 않습니다.
그런데 이 클래스는
StaticClass()를 사용할 수 있고GetClass()로 UClass 정보를 얻을 수 있고Cast<T>()가 동작하고- 에디터에 프로퍼티가 표시되고
- 블루프린트와 연결되고
- GC가 UObject 참조를 추적할 수 있습니다
이런 일이 가능한 이유는, 우리가 직접 작성하지 않은 코드가 UHT에 의해 생성되기 때문입니다.
이번 글에서는 GENERATED_BODY()가 실제로 어떤 역할을 하는지, 그리고 어떤 종류의 코드가 생성되는지 개념 중심으로 정리해보겠습니다.
🧠 먼저 중요한 전제
GENERATED_BODY() 자체가 모든 코드를 직접 생성하는 것은 아닙니다.
정확히 말하면
- GENERATED_BODY()는 UHT가 생성한 코드를 클래스 내부에 끼워 넣는 위치입니다.
즉, 전체 흐름은 다음과 같습니다.
개발자가 헤더 작성 ↓ UHT가 헤더 분석 ↓ .generated.h 파일 생성 ↓ GENERATED_BODY 매크로가 generated.h 안의 코드와 연결 ↓ 컴파일 시 생성 코드 포함
그래서 GENERATED_BODY()는 단순한 장식이 아닙니다.
👉 언리얼 Reflection 시스템에 클래스가 참여하기 위한 코드 삽입 지점이라고 볼 수 있습니다.
🎯 예제 클래스
다음 클래스를 기준으로 생각해보겠습니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
AMyActor();
UPROPERTY(EditAnywhere)
int HP;
UFUNCTION(BlueprintCallable)
void Attack();
};
우리가 직접 작성한 코드는 단순합니다.
하지만 UHT는 이 헤더를 분석하면서 다음 정보를 수집합니다.
AMyActor는 UCLASS 대상이다 부모 클래스는 AActor이다 HP는 UPROPERTY이다 Attack은 UFUNCTION이다 GENERATED_BODY 위치에 생성 코드를 삽입해야 한다
이 분석 결과를 바탕으로 MyActor.generated.h와 관련 생성 코드가 만들어집니다.
🧩 generated.h 파일은 왜 필요한가?
언리얼 헤더에는 보통 다음과 같은 include가 있습니다.
#include "MyActor.generated.h"
이 파일은 개발자가 직접 작성하는 파일이 아닙니다.
👉 UHT가 자동 생성하는 파일입니다.
이 안에는 GENERATED_BODY()와 연결되는 여러 매크로와 선언 코드가 들어갑니다.
즉,
MyActor.h → 개발자가 작성한 헤더 MyActor.generated.h → UHT가 생성한 보조 코드
라고 볼 수 있습니다.
⚙️ GENERATED_BODY가 연결하는 대표적인 코드
지금까지 설명한 GENERATED_BODY와 UHT의 전체 흐름을 그림으로 정리해보면 다음과 같습니다.
핵심은 GENERATED_BODY는 단순 매크로가 아니라,
UHT가 생성한 Reflection 코드를 클래스 내부에 연결하는 핵심 지점이라는 점입니다.

실제 생성 코드는 언리얼 버전, 빌드 설정, 클래스 종류에 따라 달라질 수 있습니다.
그래서 여기서는 실제 엔진 코드를 그대로 복사하기보다는 개념적으로 어떤 종류의 코드가 생성되는지 정리하겠습니다.
GENERATED_BODY()를 통해 연결되는 대표적인 코드는 다음과 같습니다.
- StaticClass 관련 코드
- Reflection 등록 코드
- 생성자 보조 코드
- 타입 정보 연결 코드
- 직렬화 관련 코드
- RPC 관련 코드
- 접근 제어 관련 코드
- 복사/이동 제한 관련 코드
하나씩 살펴보겠습니다.
🔥 1. StaticClass() 관련 코드
언리얼에서는 다음 코드가 가능합니다.
UClass* ClassInfo = AMyActor::StaticClass();
일반 C++ 클래스에는 이런 함수가 자동으로 존재하지 않습니다.
즉 StaticClass()는 C++ 기본 기능이 아니라,
언리얼 Reflection 시스템이 만들어주는 기능입니다.
개념적으로는 이런 코드가 연결된다고 볼 수 있습니다.
static UClass* StaticClass();
그리고 이 함수는 해당 클래스의 UClass 정보를 반환합니다.
즉,
AMyActor::StaticClass() ↓ AMyActor에 대한 UClass 메타데이터 반환
흐름입니다.
이 기능이 있어야
Object->IsA(AMyActor::StaticClass())
같은 타입 검사도 가능합니다.
🧠 2. 클래스 등록 코드
UClass가 존재하려면, 엔진이 이 클래스를 Reflection 시스템에 등록해야 합니다.
즉, 언리얼은 런타임에
- AMyActor라는 클래스가 존재한다
- 부모 클래스는 AActor이다
- 프로퍼티는 HP이다
- 함수는 Attack이다
를 알고 있어야 합니다.
이를 위해 UHT는 클래스 등록에 필요한 코드를 생성합니다.
개념적으로는 이런 흐름입니다.
클래스 이름 등록 ↓ 부모 클래스 정보 등록 ↓ 프로퍼티 정보 등록 ↓ 함수 정보 등록 ↓ UClass 생성 및 연결
이 과정 덕분에 언리얼은 런타임에 클래스 정보를 조회할 수 있습니다.
🎯 3. Property 메타데이터 연결
예제 코드에 이런 멤버가 있었습니다.
UPROPERTY(EditAnywhere) int HP;
C++ 컴파일러 입장에서는 그냥 int HP입니다.
하지만 언리얼 입장에서는 훨씬 많은 의미가 있습니다.
이름: HP 타입: int 에디터 수정 가능: EditAnywhere Reflection 대상: true
UHT는 이 정보를 수집하고, 해당 프로퍼티를 UClass에 연결할 수 있는 코드를 생성합니다.
개념적으로는 다음과 비슷합니다.
UClass(AMyActor)
└── Property List
└── HP
├── Type: int
└── Metadata: EditAnywhere
이 정보가 있기 때문에 언리얼 에디터는 Details 패널에 HP를 표시할 수 있습니다.
또한 직렬화, 블루프린트, GC 등의 시스템도 이 Reflection 정보를 사용할 수 있습니다.
🎯 4. Function 메타데이터 연결
이번에는 함수입니다.
UFUNCTION(BlueprintCallable) void Attack();
일반 C++에서는 런타임에 함수 이름과 인자 정보를 조회하기 어렵습니다.
하지만 언리얼은 이 정보를 Reflection 시스템에 등록합니다.
개념적으로는 다음과 같습니다.
UClass(AMyActor)
└── Function List
└── Attack
├── Return Type: void
├── Parameters: 없음
└── Metadata: BlueprintCallable
이 정보가 있어야 블루프린트에서 Attack 함수를 노드로 호출할 수 있습니다.
즉,
UFUNCTION 작성 ↓ UHT 분석 ↓ Function 메타데이터 생성 ↓ Blueprint 시스템에서 사용
흐름입니다.
🧩 5. 생성자 보조 코드
언리얼 클래스는 일반 C++ 클래스와 다릅니다.
UObject 계열 클래스는 엔진 객체 시스템을 통해 생성되고 관리됩니다.
따라서 생성 과정에서도 Reflection 정보가 필요합니다.
UHT가 생성하는 코드에는 클래스 생성과 관련된 보조 코드도 포함될 수 있습니다.
개념적으로는
- 객체 생성 함수 연결
- Class Default Object(CDO) 생성 지원
- 생성자 호출 정보 연결
같은 역할을 합니다.
즉 언리얼 객체 생성은 단순히
new AMyActor()
처럼 처리되는 것이 아닙니다.
엔진은 UClass 정보를 기반으로 객체를 생성하고,
기본값과 Reflection 정보를 함께 연결합니다.
🧠 6. CDO와도 연결된다
이전 글에서 CDO(Class Default Object)를 살펴봤습니다.
CDO는 클래스의 기본값을 담고 있는 기본 객체입니다.
UClass └── CDO(Class Default Object)
GENERATED_BODY()와 UHT 생성 코드는
이 UClass / CDO 구조와도 연결됩니다.
예를 들어 AMyActor의 기본 HP 값은
CDO에 저장될 수 있습니다.
그리고 새 객체가 생성될 때 이 CDO를 기준으로 초기화됩니다.
즉,
UHT 생성 코드 ↓ UClass 등록 ↓ CDO 생성 및 연결 ↓ 객체 생성 시 기본값 기준으로 사용
흐름입니다.
⚠️ 7. 복사/이동 제한 코드
언리얼 UObject 계열 객체는 일반 C++ 객체처럼 복사해서 사용하는 대상이 아닙니다.
따라서 생성 코드에는 복사 생성자나 대입 연산을 제한하는 코드가 포함될 수 있습니다.
개념적으로는 이런 방향입니다.
AMyActor(const AMyActor&) = delete; AMyActor& operator=(const AMyActor&) = delete;
이유는 간단합니다.
UObject는
- 엔진 객체 시스템에 등록되고
- UClass와 연결되며
- GC 관리 대상이 될 수 있고
- Outer, Name, Flags 같은 엔진 내부 정보를 가집니다
따라서 단순 값 복사로 다루기 어렵습니다.
🧩 8. RPC / 네트워크 관련 코드
언리얼에서는 함수에 네트워크 관련 매크로를 붙일 수 있습니다.
예를 들어
UFUNCTION(Server, Reliable) void ServerAttack();
이 경우 UHT는 네트워크 호출과 관련된 보조 코드를 생성합니다.
개념적으로는:
- RPC 함수 등록
- 네트워크 호출용 wrapper 생성
- 검증 및 실행 흐름 연결
같은 역할이 필요합니다.
즉 GENERATED_BODY()는 단순히 Reflection만을 위한 것이 아니라, 언리얼의 여러 엔진 시스템과 연결되는 코드의 입구라고 볼 수 있습니다.
🎮 실제 구조를 단순화하면
전체 구조를 단순화하면 다음과 같습니다.
개발자 코드
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
UPROPERTY()
int HP;
UFUNCTION()
void Attack();
};
UHT는 이를 분석합니다.
UCLASS 발견 UPROPERTY 발견 UFUNCTION 발견 GENERATED_BODY 위치 확인
그리고 개념적으로 이런 코드를 만들어냅니다.
AMyActor의 StaticClass 선언/정의 AMyActor의 UClass 등록 코드 HP 프로퍼티 메타데이터 Attack 함수 메타데이터 CDO 생성 보조 정보 직렬화 / 네트워크 / GC 연결 정보
즉, 우리가 작성한 클래스는 단순 C++ 클래스가 아니라,
UHT를 거치면서 언리얼 엔진이 이해할 수 있는 클래스가 됩니다.
🔥 GENERATED_BODY가 없으면 어떻게 될까?
만약 GENERATED_BODY()가 없다면 어떻게 될까요?
UCLASS()
class AMyActor : public AActor
{
public:
UPROPERTY()
int HP;
};
이 경우 언리얼 Reflection 시스템에 필요한 코드가 제대로 삽입되지 않습니다.
즉,
- StaticClass 관련 코드가 부족하고
- Reflection 등록 흐름이 깨질 수 있고
- UHT 생성 코드와 클래스가 연결되지 않을 수 있습니다
그래서 UCLASS 대상 클래스에는 GENERATED_BODY()가 필요합니다.
즉, GENERATED_BODY는 언리얼 객체 시스템에 들어가기 위한 필수 연결 지점입니다.
🧠 GENERATED_BODY는 “마법”이 아니다
처음 보면 GENERATED_BODY()는 마법처럼 보입니다.
하지만 내부 구조를 알고 나면 조금 다르게 보입니다.
UHT가 헤더를 분석한다 ↓ 필요한 코드를 생성한다 ↓ .generated.h에 넣는다 ↓ GENERATED_BODY 위치에 연결한다
즉 GENERATED_BODY()는 마법이 아니라:
👉 코드 생성 기반 Reflection 시스템의 연결점
입니다.
🎯 중요한 포인트
여기서 가장 중요한 점은 이것입니다.
GENERATED_BODY는 단순히 클래스 선언을 보조하는 매크로가 아니라,
언리얼 엔진이 해당 클래스를 Reflection 시스템에 등록하기 위한 핵심 장치입니다.
이를 통해:
- UClass 생성
- StaticClass 사용
- Property / Function 메타데이터 등록
- CDO 연결
- GC / 직렬화 / 블루프린트 / 네트워크 시스템 연결
이 가능해집니다.
🎯 핵심 정리
GENERATED_BODY()는 UHT 생성 코드가 삽입되는 위치입니다.- UHT는 UCLASS, UPROPERTY, UFUNCTION을 분석합니다.
- 분석 결과는
.generated.h및 생성 코드로 만들어집니다. - 생성 코드에는 StaticClass, UClass 등록, Property/Function 메타데이터 등이 포함될 수 있습니다.
- CDO, GC, 블루프린트, 직렬화, RPC 시스템과도 연결됩니다.
- 즉 GENERATED_BODY는 언리얼 Reflection 시스템의 핵심 연결 지점입니다.
🧩 마무리
언리얼을 처음 공부할 때는
- UCLASS는 왜 필요하지?
- GENERATED_BODY는 왜 꼭 써야 하지?
- generated.h는 왜 생기지?
같은 부분이 굉장히 낯설게 느껴질 수 있습니다.
하지만 지금까지의 흐름을 보면 이유가 명확합니다.
언리얼은 C++ 위에 자체 객체 시스템과 Reflection 시스템을 구축했습니다.
그리고 그 시스템을 연결하기 위해 UHT가 코드를 생성합니다.
GENERATED_BODY()는 그 생성 코드가 클래스 내부에 연결되는 지점입니다.
즉 핵심은 이것입니다.
GENERATED_BODY는 언리얼 클래스가 엔진 객체 시스템에 참여하기 위한 입구입니다.
이 구조를 이해하면, 언리얼의 매크로들이 단순히 복잡한 문법이 아니라
엔진 전체 구조를 연결하기 위한 장치라는 점이 보이기 시작합니다.