본문 바로가기

[C++과 Unreal Engine으로 3D 게임 개발] C++ 클래스와 리플렉션 시스템 활용

@iamrain2025. 9. 17. 11:19

리플렉션 시스템

Blueprint (시각적 스크립팅)

Blueprint는 언리얼 엔진에서 제공하는 시각적 스크립팅 도구로 노드(블록)를 연결하여 게임 로직을 작성한다.

 

장점

  • Blueprint 그래프 수정 후, 에디터에서 Play로 바로 확인 가능
  • 노드를 연결해 로직을 작성하므로, 초보자도 쉽게 접근 가능

한계점

  • 노드 수가 많아질수록 그래프가 복잡해져 가독성과 유지보수가 어려워짐
  • 내부적인 추가 해석 과정을 거치기 때문에 고성능이 요구되는 시스템에서 병목 발생 가능성 존재

C++ (네이티브 코드)

장점

  • 엔진 코어까지 직접 수정이 가능하고, 복잡하고 성능이 중요한 게임 로직을 빠르고 최적화된 방식으로 구현 가능
  • 표준 라이브러리와 외부 라이브러리 사용이 자유로워, 대규모 프로젝트에 적합
  • 포인터, 템플릿 같은 C++ 언어적 기능을 통해 메모리와 로직을 정교하게 다룰 수 있음

한계점

  • C++ 코드를 수정하면 에디터를 재시작하거나 Live Coding을 다시 컴파일해야 하므로, 반복 작업에 불리함

Blueprint와 C++의 상호보완적 관계

실무에서는 둘을 함께 사용하는 경우가 많다. 하나만 사용하는 것보다 각각의 장점을 취하는게 일반적이다.

  • Blueprint 활용 : UI 제작, 간단한 이벤트 처리, 시각적 연출 등 빠른 프로토타이핑과 직관적인 로직 작성에 사용
  • C++ 활용 : 높은 성능이 필요한 게임플레이 로직이나 엔진 레벨의 확장, 복잡한 수학 연산 등에 사용

리플렉션(Reflection)이란?

언리얼 엔진의 리플렉션 시스템은 C++ 클래스의 변수 및 함수 정보를 엔진 내부의 메타데이터 형태로 저장하고, 이를 에디터나 블루프린트에서 활용할 수 있게 만들어주는 기술이다.

 

C++ 클래스에 있는 여러 멤버(변수, 함수 등)를 "Reflection(반사)"해, 에디터와 블루프린트에서 직접 설정, 호출이 가능하게 한다.

이 덕분에 프로그래머가 만든 C++ 로직의 뼈대를 디자이너나 다른 팀원들이 에디터에서 직관적으로 조정할 수 있다. 매개변수를 코드에서만 변경하는 것이 아니라, 에디터에서 바로 조정하여 반복 테스트를 빠르게 진행할 수 있다.

 

C++ 클래스 리플렉션 등록

리플렉션 관련 매크로

`.h`

#pragma once

...
#include "Item.generated.h"

UCLASS()
class SPARTAPROJECT_API AItem : public AActor
{
	GENERATED_BODY()
	
public:	
	...
};
  • `#include "Item.generated.h"`
    • 언리얼 엔진이 자동 생성하는 헤더 파일로, 클래스의 리플렉션 및 엔진 통합에 필요한 코드가 들어있다.
    • 반드시 헤더 파일의 `#include` 구문 마지막에 위치해야 한다.
  • `UCLASS()`
    • 언리얼 엔진의 리플렉션 시스템에 등록한다는 의미다.
    • 이 매크로가 있어야 에디터에서 이 클래스를 인식하고 사용할 수 있다.
  • `GENERATED_BODY()`
    • 언리얼의 코드 생성 도구가 사용하는 코드를 삽입하는 역할을 한다.
    • 클래스 내부에 필요한 리플렉션 정보를 자동으로 생성해 준다.

`UCLASS()` 매크로의 주요 지정자

`UCLASS()` 매크로는 클래스를 리플렉션 시스템에 등록하면서 몇 가지 옵션(지정자)을 설정할 수 있다. 필요에 따라 지정자들을 조합해 클래스가 어떻게 상호작용해야 할지 명시할 수 있다.

  • 기본 동작
    • 옵션을 주지 않으면, 블루프린트에서 상속이 가능하고 변수로 참조가 가능한 형태로 등록된다.
  • 주요 옵션
    • `Blueprintable` : 블루프린트에서 상속이 가능한 클래스로 만든다.
    • `NotBlueprintable` : 블루프린트에서 상속할 수 없도록 만든다.
    • `BlueprintType` : 블루프린트에서 변수나 참조로 사용할 수 있게 한다. 단, 이 옵션만 있으면 상속은 허용되지 않고 참조만 가능하다.

C++ 클래스를 상속 받은 Blueprint 클래스 생성

우클릭 후 블루프린트 생성
블루프린트 이름과 저장 위치 지정

 

변수에 리플렉션 적용

