Tick함수, 리플렉션 시스템 활용하기

주상돈·2025년 1월 22일

TIL

목록 보기
19/53

Transform 속성 이해하기


1️⃣ Actor와 Transform

  • Actor (액터)는 언리얼 엔진에서 게임 세계(월드)에 존재하는 모든 오브젝트를 의미한다. 건물, 캐릭터, 아이템 등 눈에 보이는 모든 것들이 액터로 표현된다.

  • 액터에는 세 가지 중요한 속성이 있다.
    1. 위치(Location)
    2. 회전(Rotation)
    3. 크기(Scale)
    - 이 세 가지 속성을 Transform이라고 부르며, 뷰포트 상에서 보통 x축 (빨간색), y축 (초록색), z축 (파란색)으로 표시된다.

  • 위치 (Location)

    • 액터가 월드에서 어느 지점에 있는지를 나타낸다.
    • 예) FVector(100.0f, 200.0f, 300.0f)라면 월드의 x축으로 100, y축으로 200, z축으로 300만큼 떨어진 위치를 의미한다.
  • 회전 (Rotation)

    • 액터가 어느 방향을 바라보는지, 어떤 각도로 기울어져 있는지를 나타낸다.
    • 언리얼 C++에서는 주로 FRotator(Pitch, Yaw, Roll) 형태로 표현한다.
      • Roll: 좌우로 기울어지는 회전 (x축을 축으로 하는 회전)
      • Pitch: 앞뒤 방향의 기울어짐 (y축을 축으로 하는 회전)
      • Yaw: 좌우 방향 회전 (z축을 축으로 하는 회전)
  • 스케일 (Scale)

    • 액터의 크기 비율이다. FVector(X, Y, Z) 형태로 표현되며, 예) FVector(2.0f, 2.0f, 2.0f)는 기본 크기의 2배를 의미한다.

2️⃣ FTransform 자료형

  • 언리얼 엔진에는 위치, 회전, 스케일을 하나로 묶어 효율적으로 관리하기 위한 구조체인 FTransform이 있다.
  • FTransform은 내부적으로 다음 세 요소를 보관한다.
    • Translation: 위치를 표현하는 FVector
    • Rotation: 회전을 표현하는 FRotator
    • Scale3D: 스케일을 표현하는 FVector
  • FTransform을 활용하면 위치·회전·스케일을 한 번에 다룰 수 있어, Transform 관련 연산을 보다 편리하게 처리할 수 있다.

3️⃣ 좌표계의 개념

  • 월드 좌표계 (World Space)
    • 게임 전체 세계를 기준으로 한 절대적인 좌표계이다.
    • SetActorLocation(), GetActorLocation()처럼 액터 자체를 이동·회전·스케일할 때 대부분 월드 좌표계를 기준으로 한다.
  • 로컬 좌표계 (Local Space)
    • 액터 자신이나 부모 액터 (또는 부모 컴포넌트)의 Transform을 기준으로 한 상대적인 좌표계이다.
    • 계층 구조 (부모-자식 관계)가 있는 경우, 자식은 부모의 Transform에 종속되어 움직인다.

4️⃣ 부모-자식 컴포넌트 관계

  • 액터에는 여러 컴포넌트가 붙을 수 있으며, 최상위에 있는 루트 컴포넌트 (Root Component)를 기준으로 다른 컴포넌트들이 Attach (부착) 관계를 맺을 수 있다.
  • 부모 액터 (또는 부모 컴포넌트)가 이동·회전·스케일되면 자식들은 상대 좌표값에 따라 함께 이동한다.
  • 부모-자식 관계가 맺어져 있다면,
    • GetRelativeTransform(): 부모 기준의 상대 위치·회전·스케일을 가져옴
    • SetRelativeLocation(), SetRelativeRotation(): 부모 기준으로 자식의 위치·회전을 조정
  • 이처럼 로컬 좌표계를 적절히 활용하면, 여러 컴포넌트를 한꺼번에 움직이거나, 특정 컴포넌트만 부모 기준으로 움직이게 만들 수 있다.

