리플렉션 시스템 이해하기

 

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

  • 실무에서는 Blueprint와 C++를 함께 사용하는 경우가 많습니다. 하나만 사용하는 것보다 각각의 장점을 취하는 하이브리드 워크플로우가 일반적입니다.
    • Blueprint 활용: UI 제작, 간단한 이벤트 처리, 시각적 연출 등 빠른 프로토타이핑직관적인 로직 작성에 사용합니다.
    • C++ 활용: 높은 성능이 필요한 게임플레이 로직이나 엔진 레벨의 확장, 복잡한 수학 연산 등에 사용합니다.
  • 이렇게 분업하면 개발 속도와 퍼포먼스를 모두 확보하기가 수월해집니다.

 

리플렉션 (Reflection)이란?

  • 언리얼 엔진의 리플렉션 시스템은 C++ 클래스의 변수 및 함수 정보를 엔진 내부의 메타데이터 형태로 저장하고, 이를 에디터나 블루프린트에서 활용할 수 있게 만들어주는 기술입니다.
    • C++ 클래스에 있는 여러 멤버(변수, 함수 등)를 “Reflectoin (반사)”해, 에디터와 블루프린트에서 직접 설정, 호출이 가능하도록 합니다.
    • 이 덕분에 프로그래머가 만든 C++ 로직의 뼈대를 디자이너나 다른 팀원들이 에디터에서 직관적으로 조정할 수 있습니다.
    • 매개변수를 코드에서만 변경하는 것이 아니라, 에디터에서 바로 조정 (슬라이더나 숫자 입력)하여 반복 테스트를 빠르게 진행할 수 있습니다.
  • 리플렉션 시스템을 제대로 이해하고 활용하면, 큰 프로젝트에서도 개발 효율협업 효과를 극대화할 수 있습니다.

 

C++ 클래스 리플렉션에 등록하기

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h" // 클래스 선언에 리플렉션 관련 매크로가 포함되어 있음.

UCLASS()
class SPARTAPROJECT_API AItem : public AActor
{
		GENERATED_BODY()
	
public:	
		AItem();
	
protected:
		USceneComponent* SceneRoot;
		UStaticMeshComponent* StaticMeshComp;
	
		float RotationSpeed;
	
		virtual void BeginPlay() override;
		virtual void Tick(float DeltaTime) override;
};

 

  • #include "Item.generated.h"
    • 언리얼 엔진이 자동 생성하는 헤더 파일로, 클래스의 리플렉션 및 엔진 통합에 필요한 코드가 들어 있습니다.
    • 반드시 헤더 파일의 가장 마지막 #include 구문 아래에 위치해야 합니다. (다른 #include보다 아래에 오지 않으면 빌드 에러 발생 위험이 있습니다.)
  • UCLASS()
    • 해당 클래스를 언리얼 엔진의 리플렉션 시스템에 등록한다는 의미입니다.
    • 이 매크로가 있어야만 블루프린트 등 에디터 차원에서 이 클래스를 인식하고 사용할 수 있습니다.
  • GENERATED_BODY()
    • 언리얼의 코드 생성 도구가 사용하는 코드를 삽입하는 역할을 합니다.
    • 클래스 내부에 필요한 리플렉션 정보를 자동으로 생성해 줍니다.

 

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

  • UCLASS() 매크로는 클래스를 리플렉션 시스템에 등록하면서, 추가적으로 몇 가지 옵션 (지정자)을 설정할 수 있습니다.
  • 기본 동작
    • 만약 UCLASS()에 옵션을 주지 않으면, 블루프린트에서 상속이 가능하고 변수로 참조가 가능한 형태로 등록됩니다.
      (그냥 UCLASS() 이렇게만 등록된 상태 말하는 거임.)
  • 주요 옵션
    • Blueprintable
      • 블루프린트에서 상속 가능한 클래스로 만듭니다.
    • NotBlueprintable
      • 블루프린트에서 이 클래스를 상속할 수 없도록 합니다.
    • BlueprintType
      • 블루프린트에서 변수나 참조로 사용할 수 있게 합니다.
      • 이 옵션만 있으면, 상속은 허용되지 않고 참조만 가능합니다.
    • 필요에 따라 이 지정자들을 조합해 클래스가 어떻게 블루프린트와 상호작용해야 할지 명시할 수 있습니다.

 


 

그냥 UCLASS()를 쓴 Item 액터로 확인해보기. (상속과 참조 둘 다 가능!)

 

content 폴더에 블루프린트를 따로 저장할 수 있는 폴더 생성하기

 

-> 이 액터를 부모 클래스로 둬서 블루프린트 클래스를 만들겠다는 것. (상속받기)

 


 

-> 블루프린트 클래스 하나 만들어주고 이벤트 클래스 들어가기

 

 

-> 변수 선언하기

 

 

Item을 object reference 할 수 있게 됨.

즉, Item class를 변수화 시킨 것!

 

 

-> 클래스를 리플렉션에 등록하는 방법!


변수에 리플렉션 적용하기

 

헤더 파일에서 변수들 앞에 UPROPERTY() 붙여주면 됨.

그치만 클래스와 다르게 얘는 인자값이 없으면 (텅빈 상태로 두면) 아무런 효과가 없음.

그래서 인자값 설정해줘야 함.

 

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

  • UPROPERTY()에는 여러 지정자를 작성해, 에디터에서의 표시 여부나 Blueprint 접근성, 읽기/쓰기 권한 등을 자세하게 설정할 수 있습니다. 아래는 자주 쓰이는 대표적인 지정자들입니다.
  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에 노출되지는 않습니다.
    • “엔진이 변수의 존재는 알고 있지만, 외부에서는 보이지 않게 숨겨둔 상태”라고 볼 수 있습니다.
    • 리플렉션에 등록만 되어 있어도 가비지 컬렉션(메모리 관리)과 직렬화(세이브/로드) 같은 엔진 내부 기능이 작동할 수 있습니다.

 

