본문으로 건너뛰기

안동민 개발노트

본문 시작

C++ 클래스와 언리얼 리플렉션 시스템

UCLASS·UPROPERTY·UFUNCTION 매크로가 UHT를 거쳐 에디터와 Blueprint에 노출되는 과정을 익힙니다.

첫 번째 언리얼 C++ 프로젝트를 만들고 에디터 구조를 확인했다면, 이제 본격적으로 코드로 엔진을 제어할 단계입니다.

그 시작점이 바로 리플렉션 시스템(Reflection System)입니다.

이 개념을 이해해야 C++ 클래스가 에디터와 블루프린트 안에서 정상적으로 노출되고, 설정 가능한 형태로 동작합니다.

일반 C++ 클래스만으로는 언리얼 에디터가 코드를 충분히 이해하고 상호작용하기 어렵습니다.

언리얼 엔진은 자체 메커니즘으로 C++ 메타데이터를 생성해, 에디터 노출, 속성 편집, 블루프린트 연동을 가능하게 합니다.

이 연결의 중심이 바로 리플렉션 시스템입니다.

리플렉션 시스템의 핵심 흐름: UCLASS/UPROPERTY/UFUNCTION 선언이 UHT 처리와 .generated.h 생성을 거쳐 에디터와 블루프린트 기능으로 연결됩니다.

C++ 선언은 UHT를 거쳐 런타임 도구가 읽는 타입 정보가 된다

리플렉션 매크로는 장식이 아니라 헤더 선언을 생성 코드와 에디터·블루프린트·직렬화·GC에 전달하는 빌드 계약이다.

  1. Header Macro

    UCLASS, UPROPERTY, UFUNCTION으로 도구가 읽을 대상을 표시한다.

  2. UHT

    컴파일 전에 헤더를 분석해 타입·속성·함수 정보를 추출한다.

  3. Generated Code

    등록 함수와 Property 테이블을 만들어 C++ 빌드에 합친다.

  4. Runtime Tools

    Details, Blueprint, Serialize, GC가 같은 타입 정보를 사용한다.

  5. include 순서

    generated.h 는 헤더의 마지막 include에 둔다.

  6. 본문 위치

    GENERATED_BODY() 를 클래스 본문 계약에 맞게 둔다.

  7. 노출 최소화

    도구가 필요로 하는 멤버만 지정자로 공개한다.


언리얼의 리플렉션 시스템이란?

리플렉션 시스템은 실행 중 클래스 구조와 동작 정보를 조회/활용할 수 있게 해 주는 메커니즘입니다.

일반 C++은 이런 실행 시점 정보(Runtime Information)를 충분히 제공하지 않지만, 언리얼은 전용 매크로로 클래스/변수/함수 메타데이터를 생성해 런타임과 에디터에서 활용할 수 있게 합니다.

이 메타데이터 덕분에 언리얼 엔진 에디터는 여러분이 C++로 만든 클래스를 인식하고, 해당 클래스의 인스턴스를 월드에 배치하거나, 클래스 내부의 변수를 디테일 패널에서 편집할 수 있게 됩니다.

또한 블루프린트에서 여러분의 C++ 함수를 호출하거나 변수에 접근하는 것도 이 리플렉션 시스템 덕분에 가능한 일이죠.

그럼, 이러한 리플렉션 시스템을 활성화하기 위해 사용되는 주요 매크로들을 하나씩 살펴보겠습니다.


UCLASS(): 언리얼 엔진 클래스로서의 선언

가장 기본이 되는 매크로는 UCLASS()입니다.

여러분이 만드는 C++ 클래스가 언리얼 엔진의 클래스로 인식되려면 반드시 클래스 선언 바로 위에 이 매크로를 붙여야 합니다.

MyAwesomeActor.h
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyAwesomeActor.generated.h" // 필수 포함 파일

UCLASS() // 이 클래스가 언리얼 엔진의 클래스임을 선언합니다.
class MYFIRSTCPPPROJECT_API AMyAwesomeActor : public AActor
{
    GENERATED_BODY() // 이 클래스의 본문을 생성하기 위한 매크로입니다.

public:
    // 생성자입니다.
    AMyAwesomeActor();

protected:
    // 게임 시작 시 호출됩니다.
    virtual void BeginPlay() override;

public:
    // 매 프레임 호출됩니다.
    virtual void Tick(float DeltaTime) override;
};