C++ 코드로 Transform 다루기


1️⃣ Transform 조정 함수

  • 언리얼 에디터에서 액터를 선택하면 3D Gizmo(화살표, 회전 핸들, 스케일 핸들)을 마우스로 직접 움직여서 위치·회전·스케일을 조정할 수 있다.

    • 단축키 W: 이동(Translate)
    • 단축키 E: 회전(Rotate)
    • 단축키 R: 스케일(Scale)
  • 게임 로직에서 액터의 Transform을 변경하고 싶다면, C++ 또는 블루프린트 함수를 사용합니다. 대표적인 함수들은 아래와 같다.

    • SetActorLocation(FVector NewLocation): 액터 위치 이동
    • SetActorRotation(FRotator NewRotation): 액터 회전
    • SetActorScale3D(FVector NewScale): 액터 스케일 변경
    • GetActorLocation(), GetActorRotation(), GetActorScale3D(): 현재 Transform 정보 가져오기
    • SetActorTransform(FTransform NewTransform): 위치·회전·스케일을 한 번에 설정

2️⃣ BeginPlay() 함수에서 Transform 변경

  • 현재 임시로 배치된 액터의 Transform 위치를 확인해보자.

    아래는 Item 액터를 예로 들어, 액터가 생성된 직후(BeginPlay())에 특정 위치, 회전, 스케일을 설정하는 코드이다.
#pragma once

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

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

public:
    AItem();

protected:
		USceneComponent* SceneRoot;
		UStaticMeshComponent* StaticMeshComp;

    virtual void BeginPlay() override;
};

Item.h

#include "AItem.h"

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

		StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
		StaticMeshComp->SetupAttachment(SceneRoot);
		
		static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/Resources/Props/SM_Chair.SM_Chair"));
		if (MeshAsset.Succeeded())
		{
			StaticMeshComp->SetStaticMesh(MeshAsset.Object);
		}
		
		static ConstructorHelpers::FObjectFinder<UMaterial> MaterialAsset(TEXT("/Game/Resources/Materials/M_Metal_Gold.M_Metal_Gold"));
		if (MaterialAsset.Succeeded())
		{
			StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
		}
}

void AItem::BeginPlay()
{
    Super::BeginPlay();
        
    // 위치, 회전, 스케일 설정하기
    // (300, 200, 100) 위치로 이동
    SetActorLocation(FVector(300.0f, 200.0f, 100.0f));
    // Yaw 방향으로 45도 회전
    SetActorRotation(FRotator(0.0f, 45.0f, 0.0f));
    // 모든 축을 2배로 스케일
    SetActorScale3D(FVector(2.0f));
}

Item.cpp

  • BeginPlay()는 액터가 월드에 배치된 후 초기화가 끝나면 자동으로 호출됩니다. 여기서 SetActorLocation 등으로 Transform을 바꾸면 게임 시작과 동시에 적용된다.
  • SetActorScale3D(FVector(2.0f))와 같이 스케일 값을 통일하면, 모든 축 (x, y, z)이 같은 비율로 확대·축소된다.
  • 빌드 후 언리얼 에디터에서 플레이하면, Outliner → Item 액터 Details 패널에서 새로운 Transform이 적용된 것을 확인할 수 있다.

참고
위 코드를 이렇게 쓸 수도 있다.

FVector NewLocation(300.0f, 200.0f, 100.0f);
FRotator NewRotation(0.0f, 90.0f, 0.0f);
FVector NewScale(2.0f);
FTransform NewTransform(NewRotation, NewLocation, NewScale);
SetActorTransform(NewTransform);

3. Tick 함수와 프레임 독립적인 로직 이해하기


