C++ 클래스와 언리얼 리플렉션 시스템
첫 번째 언리얼 엔진 C++ 프로젝트를 성공적으로 생성하고 언리얼 에디터의 주요 패널들을 살펴보는 시간을 가졌습니다. 이제부터는 본격적으로 C++ 코드를 작성하며 언리얼 엔진을 제어하는 방법을 배울 차례입니다. 그 첫걸음으로, 언리얼 엔진 C++의 가장 핵심적인 개념 중 하나인 리플렉션 시스템(Reflection System) 과 이를 가능하게 하는 특별한 매크로들에 대해 알아보겠습니다.
C++은 강력한 언어지만, 일반적인 C++ 클래스만으로는 언리얼 엔진 에디터가 여러분의 코드를 인식하고 상호작용하기 어렵습니다. 언리얼 엔진은 자체적인 메커니즘을 통해 여러분이 작성한 C++ 코드를 에디터에서 시각적으로 확인하고, 속성을 조작하며, 블루프린트와 연동할 수 있도록 만드는데, 이 핵심적인 역할을 하는 것이 바로 '리플렉션 시스템'입니다.
언리얼의 리플렉션 시스템이란?
리플렉션 시스템은 프로그램 실행 중에 자신의 구조나 동작을 검사하고 수정할 수 있는 능력을 의미합니다. 일반적인 C++은 이러한 '실행 시점 정보(Runtime Information)'를 직접적으로 제공하지 않습니다. 하지만 언리얼 엔진은 특별한 매크로들을 사용하여 여러분의 C++ 클래스와 변수, 함수 등의 메타데이터(Metadata)를 생성하고, 이를 통해 런타임에 이 정보들을 활용할 수 있도록 합니다.
이 메타데이터 덕분에 언리얼 엔진 에디터는 여러분이 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()
매크로를 사용한 모든 언리얼 클래스에서 반드시 가장 마지막에 포함되어야 합니다. 언리얼 엔진의 빌드 시스템(Unreal Build Tool)이 여러분의 코드를 분석하여 자동으로 생성하는 파일입니다. 이 파일 안에 리플렉션 시스템이 필요로 하는 메타데이터 코드가 들어 있습니다.UCLASS()
: 이 매크로가 붙어 있는 클래스는 언리얼 엔진에 의해 특별하게 처리됩니다. 에디터에서 이 클래스를 기반으로 액터를 생성하거나, 블루프린트로 파생시킬 수 있게 됩니다.GENERATED_BODY()
: 이 매크로도UCLASS()
와 함께 모든 언리얼 클래스의 본문(Body) 시작 부분에 필수적으로 포함되어야 합니다. 언리얼 엔진의 리플렉션 시스템이 제대로 작동하기 위해 필요한 내부 코드를 자동으로 생성해줍니다.A
접두사:AMyAwesomeActor
처럼 언리얼 엔진의 클래스 이름 앞에는 보통A
접두사가 붙습니다. 이는 액터(Actor) 계열 클래스임을 나타내는 컨벤션(관례)입니다. 컴포넌트는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)" 함수임을 나타냅니다. 순수 함수는 객체의 상태를 변경하지 않고(Side Effect가 없고), 항상 동일한 입력에 대해 동일한 출력을 반환하는 함수를 의미합니다. 블루프린트에서 이 함수는 실행 핀(Execution Pin) 없이 데이터 핀만 가집니다. 주로 Getter 함수에 사용됩니다.Category = "My Functions"
:UPROPERTY
와 마찬가지로 블루프린트 에디터에서 함수가 특정 카테고리 아래에 표시되도록 합니다.Server
/Client
/NetMulticast
: 네트워크 게임 개발 시 함수가 어떤 방식으로 복제되고 실행될지 정의하는 키워드입니다. 이 책의 범위를 벗어나지만, 나중에 고급 과정을 학습할 때 접하게 될 것입니다.
정리 및 다음 단계
지금까지 언리얼 엔진 C++ 개발의 핵심인 리플렉션 시스템과 UCLASS()
, UPROPERTY()
, UFUNCTION()
매크로에 대해 살펴보았습니다. 이 매크로들은 단순한 키워드가 아니라, 여러분이 작성하는 C++ 코드가 언리얼 엔진 에디터 및 블루프린트와 매끄럽게 상호작용하도록 만들어주는 마법과 같은 도구들입니다.
이 개념들을 머릿속에 잘 넣어두고 다음 절로 넘어갈 준비를 해볼까요? 다음 절에서는 이 매크로들을 직접 사용해보고, 언리얼 엔진의 액터 클래스를 상속받아 기본적인 C++ 클래스를 생성하고 수정하는 실습을 진행할 것입니다. 이론을 실제 코드로 적용해보는 경험을 통해 더욱 명확하게 이해하실 수 있을 거예요.