위 코드에서 몇 가지 중요한 점을 짚어보겠습니다.

  • #include "MyAwesomeActor.generated.h": UCLASS()를 사용하는 언리얼 클래스에서 반드시 마지막 include여야 합니다. Unreal Build Tool이 자동 생성하는 파일이며, 리플렉션 시스템에 필요한 메타데이터 코드가 들어 있습니다.

  • UCLASS(): 이 매크로가 붙어 있는 클래스는 언리얼 엔진에 의해 특별하게 처리됩니다. 에디터에서 이 클래스를 기반으로 액터를 생성하거나, 블루프린트로 파생시킬 수 있게 됩니다.

  • GENERATED_BODY(): 이 매크로도 UCLASS()와 함께 모든 언리얼 클래스의 본문(Body) 시작 부분에 필수적으로 포함되어야 합니다. 언리얼 엔진의 리플렉션 시스템이 제대로 작동하기 위해 필요한 내부 코드를 자동으로 생성해줍니다.

  • A 접두사: AMyAwesomeActor처럼 액터 계열 클래스에는 보통 A를 붙입니다. 컴포넌트는 U(예: UStaticMeshComponent), 열거형은 E(예: ECollisionChannel)를 쓰는 식입니다.

    필수 규칙은 아니지만 코드 가독성과 엔진 코드 일관성을 위해 강력히 권장됩니다.

  • MYFIRSTCPPPROJECT_API: 이는 DLL 익스포트/임포트와 관련된 매크로입니다. 여러분의 프로젝트 모듈에서 이 클래스를 외부에 노출할 때 사용됩니다. 크게 신경 쓰지 않아도 되지만, 언리얼 프로젝트에서 자동 생성되는 코드에 붙어 있음을 인지하고 계시면 됩니다.

  • public AActor: 우리가 만든 AMyAwesomeActorAActor를 상속받고 있습니다. AActor는 언리얼 엔진 월드에 배치할 수 있는 모든 객체의 기본 클래스입니다. 나중에 다른 기본 클래스들도 배우게 될 것입니다.


UPROPERTY(): 에디터 노출 및 직렬화

UPROPERTY 지정자는 에디터, 블루프린트, 저장, GC의 접근 범위를 정한다

프로퍼티 지정자는 표시용 주석이 아니라 엔진이 C++ 멤버를 어떻게 다룰지 알려주는 계약이다.

지정자의미사용 기준
EditAnywhere기본값과 인스턴스 양쪽에서 수정할 수 있다레벨별 조정이 필요한 값
VisibleAnywhere에디터에 보이지만 직접 수정하지 않는다상태 확인용 읽기 값
BlueprintReadWrite블루프린트에서 읽고 쓸 수 있다BP가 실제로 조정해야 하는 값
Category / SerializationDetails 그룹과 저장·참조 추적을 연결한다팀이 찾기 쉬운 이름과 GC 대상

UPROPERTY() 매크로는 C++ 클래스 내의 멤버 변수(속성)를 언리얼 엔진의 리플렉션 시스템에 노출시켜 에디터의 디테일 패널에서 편집할 수 있게 하거나, 저장/로드(직렬화, Serialization)할 수 있도록 합니다.

MyAwesomeActor.h
#pragma once

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

UCLASS()
class MYFIRSTCPPPROJECT_API AMyAwesomeActor : public AActor
{
    GENERATED_BODY()

public:
    AMyAwesomeActor();

protected:
    virtual void BeginPlay() override;

public:
    virtual void Tick(float DeltaTime) override;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "My Properties")
    float MyFloatVariable; // 에디터에서 편집 가능한 float 변수

    UPROPERTY(VisibleAnywhere, Category = "My Properties")
    int MyIntegerVariable; // 에디터에서 볼 수만 있는 int 변수

    UPROPERTY(EditDefaultsOnly, Category = "My Properties")
    FString MyStringVariable; // 블루프린트 기본값에서만 편집 가능한 문자열
};

UPROPERTY() 매크로 괄호 안에 들어가는 키워드들은 해당 변수가 에디터에서 어떻게 동작할지, 블루프린트에서 어떻게 접근할지 등을 정의합니다.