1️⃣ 게임 프레임 업데이트와 Tick

  • 언리얼 엔진은 게임 실행 중 매 프레임마다 여러 작업을 수행한다.
    1. 렌더링(Rendering): 화면 그리기 (일반적으로 1초에 60프레임, 120프레임 등)
    2. 물리 연산: 충돌·중력·마찰 등 물리 엔진 처리
    3. 오브젝트 업데이트: 게임 내 액터들의 상태 갱신
  • 특정 액터가 “매 프레임마다” 수행할 로직이 있다면, 언리얼 엔진은 그 액터의 Tick(float DeltaTime) 함수를 매 프레임 호출해준다.

2️⃣ Tick 함수 활성화하기

  • 액터를 생성할 때 기본적으로 Tick() 함수를 사용하려면, 아래와 같이 생성자에서 설정해야 한다.
PrimaryActorTick.bCanEverTick = true;
  • bCanEverTickfalse면 엔진은 성능 최적화를 위해 해당 액터의 Tick을 호출하지 않는다.
  • 불필요한 Tick 호출은 성능에 부담을 줄 수 있으므로, 사용하지 않는다면 false를 명시해주어 최적화 효과와 코드 명확성을 높이자.

3️⃣ DeltaTime이란?

  • Tick(float DeltaTime) 함수에서 DeltaTime은 “직전 프레임부터 현재 프레임까지 걸린 시간(초)”입니다.
    • 60 FPS 경: DeltaTime ≈ 1/60초 ≈ 0.0167초
    • 120 FPS 환경: DeltaTime ≈ 1/120초 ≈ 0.0083초
  • 프레임 레이트가 높을수록 DeltaTime이 작아지고, 낮을수록 DeltaTime이 커집니다.

4️⃣ DeltaTime을 활용한 프레임 독립적인 움직임

  • 단순히 “매 프레임마다 X 좌표를 1씩 증가”시키면, FPS가 높을수록 더 빨리 움직여 게임 체감 속도가 달라진다.
  • 이를 방지하려면, DeltaTime을 곱해서 초 단위 기준으로 이동·회전을 계산해야 한다.
    • 예) 초당 100만큼 이동하고 싶다면: 100 * DeltaTime을 매 프레임마다 더해줌
      • 60 FPS → 한 프레임당 1.67씩 이동, 60 프레임 곱하면 100
      • 120 FPS → 한 프레임당 0.83씩 이동, 120 프레임 곱하면 100
    • 이처럼 어느 FPS 환경에서도 동일한 실제 속도를 유지할 수 있다.

Tick 함수와 DeltaTime을 활용한 회전 구현하기

1️⃣ 회전 속도 변수와 Tick 함수 선언

  • 이동뿐 아니라 회전도 마찬가지로 DeltaTime을 곱해주면, 초당 일정 각도로 회전하는 것을 쉽게 구현할 수 있다.
  • Item 클래스에 회전 속도 변수를 추가해보자.
#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;
		
		// 회전 속도를 나타내는 변수(초당 도(degrees) 단위)
		float RotationSpeed;
		
		virtual void BeginPlay() override;
		virtual void Tick(float DeltaTime) override;
};

Item.h

#include "Item.h"

AItem::AItem()
{
		SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
		SetRootComponent(SceneRoot);
	
		StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
		StaticMeshComp->SetupAttachment(SceneRoot);
	
		static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/Resources/Props/SM_Chair.SM_Chair"));
		if (MeshAsset.Succeeded())
		{
			StaticMeshComp->SetStaticMesh(MeshAsset.Object);
		}
	
		static ConstructorHelpers::FObjectFinder<UMaterial> MaterialAsset(TEXT("/Game/Resources/Materials/M_Metal_Gold.M_Metal_Gold"));
		if (MaterialAsset.Succeeded())
		{
			StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
		}

		// Tick 함수를 켜줍니다.
		PrimaryActorTick.bCanEverTick = true;	
		// 기본 회전 속도 (1초에 90도 회전)
		RotationSpeed = 90.0f;
}