<Item.h>

#pragma once

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

UCLASS()
class TEST01_API AItem : public AActor
{
	GENERATED_BODY()
	
public:	
	AItem();

protected:
	// Root Scene Component, 에디터에서 볼 수만 있고 수정 불가
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Item|Components")
	USceneComponent* SceneRoot; 
    
    // Static Mesh, 에디터와 Blueprint에서 수정 가능
	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;
};

 

Category="Item|Components" 

-> Item이라는 상위 카테고리에 하위 카테고리는 Components로 뜨게 하겠다는 뜻.

 

<Item.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));
	}
}

 

staticmeshcomponent를 editanywhere로 설정해뒀기 때문에, .cpp 파일에 있던 관련된 코드들을 삭제해도 됨.

 

  • 위와 같이 UPROPERTY() 지정자를 설정해두면, Blueprint 클래스를 통해 해당 변수들을 에디터 내에서 쉽게 조정할 수 있게 됩니다.
  • 이미 만든 BP_Item (Item을 부모로 하는 블루프린트)에 들어갑니다.
  • Class Defaults 탭 (또는 Details 패널)에서 RotationSpeed를 확인할 수 있습니다.
  • RotationSpeed 값만 조정해도, 코드를 다시 빌드하지 않고 오브젝트의 회전 속도를 직접 바꿔볼 수 있습니다.
  • StaticMeshComp나 Material 속성도 에디터에서 선택해 바꿀 수 있습니다.
  • 이렇게 코드 수정 없이 에디터에서 다양한 속성을 즉시 변경하고 테스트할 수 있다는 점이 리플렉션의 큰 장점입니다.

 

 

인스턴스 창!

 

클래스 디폴트 창이라는 건 또 따로 있음. 

C++ 클래스는 디폴트 수정이 불가능함.

 

 

블루프린트로 상속받은 Item 액터는 클래스 디폴트값 수정 가능함. 

 

위에 open full blueprint editor 들어가서 아래와 같이 변경 가능! 

그럼 이제 모양이나 질감이 바뀜.

 

 


함수에 리플렉션 적용하기

함수 리플렉션이란?

  • 지금까지 살펴본 것은 멤버 변수를 리플렉션 시스템에 노출하는 방법이었습니다. 이와 비슷하게, 함수 또한 블루프린트에서 직접 호출할 수 있도록 등록할 수 있습니다. 이렇게 하면, 복잡한 C++ 로직을 Blueprint에서 간단한 노드로 불러와 제어할 수 있으므로 작업 효율이 높아집니다.
    • UPROPERTY()가 멤버 변수를 리플렉션 시스템에 등록한다면,
    • UFUNCTION()은 멤버 함수를 등록합니다.
  • C++에서 만든 함수를 Blueprint 노드로 노출하고 싶을 때, UFUNCTION() 매크로를 사용합니다.

함수를 에디터 상에 노출하는 방법

함수 리플렉션 기능이 상당히 많음

많이 쓰이는 세가지!

 

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

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

 

 

	// 함수를 블루프린트에서 호출 가능하도록 설정
    UFUNCTION(BlueprintCallable, Category="Item|Actions")
	void ResetActorPosition(); // Actor를 원점으로 돌려놓는 함수.

	// 블루프린트에서 값만 반환하도록 설정
	UFUNCTION(BlueprintPure, Category="Item|Properties")
	float GetRotationSpeed() const;

	// C++에서 호출되지만 구현은 블루프린트에서 수행
	UFUNCTION(BlueprintImplementableEvent, Category="Item|Event")
	void OnItemPickedUP();

 

-> 헤더파일에 정의

 

#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();

	// 블루프린트에서 구현한 함수를 C++에서 호출함
	OnItemPickedUP();
}


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

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

// BlueprintCallable 함수 구현
void AItem::ResetActorPosition()
{
	// (0, 0, 0) 위치로 되돌립니다.
	SetActorLocation(FVector::ZeroVector); // ZeroVector를 쓰면 FVector 값이 원점으로 가게 됨!!
}

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

// 선언해둔 OnItemPickedUP은 굳이 내부에서 구현을 할 필요가 없음
// 위에 beginplay쪽에 호출만 해둠.

 

SetActorLocation(FVector::ZeroVector); 

-> ZeroVector를 쓰면 FVector 값이 원점으로 가게 됨!! 굳이 힘들게 0.0f 이런 거 안 써도 됨. 꿀팁!

 

 

Blueprint에서 함수 노드 사용

  • 이제 블루프린트 (BP_Item)의 Event Graph 창에서 우클릭한 뒤, 함수 이름을 검색하면 아래와 같이 노드가 노출됩니다.

 

  • ResetActorPosition → BlueprintCallable로 선언했으므로, 노드로 실행(Exec)할 수 있습니다.
  • GetRotationSpeed → BlueprintPure로 선언했으므로, 단순히 값만 반환하는 Getter 노드로 사용됩니다.
  • OnItemPickedUp → BlueprintImplementableEvent로 선언했으므로, 이벤트 그래프 안에서 구현한 내용을 C++에서 OnItemPickedUp()를 호출함으로써 실행할 수 있습니다.
  • 블루프린트에서 이벤트처럼 구현한 OnItemPickedUp()은 C++에선 함수 이름만 존재하고 실제 코드는 없습니다. 대신, Blueprint에서 이벤트 그래프를 통해 시각적 로직으로 구현해두면, C++에서 OnItemPickedUp()를 부르는 순간 그 이벤트가 실행됩니다.

+ Recent posts