`UPROPERTY()` 매크로의 주요 지정자

`UPROPERTY()`에는 여러 지정자를 작성해 자세한 설정을 할 수 있다.

  1. 편집 가능 범위
    • `VisibleAnywhere` : 읽기 전용으로 표시. 수정 불가능.
    • `EditAnywhere` : 클래스 기본값, 인스턴스 모두에서 수정 가능.
    • `EditDefaultsOnly` : 클래스 기본값에서만 수정 가능.
    • `EditInstanceOnly` : 인스턴스에서만 수정 가능.
  2. Blueprint 접근자
    • `BlueprintReadWrite` : Blueprint 그래프에서 Getter / Setter 로 값을 읽거나 쓸 수 있다.
    • `BlueprintReadOnly` : Blueprint 그래프에서 Getter 핀만 노출되어, 읽기만 가능하다.
  3. Category
    • Details 패널에서 "Rotation" 범주(폴더) 아래에 표시된다.
    • 여러 변수를 비슷한 카테고리에 묶으면, 세부 정보 패널에서 깔끔하게 정리되어 보인다.
  4. 메타 옵션
    • `meta=(ClampMin="0.0")` : 에디터에서 변수 입력 시 최소값을 제한할 수 있다.
    • `meta=(AllowPrivateAccess="true")` : 해당 멤버가 `private`로 선언되어 있어도, 에디터나 Blueprint에서 접근할 수 있도록 허용한다.
만약 `UPROPERTY()`만 있고, 추가 지정자가 하나도 없다면?
엔진 리플렉션 시스템에는 등록되지만, 에디터나 Blueprint에 노출되지 않는다. "엔진이 변수의 존재는 알지만, 외부에 보이지 않게 숨긴 상태"라고 볼 수 있다.
리플렉션에 등록만 되어 있어도 가비지 컬렉션과 직렬화 같은 엔진 내부 기능이 작동할 수 있다.

클래스 변수 리플렉션 등록

`.h`

...

UCLASS()
class SPARTAPROJECT_API AItem : public AActor
{
	...

protected:
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Components")
	USceneComponent* SceneRoot;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item|Components")
	UStaticMeshComponent* StaticMeshComp;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Item|Properties")
	float RotationSpeed;

	virtual void BeginPlay() override;
	virtual void Tick(float DeltaTime) override;
};

`.cpp`

#include "Item.h"

AItem::AItem()
{
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
	SetRootComponent(SceneRoot);

	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
	StaticMeshComp->SetupAttachment(SceneRoot);

	PrimaryActorTick.bCanEverTick = true;

	RotationSpeed = 90.0f;
}

void AItem::BeginPlay()
{
	Super::BeginPlay();
}

void AItem::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (!FMath::IsNearlyZero(RotationSpeed))
	{
		AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaTime, 0.0f));
	}
}

Blueprint에서 C++ 변수 조정

생성한 블루프린트에 들어가면 아래와 같은 창을 볼 수 있다. 이 창에서 값을 조절하면 빌드 없이 바로 적용된다.

 

함수에 리플렉션 적용

함수 리플렉션이란?

`UFUNCTION()` 매크로를 사용해 함수를 블루프린트에서 직접 호출할 수 있도록 등록할 수 있다.

`UFUNCTION()` 매크로의 주요 지정자

  • Blueprint 관련
    • `BlueprintCallable` : Blueprint 이벤트 그래프(노드)에서 호출(Execute) 가능한 함수로 만든다.
    • `BlueprintPure` : Getter 역할만 수행한다.(Exec 핀 없이 Return Value만 노출)
    • `BlueprintImplementableEvent` : 함수의 선언만 C++에 있고, 구현은 블루프린트에서 하도록 한다. C++ 코드에서는 함수 이름만 정의하고, 실제 동작은 Blueprint Event Graph 안에서 이벤트 노드처럼 구현된다.
만약 `UFUNCTION()`에 지정자를 하나도 쓰지 않으면?
`UPROPERTY()`와 마찬가지로, 언리얼 리플렉션에 등록되지만 노출되지 않는다.
"엔진이 함수의 존재는 알지만, Blueprint에서 호출할 수 없게 숨긴 상태"라고 보면 된다.

클래스 함수 리플렉션 등록

`.h`

...

UCLASS()
class SPARTAPROJECT_API AItem : public AActor
{
	...

protected:
	...

	UFUNCTION(BlueprintCallable, Category = "Item|Actions")
	void ResetActorPosition();

	UFUNCTION(BlueprintPure, Category = "Item|Properties")
	float GetRotationSpeed() const;

	UFUNCTION(BlueprintImplementableEvent, Category = "Item|Event")
	void OnItemPickedUp();
};

`.cpp`

...

void AItem::ResetActorPosition()
{
	SetActorLocation(FVector::ZeroVector);
}

float AItem::GetRotationSpeed() const
{
	return RotationSpeed;
}

Blueprint에서 함수 노드 사용

iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차