몇 가지 자주 사용되는 키워드를 소개합니다.

  • EditAnywhere: 이 변수는 언리얼 에디터의 디테일 패널에서 어디에서든(에디터 레벨, 블루프린트 기본값 등) 편집할 수 있습니다. 가장 일반적으로 사용됩니다.
  • VisibleAnywhere: 이 변수는 디테일 패널에서 볼 수는 있지만(Read-Only) 편집할 수는 없습니다.
  • EditDefaultsOnly: 이 변수는 블루프린트 클래스의 기본값에서만 편집할 수 있습니다. 즉, 레벨에 배치된 액터 인스턴스에서는 변경할 수 없고, 해당 블루프린트 클래스 자체의 기본값을 설정할 때만 편집 가능합니다.
  • BlueprintReadWrite: 이 변수는 블루프린트에서 읽고 쓰는(Set/Get) 것이 모두 가능하게 합니다.
  • BlueprintReadOnly: 이 변수는 블루프린트에서 읽기만(Get)가능하게 합니다.
  • Category = "My Properties": 디테일 패널에서 이 변수들이 특정 카테고리(My Properties) 아래에 묶여 표시되도록 합니다. 여러 변수가 있을 때 패널을 깔끔하게 정리하는 데 유용합니다.
  • meta=(DisplayName="My Custom Name"): 디테일 패널에 표시되는 변수 이름을 원하는 대로 지정할 수 있습니다.

UFUNCTION(): 블루프린트 노출, 호출 가능

UFUNCTION() 매크로는 C++ 클래스 내의 멤버 함수(메서드)를 언리얼 엔진의 리플렉션 시스템에 노출시켜 블루프린트에서 호출할 수 있게 합니다.

MyAwesomeActor.h
#pragma once

// ... (생략) ...

UCLASS()
class MYFIRSTCPPPROJECT_API AMyAwesomeActor : public AActor
{
    GENERATED_BODY()

public:
    AMyAwesomeActor();

protected:
    virtual void BeginPlay() override;

public:
    virtual void Tick(float DeltaTime) override;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "My Properties")
    float CurrentHealth;

    UFUNCTION(BlueprintCallable, Category = "My Functions")
    void TakeDamage(float DamageAmount); // 블루프린트에서 호출 가능한 함수

    UFUNCTION(BlueprintPure, Category = "My Functions")
    float GetHealthRatio() const; // 블루프린트에서 호출 가능한 순수(Pure) 함수
};

UFUNCTION() 매크로 괄호 안의 키워드도 UPROPERTY()와 유사하게 함수의 동작 방식을 정의합니다.

  • BlueprintCallable: 이 함수를 블루프린트에서 호출(Call) 할 수 있게 합니다. 예를 들어, 버튼을 클릭했을 때 특정 C++ 함수를 실행하고 싶을 때 사용합니다.

  • BlueprintPure: 블루프린트 호출 가능 + 순수(Pure) 함수임을 뜻합니다. 순수 함수는 상태를 변경하지 않고(부작용 없음), 같은 입력에 같은 출력을 반환합니다.

    블루프린트에서는 실행 핀 없이 데이터 핀만 가지며, 주로 Getter 함수에 사용됩니다.

  • Category = "My Functions": UPROPERTY와 마찬가지로 블루프린트 에디터에서 함수가 특정 카테고리 아래에 표시되도록 합니다.

  • Server / Client / NetMulticast: 네트워크 게임 개발 시 함수가 어떤 방식으로 복제되고 실행될지 정의하는 키워드입니다. 이 책의 범위를 벗어나지만, 나중에 고급 과정을 학습할 때 접하게 될 것입니다.


실무에서 자주 혼동되는 UPROPERTY/UFUNCTION 키워드를 목적별로 빠르게 고를 수 있도록 정리한 시각 요약입니다.

매크로 공개 계약

C++ 선언은 컴파일만으로 끝나지 않습니다. UHT가 읽을 수 있는 매크로와 specifier를 붙여야 에디터, 저장, 블루프린트 노출까지 이어집니다.

  1. UCLASS
    타입을 엔진 객체로 등록

    UCLASS 클래스 생성, 블루프린트 상속, 에디터 노출의 출발점입니다. GENERATED_BODY() 와 generated header 순서를 같이 지킵니다.

  2. UPROPERTY
    값과 참조를 엔진이 추적

    UPROPERTY 디테일 패널 편집, 직렬화, GC 안전 참조, 블루프린트 get/set 권한을 specifier로 나눕니다.

  3. UFUNCTION
    동작을 노드와 이벤트로 공개

    UFUNCTION BlueprintCallable 은 실행 핀, BlueprintPure 는 데이터 핀 중심입니다. 상태 변경 여부로 구분합니다.

리플렉션 매크로를 실제 코드에 붙일 때는 "엔진이 알아야 하는 정보"와 "블루프린트에 공개할 권한"을 분리해 점검하면 실수를 줄일 수 있습니다.

노출 대상과 허용 동작을 따로 결정한다

