C++ 클래스와 언리얼 리플렉션 시스템
첫 번째 언리얼 C++ 프로젝트를 만들고 에디터 구조를 확인했다면, 이제 본격적으로 코드로 엔진을 제어할 단계입니다.
그 시작점이 바로 리플렉션 시스템(Reflection System)입니다. 이 개념을 이해해야 C++ 클래스가 에디터와 블루프린트 안에서 정상적으로 노출되고, 설정 가능한 형태로 동작합니다.
일반 C++ 클래스만으로는 언리얼 에디터가 코드를 충분히 이해하고 상호작용하기 어렵습니다.
언리얼 엔진은 자체 메커니즘으로 C++ 메타데이터를 생성해, 에디터 노출, 속성 편집, 블루프린트 연동을 가능하게 합니다. 이 연결의 중심이 바로 리플렉션 시스템입니다.
리플렉션 시스템의 핵심 흐름: UCLASS/UPROPERTY/UFUNCTION 선언이 UHT 처리와 .generated.h 생성을 거쳐 에디터와 블루프린트 기능으로 연결됩니다.
언리얼의 리플렉션 시스템이란?
리플렉션 시스템은 실행 중 클래스 구조와 동작 정보를 조회/활용할 수 있게 해 주는 메커니즘입니다.
일반 C++은 이런 실행 시점 정보(Runtime Information)를 충분히 제공하지 않지만, 언리얼은 전용 매크로로 클래스/변수/함수 메타데이터를 생성해 런타임과 에디터에서 활용할 수 있게 합니다.
이 메타데이터 덕분에 언리얼 엔진 에디터는 여러분이 C++로 만든 클래스를 인식하고, 해당 클래스의 인스턴스를 월드에 배치하거나, 클래스 내부의 변수를 디테일 패널에서 편집할 수 있게 됩니다. 또한 블루프린트에서 여러분의 C++ 함수를 호출하거나 변수에 접근하는 것도 이 리플렉션 시스템 덕분에 가능한 일이죠.
그럼, 이러한 리플렉션 시스템을 활성화하기 위해 사용되는 주요 매크로들을 하나씩 살펴보겠습니다.
UCLASS(): 언리얼 엔진 클래스로서의 선언
가장 기본이 되는 매크로는 UCLASS()입니다. 여러분이 만드는 C++ 클래스가 언리얼 엔진의 클래스로 인식되려면 반드시 클래스 선언 바로 위에 이 매크로를 붙여야 합니다.
#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: 우리가 만든AMyAwesomeActor는AActor를 상속받고 있습니다.AActor는 언리얼 엔진 월드에 배치할 수 있는 모든 객체의 기본 클래스입니다. 나중에 다른 기본 클래스들도 배우게 될 것입니다.
UPROPERTY(): 에디터 노출 및 직렬화
UPROPERTY() 매크로는 C++ 클래스 내의 멤버 변수(속성)를 언리얼 엔진의 리플렉션 시스템에 노출시켜 에디터의 디테일 패널에서 편집할 수 있게 하거나, 저장/로드(직렬화, Serialization)할 수 있도록 합니다.
#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++ 클래스 내의 멤버 함수(메서드)를 언리얼 엔진의 리플렉션 시스템에 노출시켜 블루프린트에서 호출할 수 있게 합니다.
#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++ 개발의 핵심인 리플렉션 시스템과 UCLASS(), UPROPERTY(), UFUNCTION() 매크로에 대해 살펴보았습니다. 이 매크로들은 단순한 키워드가 아니라, 여러분이 작성하는 C++ 코드가 언리얼 엔진 에디터 및 블루프린트와 매끄럽게 상호작용하도록 만들어주는 마법과 같은 도구들입니다.
정리하면, 리플렉션 시스템과 핵심 매크로의 역할을 이해하는 것이 언리얼 C++ 기초의 출발점입니다. 다음 절에서는 이 매크로들을 직접 사용해보고, 언리얼 엔진의 액터 클래스를 상속받아 기본적인 C++ 클래스를 생성하고 수정하는 실습을 진행합니다. 이론을 실제 코드로 적용하면서 개념을 더 명확하게 연결할 수 있습니다.