void AItem::BeginPlay()
{
		Super::BeginPlay();
	
		SetActorLocation(FVector(300.0f, 200.0f, 100.0f));
		SetActorRotation(FRotator(0.0f, 90.0f, 0.0f));
		SetActorScale3D(FVector(2.0f));
}

void AItem::Tick(float DeltaTime)
{
		Super::Tick(DeltaTime);
		
		// RotationSpeed가 0이 아니라면 회전 처리
		if (!FMath::IsNearlyZero(RotationSpeed))
		{
	    // 초당 RotationSpeed만큼, 한 프레임당 (RotationSpeed * DeltaTime)만큼 회전
			AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaTime, 0.0f));
		}
}

Item.cpp

  • FMath::IsNearlyZero는 부동소수점 비교에서 안전하게 0에 가까운지 확인해주는 함수이다.
  • RotationSpeed = 360.0f면 1초에 한 바퀴(360도) 회전, 180.0f면 2초에 한 바퀴 회전하게 된다.
  • AddActorLocalRotation()은 액터의 로컬 기준으로 회전을 추가해주는 함수이다.
    • 만약 월드 좌표 기준으로 회전하고 싶다면 AddActorWorldRotation()을 사용할 수도 있다.

3️⃣ 회전 테스트

  • RotationSpeed 값에 따라 회전 속도가 달라지는 것을 테스트 해볼 수도 있다.

    사진을 보면 Yaw의 값이 변하는 것을 볼 수 있다.
  • 만약 PitchRoll 방향 회전을 주고 싶다면, FRotator(Pitch, Yaw, Roll) 부분을 적절히 수정하면 된다.
  • 이 원리를 이동 (Translate)에도 동일하게 적용할 수 있다. 예를 들어, AddActorWorldOffset()DeltaTime을 곱해주면 초당 이동 거리를 일정하게 만들 수 있다.

리플렉션 시스템 이해하기


1️⃣ Blueprint (시각적 스크립팅) 이해

  • Blueprint는 언리얼 엔진에서 제공하는 시각적 스크립팅 도구로, 노드 (블록)를 연결하여 게임 로직을 작성한다.
  • 장점
    • Blueprint 그래프를 수정 후, 에디터에서 Play 버튼을 누르면 곧바로 결과를 확인할 수 있습니다. 아이디어를 빠르게 검증하고 반복할 때 큰 장점이 된다.
    • “레고 블록”을 쌓듯이 노드를 연결해 로직을 작성하므로, 프로그래밍 언어에 익숙하지 않은 초급자도 쉽게 접근할 수 있다.
  • 한계점
    • 노드 수가 많아질수록 그래프가 복잡해져, 가독성과 유지보수가 어려워질 수 있다.
    • C++과 비교했을 때, Blueprint는 내부적으로 추가 해석 과정을 거칩니다. 따라서 물리 연산이나 AI 같은 높은 성능이 필요한 시스템에서는 병목이 될 수 있다.

2️⃣ C++ (네이티브 코드 프로그래밍) 이해

  • 장점
    • 엔진 코어까지 직접 수정 가능하며, 복잡하고 성능이 중요한 게임 로직을 빠르고 최적화된 방식으로 구현할 수 있다.
    • 표준 라이브러리와 외부 라이브러리를 자유롭게 사용할 수 있어, 대규모 프로젝트에 적합하다.
    • 포인터, 템플릿 같은 C++ 언어적 기능을 통해 메모리와 로직을 정교하게 다룰 수 있다.
  • 한계점
    • C++ 코드를 수정하면 에디터를 재시작하거나 Live Coding을 다시 컴파일해야 하므로, 반복 작업 시 조금 번거로울 수 있다.

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

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

4️⃣ 리플렉션 (Reflection)이란?

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

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


1️⃣ **Item 클래스 다시보기**

  • AItem 클래스의 헤더(Item.h)입니다. 이미 클래스 선언에 리플렉션 관련 매크로가 포함되어 있다.