리플렉션 매크로는 C++ 심볼을 에디터·Blueprint·직렬화·GC 시스템에 등록하고, specifier가 사용 범위를 좁힌다.

  1. 타입

    UCLASS 클래스 자체의 Blueprint 사용 방식과 상속 가능 범위를 정한다. BlueprintType 변수 타입으로 사용 Blueprintable BP 자식 클래스 생성

  2. 데이터

    UPROPERTY Details 편집 범위와 Blueprint 그래프 접근 권한은 서로 다른 축이다. EditDefaultsOnly 클래스 기본값만 편집 BlueprintReadOnly 그래프에서는 읽기만

  3. 호출

    UFUNCTION Blueprint 호출 가능성과 네트워크 실행 방향을 명시한다. Callable / Pure 실행 노드 / 조회 노드 Server / Client RPC 실행 위치

  4. UObject 참조를 추적

    GC UObject 참조를 추적 멤버 참조는 UPROPERTY와 TObjectPtr로 수명 시스템에 보이게 한다.

  5. 노출은 권한 검증이 아니다

    GUARD 노출은 권한 검증이 아니다 BlueprintCallable이나 RPC에도 서버 측 값 검증은 별도로 필요하다.

UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Combat")
float MaxHealth = 100.0f;

UPROPERTY()
TObjectPtr<UObject> ActiveEffect;

리플렉션 관련 오류가 났을 때는 C++ 문법만 보지 말고 UHT가 읽는 선언, 자동 생성 파일, 에디터 노출 단계까지 같은 순서로 되짚어야 합니다.

리플렉션 오류는 선언에서 도구 노출까지 역방향으로 추적한다

보이지 않는 속성이나 UHT 오류는 증상만 고치지 말고 헤더 계약, 생성 코드, 빌드 산출물, 에디터 상태를 순서대로 좁힌다.

  1. 1
    Symptom

    UHT 오류, 컴파일 실패, Details·노드 미노출 중 증상을 분류한다.

  2. 2
    Header

    매크로, 지정자, generated.h 순서와 선언 위치를 확인한다.

  3. 3
    Build

    UHT가 새 generated code를 만들고 대상 모듈이 다시 빌드됐는지 본다.

  4. 4
    Editor

    올바른 클래스·인스턴스와 최신 모듈을 보고 있는지 확인한다.

  5. 5
    Recheck

    최소 선언으로 재현하고 지정자를 하나씩 되돌려 원인을 고정한다.

  6. 6
    컴파일 전

    UHT 메시지와 헤더 계약을 먼저 본다.

  7. 7
    컴파일 후

    모듈·에디터 인스턴스·Hot Reload 상태를 본다.

  8. 8
    노출 실패

    접근 지정자와 Blueprint/Editor 지정자를 분리한다.


정리 및 다음 단계

지금까지 언리얼 엔진 C++ 개발의 핵심인 리플렉션 시스템과 UCLASS(), UPROPERTY(), UFUNCTION() 매크로를 살펴보았습니다.

이 매크로들은 C++ 코드가 언리얼 에디터와 블루프린트에 노출될 수 있도록 메타데이터를 제공하는 장치입니다.

정리하면, 리플렉션 시스템과 핵심 매크로의 역할을 이해하는 것이 언리얼 C++ 기초의 출발점입니다.

다음 절에서는 이 매크로들을 직접 사용해보고, 언리얼 엔진의 액터 클래스를 상속받아 기본적인 C++ 클래스를 생성하고 수정하는 실습을 진행합니다.

이론을 실제 코드로 적용하면서 개념을 더 명확하게 연결할 수 있습니다.

마지막으로 UHT가 실제로 읽는 선언과 블루프린트 노출 권한을 함께 점검해 보세요.

리플렉션 선언은 클래스, 속성, 함수, 빌드 신호를 함께 점검한다

매크로를 많이 붙이는 것이 목표가 아니라 에디터가 알아야 할 것만 정확히 노출하는 것이 목표다.

계약필수 조건오류 때 먼저 볼 것
ClassUCLASS와 GENERATED_BODY가 있다엔진 타입 시스템에 등록되지 않음
PropertyUPROPERTY 지정자가 저장·편집·읽기 권한을 정한다Details 값이 보이지 않음
FunctionUFUNCTION이 호출, 이벤트, RPC 경로를 연다노드 검색 결과가 없음
Build Signalgenerated.h 위치와 매크로 누락을 확인한다일반 C++ 오류처럼 보이는 UHT 실패