TIL#49-Unreal C++[RE:] Actor와 Component 구조 이해하기

ㅎ12·2026년 8월 28일
post-thumbnail

📚 금일 학습 내용

  • Unreal C++의 Actor 기본 구조
  • C++ Actor 클래스 생성 및 Blueprint 연동
  • SceneComponent와 StaticMeshComponent 구성
  • CreateDefaultSubobject()를 활용한 Component 생성
  • SetupAttachment()를 활용한 Component 계층 구조 설정
  • ConstructorHelpers를 활용한 Static Mesh 적용
  • BeginPlay()와 Tick()의 실행 시점
  • DeltaTime을 활용한 프레임 독립적인 Actor 회전
  • Transform을 활용한 Actor 위치 및 회전 제어

1. 베이스 프로젝트 복습

: 본격적으로 Character, Item, GameState 등의 시스템을 구현하기 전 Unreal C++의 기본 구조를 복습하기 위해 간단한 Actor를 직접 생성했다.

이번 단계의 목표는 복잡한 게임 기능을 만드는 것보다,

C++로 생성한 클래스가 Unreal Editor의 Actor와 Component로 어떻게 연결되는지 이해하는 것이었다.

전체적인 구조는 다음과 같다.

C++ Actor Class
    ↓
Component 생성
    ↓
Component 계층 구조 설정
    ↓
Static Mesh 적용
    ↓
Level에 Actor 배치
    ↓
BeginPlay / Tick을 이용한 동작 구현

이 과정을 통해 이후 Character나 Item을 구현하기 전에 Unreal C++ 클래스의 가장 기본적인 동작 방식을 다시 확인했다.


2. Actor란?

: Unreal에서 Actor는 Level에 배치할 수 있는 객체의 기본 단위이다.

게임 속 아이템, 장애물, 문, 스폰 지점 등 World에 존재하는 다양한 객체들이 Actor를 기반으로 만들어질 수 있다.

이번에는 AActor를 상속받는 Item 클래스를 생성했다.

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

public:
    AItem();

protected:
    virtual void BeginPlay() override;

public:
    virtual void Tick(float DeltaTime) override;
};

여기서

public AActor

는 AItem 클래스가 Unreal의 AActor를 상속받는다는 의미이다.

따라서 Item 역시 Level에 배치할 수 있는 하나의 Actor가 된다.

AActor
   ↓ 상속
AItem
   ↓
Level에 배치 가능한 Actor

C++에서 단순히 클래스를 하나 만드는 것과 달리 Unreal에서는 어떤 Unreal 클래스를 상속받는지에 따라 해당 객체가 사용할 수 있는 기능과 역할이 달라진다는 점을 확인할 수 있었다.


3. Component를 이용해 Actor 구성하기

: Actor 자체는 게임 World에 존재할 수 있는 하나의 객체이지만, 실제 모습이나 기능은 여러 Component를 조합하여 구성할 수 있다.

이번 Item Actor에는 다음 Component를 사용했다.

Item (Actor)
│
└─ SceneRoot
      │
      └─ StaticMeshComp

SceneComponent

: SceneComponent는 위치, 회전, 크기와 같은 Transform 정보를 가질 수 있는 Component이다.

이번에는 Actor의 Root Component로 사용했다.

SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
RootComponent = SceneRoot;

RootComponent는 Actor에 포함된 다른 Component들이 Transform을 기준으로 삼을 수 있는 최상위 Component이다.

즉,

Actor
 ↓
RootComponent
 ↓
Child Components

형태의 기준점 역할을 한다.


4. StaticMeshComponent 추가하기

: Actor를 실제 화면에서 확인하기 위해 StaticMeshComponent를 추가했다.

StaticMeshComp =
    CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp"));

StaticMeshComponent는 3D Mesh를 Actor에 표시할 수 있도록 해주는 Component이다.

하지만 Component를 생성했다고 해서 자동으로 Root Component 아래에 연결되는 것은 아니다.

따라서 SetupAttachment()를 사용했다.

StaticMeshComp->SetupAttachment(SceneRoot);

결과적으로 Component 구조는 다음과 같이 구성된다.

Item
│
└─ SceneRoot
      │
      └─ StaticMeshComp

이 구조를 통해 SceneRoot의 Transform이 변경되면 자식인 StaticMeshComp도 함께 영향을 받는다.

이후 Character를 만들면서 Camera와 SpringArm을 연결할 때도 같은 Component 계층 구조를 사용하게 되었다.


5. CreateDefaultSubobject()

: 이번 과정에서 Component를 생성하기 위해 반복적으로 사용한 함수가 CreateDefaultSubobject()였다.

CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));

또는

CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp"));

와 같이 사용한다.

이를 구조적으로 보면 다음과 같다.

CreateDefaultSubobject
        ↓
Component 생성
        ↓
Actor의 기본 구성 요소로 등록

단순한 일반 C++ 객체를 생성하는 것이 아니라 Unreal의 객체 시스템에서 관리되는 Component를 Actor의 기본 구성 요소로 생성하는 것이라고 이해했다.

이후 Character 구현에서도

CreateDefaultSubobject<USpringArmComponent>()
CreateDefaultSubobject<UCameraComponent>()

형태로 동일한 방식을 사용하게 된다.

따라서 이번 Item Actor 구현은 이후 여러 Actor와 Character를 구성하는 기본 패턴을 익히는 과정이기도 했다.


6. ConstructorHelpers를 활용한 Mesh 적용

: StaticMeshComponent를 생성한 뒤 실제 Mesh Asset을 연결했다.

C++ Constructor에서 Unreal Asset을 불러오기 위해 ConstructorHelpers를 사용했다.

구조는 다음과 같다.

static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(
    TEXT("Asset Path")
);

if (MeshAsset.Succeeded())
{
    StaticMeshComp->SetStaticMesh(MeshAsset.Object);
}

FObjectFinder를 통해 지정된 경로의 Asset을 찾고,

MeshAsset.Succeeded()

를 통해 정상적으로 Asset을 찾았는지 확인한다.

Asset을 정상적으로 찾았다면

SetStaticMesh()

를 이용해 StaticMeshComponent에 적용한다.

Asset Path
    ↓
FObjectFinder
    ↓
Asset 검색
    ↓
Succeeded()
    ↓
SetStaticMesh()
    ↓
Actor에 Mesh 표시

이번 실습에서는 Chair Mesh를 적용해 C++로 만든 Actor가 Level에서 실제 Mesh를 가진 객체로 표시되는 것을 확인했다.


7. Constructor의 역할

: Component 생성과 초기 설정은 Item의 Constructor에서 진행했다.

AItem::AItem()
{
    PrimaryActorTick.bCanEverTick = true;

    SceneRoot =
        CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));

    RootComponent = SceneRoot;

    StaticMeshComp =
        CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp"));

    StaticMeshComp->SetupAttachment(SceneRoot);
}

Constructor에서는 Actor가 생성될 때 필요한 기본 구조와 초기값을 설정할 수 있다.

이번에는

Actor 생성
 ↓
SceneRoot 생성
 ↓
RootComponent 지정
 ↓
StaticMeshComponent 생성
 ↓
SceneRoot에 부착

순서로 기본 구조를 구성했다.

이후 Character 구현에서도 Constructor에서 Camera나 SpringArm Component를 생성하고 이동 속도의 초기값을 지정하는 등 동일한 패턴을 사용했다.


8. BeginPlay()

: BeginPlay()는 Actor가 실제 게임 플레이에 들어갔을 때 호출된다.

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

Constructor가 객체의 기본 구조를 구성하는 단계라면 BeginPlay()는 게임이 시작된 이후 필요한 초기 동작을 수행하는 단계로 볼 수 있다.

Constructor
→ Actor의 기본 구조 및 초기값 설정

BeginPlay
→ 실제 Gameplay가 시작될 때 한 번 실행

이번 실습에서는 BeginPlay()를 통해 Actor의 Transform을 변경하거나 로그를 출력하면서 실제 실행 시점을 확인했다.

또한

Super::BeginPlay();

를 호출하여 부모 클래스인 AActor의 BeginPlay()도 정상적으로 실행되도록 했다.


9. Tick()과 DeltaTime

: Tick()은 게임이 실행되는 동안 매 Frame 반복해서 호출될 수 있는 함수이다.

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

Tick을 사용하려면 Constructor에서 다음 설정이 활성화되어 있어야 한다.

PrimaryActorTick.bCanEverTick = true;

이번 실습에서는 Tick을 이용해 Actor가 계속 회전하도록 만들었다.

개념적인 흐름은 다음과 같다.

게임 실행

Frame 1 → Tick()
Frame 2 → Tick()
Frame 3 → Tick()
Frame 4 → Tick()
...

하지만 단순히 Frame마다 일정한 값을 더하면 PC의 Frame Rate에 따라 움직임의 속도가 달라질 수 있다.

이를 보정하기 위해 DeltaTime을 사용한다.

RotationSpeed * DeltaTime

DeltaTime은 이전 Frame에서 현재 Frame까지 걸린 시간을 의미한다.

따라서

회전량 = 회전 속도 × DeltaTime