#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;

};

Item.h

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

2️⃣ **UCLASS() 매크로의 주요 지정자**

  • UCLASS() 매크로는 클래스를 리플렉션 시스템에 등록하면서, 추가적으로 몇 가지 옵션 (지정자)을 설정할 수 있다.

  • 기본 동작
    - 만약 UCLASS()에 옵션을 주지 않으면, 블루프린트에서 상속이 가능하고 변수로 참조가 가능한 형태로 등록된다. 즉, 내부적으로 Blueprintable, BlueprintType과 동일한 효과를 가지게 된다.

    UCLASS(Blueprintable, BlueprintType) = UCLASS()이다

  • 주요 옵션

    • Blueprintable
      • 블루프린트에서 상속 가능한 클래스로 만든다.
    • NotBlueprintable
      • 블루프린트에서 이 클래스를 상속할 수 없도록 한다.
    • BlueprintType
      • 블루프린트에서 변수나 참조로 사용할 수 있게 한다.
      • 이 옵션만 있으면, 상속은 허용되지 않고 참조만 가능하다.
    • 필요에 따라 이 지정자들을 조합해 클래스가 어떻게 블루프린트와 상호작용해야 할지 명시할 수 있다.


변수에 리플렉션 적용하기


1️⃣ **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에 노출되지는 않는다.
    • “엔진이 변수의 존재는 알고 있지만, 외부에서는 보이지 않게 숨겨둔 상태”라고 볼 수 있다.
    • 리플렉션에 등록만 되어 있어도 가비지 컬렉션(메모리 관리)과 직렬화(세이브/로드) 같은 엔진 내부 기능이 작동할 수 있다.

2️⃣ Item 클래스 변수 리플렉션 등록

  • Item 클래스 변수들에 대해 UPROPERTY() 매크로를 적용한 모습이다.
#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:
		// 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;
};

item.h

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

Item.cpp

3️⃣ Blueprint에서 C++ 변수들 조정

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

    위와 같이 Mesh나 Material속성을 바꿀 수 있다.

함수에 리플렉션 적용하기


1️⃣ 함수 리플렉션이란?

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

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

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

3️⃣ Item 클래스 함수 리플렉션 등록

  • Item 클래스에 여러 함수를 정의하고, UFUNCTION() 매크로를 통해 각각 다른 방식으로 블루프린트에 노출한 코드이다.
#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:
		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;
		
		// 함수를 블루프린트에서 호출 가능하도록 설정
	  UFUNCTION(BlueprintCallable, Category="Item|Actions")
	  void ResetActorPosition();
		
		// 블루프린트에서 값만 반환하도록 설정
    UFUNCTION(BlueprintPure, Category = "Item|Properties")
    float GetRotationSpeed() const;

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

Item.h

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

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

Item.cpp

4️⃣ Blueprint에서 함수 노드 사용

  • 이제 블루프린트 (BP_Item)의 Event Graph 창에서 우클릭한 뒤, 함수 이름을 검색하면 아래와 같이 노드가 노출된다.
  • ResetActorPositionBlueprintCallable로 선언했으므로, 노드로 실행(Exec)할 수 있다.
  • GetRotationSpeedBlueprintPure로 선언했으므로, 단순히 값만 반환하는 Getter 노드로 사용된다.
  • OnItemPickedUpBlueprintImplementableEvent로 선언했으므로, 이벤트 그래프 안에서 구현한 내용을 C++에서 OnItemPickedUp()를 호출함으로써 실행할 수 있다.
  • 블루프린트에서 이벤트처럼 구현한 OnItemPickedUp은 C++에선 함수 이름만 존재하고 실제 코드는 없다. 대신, Blueprint에서 이벤트 그래프를 통해 시각적 로직으로 구현해두면, C++에서 OnItemPickedUp()를 부르는 순간 그 이벤트가 실행된다.

0개의 댓글