GENERATED_BODY는 실제로 어떤 코드를 생성할까?

//
//

🚀 들어가며

이전 글에서는 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 코드를 클래스 내부에 연결하는 핵심 지점이라는 점입니다.

Unreal Engine의 GENERATED_BODY 매크로가 UHT, generated.h, UClass Reflection 시스템과 연결되는 구조를 설명하는 인포그래픽
GENERATED_BODY는 단순 매크로가 아니라, UHT가 생성한 Reflection 코드를 Unreal Engine 클래스 내부에 연결하는 핵심 지점입니다.

실제 생성 코드는 언리얼 버전, 빌드 설정, 클래스 종류에 따라 달라질 수 있습니다.

그래서 여기서는 실제 엔진 코드를 그대로 복사하기보다는 개념적으로 어떤 종류의 코드가 생성되는지 정리하겠습니다.

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는 언리얼 클래스가 엔진 객체 시스템에 참여하기 위한 입구입니다.

이 구조를 이해하면, 언리얼의 매크로들이 단순히 복잡한 문법이 아니라
엔진 전체 구조를 연결하기 위한 장치라는 점이 보이기 시작합니다.

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

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

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

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

//
   

댓글 남기기

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