Actor 기본 구조SceneComponent와 StaticMeshComponent 구성CreateDefaultSubobject()를 활용한 Component 생성SetupAttachment()를 활용한 Component 계층 구조 설정ConstructorHelpers를 활용한 Static Mesh 적용BeginPlay()와 Tick()의 실행 시점DeltaTime을 활용한 프레임 독립적인 Actor 회전: 본격적으로 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++ 클래스의 가장 기본적인 동작 방식을 다시 확인했다.
: 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 클래스를 상속받는지에 따라 해당 객체가 사용할 수 있는 기능과 역할이 달라진다는 점을 확인할 수 있었다.
: Actor 자체는 게임 World에 존재할 수 있는 하나의 객체이지만, 실제 모습이나 기능은 여러 Component를 조합하여 구성할 수 있다.
이번 Item Actor에는 다음 Component를 사용했다.
Item (Actor)
│
└─ SceneRoot
│
└─ StaticMeshComp
: SceneComponent는 위치, 회전, 크기와 같은 Transform 정보를 가질 수 있는 Component이다.
이번에는 Actor의 Root Component로 사용했다.
SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
RootComponent = SceneRoot;
RootComponent는 Actor에 포함된 다른 Component들이 Transform을 기준으로 삼을 수 있는 최상위 Component이다.
즉,
Actor
↓
RootComponent
↓
Child Components
형태의 기준점 역할을 한다.
: 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 계층 구조를 사용하게 되었다.
: 이번 과정에서 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를 구성하는 기본 패턴을 익히는 과정이기도 했다.
: 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를 가진 객체로 표시되는 것을 확인했다.
: 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를 생성하고 이동 속도의 초기값을 지정하는 등 동일한 패턴을 사용했다.
: BeginPlay()는 Actor가 실제 게임 플레이에 들어갔을 때 호출된다.
void AItem::BeginPlay()
{
Super::BeginPlay();
}
Constructor가 객체의 기본 구조를 구성하는 단계라면 BeginPlay()는 게임이 시작된 이후 필요한 초기 동작을 수행하는 단계로 볼 수 있다.
Constructor
→ Actor의 기본 구조 및 초기값 설정
BeginPlay
→ 실제 Gameplay가 시작될 때 한 번 실행
이번 실습에서는 BeginPlay()를 통해 Actor의 Transform을 변경하거나 로그를 출력하면서 실제 실행 시점을 확인했다.
또한
Super::BeginPlay();
를 호출하여 부모 클래스인 AActor의 BeginPlay()도 정상적으로 실행되도록 했다.
: 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 함수에 전달되는 값이 아니라 프레임에 영향을 덜 받는 움직임을 구현하기 위해 필요한 값이라는 점을 다시 확인했다.
: Unreal에서 Actor의 상태를 구성하는 기본적인 Transform 요소는 다음 세 가지이다.
Location
→ 위치
Rotation
→ 회전
Scale
→ 크기
이번 실습에서는 Actor의 위치와 회전을 변경하면서 Transform이 실제 World의 Actor에 어떻게 적용되는지 확인했다.
특히 Tick과 Rotation을 함께 사용하면 다음과 같은 구조로 지속적인 회전을 만들 수 있다.
Tick
↓
현재 Rotation 확인
↓
DeltaTime을 반영한 회전값 계산
↓
Actor Rotation 변경
↓
다음 Frame에서 다시 Tick
이를 통해 C++ 코드에서 변경한 값이 Unreal Editor의 World에 존재하는 Actor의 움직임으로 이어지는 과정을 확인할 수 있었다.
: 이번 실습에서 가장 중요했던 부분은 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들의 조합으로 구성한다는 동일한 방식이었다.
: 이번 단계에서는 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를 구현하는 과정으로 확장했다.