형태로 계산하면 Frame Rate가 달라도 시간 기준으로 비교적 일정한 속도로 움직이게 만들 수 있다.

이 과정에서 DeltaTime이 단순히 Tick 함수에 전달되는 값이 아니라 프레임에 영향을 덜 받는 움직임을 구현하기 위해 필요한 값이라는 점을 다시 확인했다.


10. Transform을 이용한 Actor 제어

: Unreal에서 Actor의 상태를 구성하는 기본적인 Transform 요소는 다음 세 가지이다.

Location
→ 위치

Rotation
→ 회전

Scale
→ 크기

이번 실습에서는 Actor의 위치와 회전을 변경하면서 Transform이 실제 World의 Actor에 어떻게 적용되는지 확인했다.

특히 Tick과 Rotation을 함께 사용하면 다음과 같은 구조로 지속적인 회전을 만들 수 있다.

Tick
 ↓
현재 Rotation 확인
 ↓
DeltaTime을 반영한 회전값 계산
 ↓
Actor Rotation 변경
 ↓
다음 Frame에서 다시 Tick

이를 통해 C++ 코드에서 변경한 값이 Unreal Editor의 World에 존재하는 Actor의 움직임으로 이어지는 과정을 확인할 수 있었다.


11. Actor와 Component의 관계 정리

: 이번 실습에서 가장 중요했던 부분은 Actor와 Component의 역할을 구분하는 것이었다.

Actor
│
│  → Level에 배치되는 객체
│
└─ Component
    │
    ├─ SceneComponent
    │   → Transform 기준
    │
    └─ StaticMeshComponent
        → 실제 3D Mesh 표시

즉 Item이라는 Actor 자체에 모든 기능을 직접 넣는 것이 아니라 필요한 기능을 가진 Component를 추가하여 하나의 객체를 구성할 수 있다.

이 구조는 이후 Character에서도 그대로 확장된다.

SpartaCharacter
│
├─ Capsule
├─ Mesh
├─ CharacterMovement
├─ SpringArm
└─ Camera

결국 Item에서 SceneRoot + StaticMesh를 구성했던 것과 Character에서 여러 Component를 구성했던 것은 규모만 다를 뿐 Actor를 Component들의 조합으로 구성한다는 동일한 방식이었다.


12. 구현 결과

: 이번 단계에서는 C++로 생성한 Item Actor에 Root Component와 Static Mesh Component를 구성하고 실제 Mesh를 적용했다.

또한 BeginPlay()와 Tick()을 사용하여 Actor의 Transform을 변경하고 지속적인 회전 동작을 확인했다.

전체적인 구조를 정리하면 다음과 같다.

AActor
  ↓
AItem
  ↓
Constructor
  ├─ SceneRoot 생성
  ├─ StaticMeshComp 생성
  ├─ Component 연결
  └─ Mesh 적용
  ↓
BeginPlay
  └─ 게임 시작 시 초기 동작
  ↓
Tick
  └─ DeltaTime을 활용한 지속적인 동작

간단한 실습이었지만 이후 진행한 Character, Item System, Spawn System을 이해하기 위한 가장 기본적인 Unreal C++ 구조를 복습할 수 있었다.


(+)

AActor를 상속받아 World에 존재할 수 있는 객체를 만들고, 그 안에 SceneComponent, StaticMeshComponent와 같은 Component를 조합하여 실제 게임에서 필요한 형태와 기능을 구성할 수 있었다.

특히

Actor
→ 게임 World에 존재하는 객체

Component
→ Actor를 구성하는 기능 단위

RootComponent
→ Component Transform의 기준

Constructor
→ 기본 구조와 초기값 설정

BeginPlay
→ Gameplay 시작 시 실행

Tick
→ Gameplay 중 Frame마다 반복 실행

으로 각 요소의 역할을 구분해서 정리할 수 있었다.

처음에는 CreateDefaultSubobject(), SetupAttachment(), RootComponent 등이 각각 별개의 코드처럼 느껴졌지만, 다시 살펴보니 결국 Actor 내부에 Component 계층 구조를 만드는 하나의 과정이었다.

또한 Tick()과 DeltaTime을 이용한 회전 구현을 통해 C++에서 계산한 값이 실제 World에 존재하는 Actor의 움직임으로 연결되는 과정도 확인할 수 있었다.

이후에는 이 기본 Actor 구조에서 한 단계 더 나아가 ACharacter를 기반으로 캐릭터를 구성하고, PlayerController와 Enhanced Input을 연결하여 플레이어가 직접 조작할 수 있는 Character를 구현하는 과정으로 확장했다.

0개의 댓글