[DAY35] Unreal Engine C++ 3D Game Dev(1)

베리투스·2025년 9월 22일

TIL: Today I Learned

목록 보기
36/93

오늘은 언리얼 엔진 C++의 핵심인 액터(Actor)에 대해 깊이 파고들었다. 단순히 C++ 클래스를 생성하는 것에서 그치지 않고, 눈에 보이도록 컴포넌트(Component)를 추가하고, 액터의 생성부터 소멸까지의 과정인 라이프사이클(Lifecycle)을 이해했다. 마지막으로 Tick 함수델타 타임(Delta Time)을 활용해 액터를 부드럽게 회전시키는 방법까지 실습하며 액터의 전반적인 흐름을 익혔다. 🤩


📌 목표

  • 언리얼 액터(Actor) 클래스 생성 및 삭제 방법 이해하기
  • 액터에 루트 컴포넌트스태틱 메시 컴포넌트를 추가하여 월드에 보이게 만들기
  • 액터의 라이프사이클 주요 함수(생성자, BeginPlay, Tick 등)의 역할과 호출 순서 파악하기
  • Tick 함수와 델타 타임을 이용해 프레임에 독립적인 움직임 구현하기

📖 이론

1. 액터(Actor)와 컴포넌트(Component)

언리얼 엔진에서 월드(레벨)에 배치할 수 있는 모든 오브젝트를 "액터(Actor)"라고 한다. 캐릭터, 아이템, 건축물 모두 액터다. 하지만 C++로 액터 클래스를 처음 생성하고 월드에 배치하면 투명하고, 위치도 월드 원점(0,0,0)에 고정되어 제대로 제어할 수 없다.

이유는 액터가 '껍데기'에 불과하기 때문이다. 👽 실제 기능과 형태는 "컴포넌트(Component)"라는 부품을 조립해서 완성된다.

  • 루트 컴포넌트 (Root Component): 모든 컴포넌트의 기준점이자, 액터의 트랜스폼(Transform: 위치, 회전, 크기) 정보를 가지는 가장 중요한 부품이다. 이게 없으면 액터는 좌표를 가질 수 없다.
  • 스태틱 메시 컴포넌트 (Static Mesh Component): 액터의 3D 모델(외형)을 담당하는 부품이다.

이 부품들을 C++ 코드로 직접 조립하는 과정을 진행했다.

2. 액터 라이프사이클 (Actor Lifecycle)

액터는 사람처럼 태어나고, 살아가고, 죽는 생명 주기(Lifecycle)를 가진다. 각 단계마다 특정 함수들이 자동으로 호출된다.

  • 탄생 (Spawning)
    1. 생성자 (Constructor): 메모리에 생성될 때 단 한 번 호출. 컴포넌트를 생성하고 부착하는 일을 한다.
    2. BeginPlay(): 액터가 월드에 완전히 배치되고 게임이 시작될 때 호출. 다른 액터와 상호작용하는 로직을 넣기 좋다.
  • 활동 (Ticking)
    • Tick(): 게임이 실행되는 동안 매 프레임마다 호출된다. 캐릭터 이동, 회전처럼 지속적인 업데이트가 필요할 때 사용한다. (성능 저하의 주범이 될 수 있어 조심해야 한다! ⚠️)
  • 죽음 (Destroying)
    • EndPlay(): 액터가 월드에서 사라지는 모든 경우(파괴, 레벨 변경 등)에 호출. 마무리 작업을 하기에 적합하다.

이 순서를 UE_LOG로 직접 찍어보니 개념이 확실하게 잡혔다.

3. 델타 타임 (Delta Time)

Tick 함수는 컴퓨터 성능에 따라 1초에 호출되는 횟수(FPS)가 다르다. 좋은 컴퓨터는 1초에 120번, 안 좋은 컴퓨터는 30번 호출될 수 있다. 만약 Tick 함수에서 매번 1도씩 회전시키면, 누구는 1초에 120도를, 누구는 30도를 돌게 된다.

델타 타임(DeltaTime)은 바로 이전 프레임부터 현재 프레임까지 걸린 '시간'이다. 이 값을 곱해주면 프레임이 아니라 '시간'에 기반하여 계산되므로, 모든 컴퓨터에서 동일한 속도로 움직이게 된다. 움직임을 구현할 때 델타 타임은 필수다!


💻 코드

Item.h

#pragma once

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

// 헤더 파일에서는 using namespace std; 사용을 지양하는 것이 좋다고 배웠다.

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

protected:
	// Actor Lifecycle 함수 2. 게임 시작 시 호출됨.
	virtual void BeginPlay() override;

public:	
	// Actor Lifecycle 함수 3. 매 프레임 호출됨.
	virtual void Tick(float DeltaTime) override;

	// 컴포넌트를 가리킬 포인터 변수.
	// UPROPERTY 매크로는 언리얼 리플렉션 시스템이 이 변수를 인식하게 함.
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	USceneComponent* SceneRoot;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	UStaticMeshComponent* Mesh;

private:
	// Tick에서 사용할 회전 속도 변수
	UPROPERTY(EditAnywhere, Category="Movement")
	FRotator RotationSpeed;
};

Item.cpp

#include "Item.h"
#include "Components/StaticMeshComponent.h"

using namespace std; // cpp 파일에서는 편의상 사용

// Actor Lifecycle 함수 1. 액터가 메모리에 생성될 때 가장 먼저 호출됨.
AItem::AItem()
{
 	// Tick 함수를 매 프레임 호출하도록 설정. 성능을 위해 필요없으면 꺼야 함.
	PrimaryActorTick.bCanEverTick = true;

	// 1. SceneComponent를 생성해서 루트 컴포넌트로 지정. (좌표계를 갖게 됨)
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
	SetRootComponent(SceneRoot);

	// 2. StaticMeshComponent를 생성하고 루트 컴포넌트에 자식으로 붙임.
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	Mesh->SetupAttachment(GetRootComponent());

	// 3. C++ 코드로 에셋(3D 모델)을 찾아와서 메시에 할당.
	// ConstructorHelpers는 생성자에서만 사용 가능!
	static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/ArtResource/Props/SM_Sword.SM_Sword"));
	if (MeshAsset.Succeeded())
	{
		Mesh->SetStaticMesh(MeshAsset.Object);
	}

	// 4. Tick에서 사용할 회전 속도 값 초기화 (1초에 Y축으로 90도 회전)
	RotationSpeed = FRotator(0.f, 90.f, 0.f);
}

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

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

	// 5. 매 프레임마다 액터를 회전시킨다.
	// RotationSpeed가 0에 가까운 값이 아닐 때만 회전시켜서 불필요한 연산을 줄임 (최적화)
	if (!RotationSpeed.IsNearlyZero())
	{
		// DeltaTime을 곱해줘야 어떤 컴퓨터(프레임)에서도 동일한 속도로 회전한다. 이게 핵심!
		AddActorLocalRotation(RotationSpeed * DeltaTime);
	}
}

⚠️ 실수

  • 에디터를 켜놓고 빌드하기: 언리얼 에디터를 끄지 않고 Visual Studio에서 빌드(Ctrl+Shift+B)를 돌렸다가 계속 ...dll 파일에 접근할 수 없다는 에러가 발생했다. 빌드할 땐 반드시 에디터를 종료하는 습관을 들여야 한다.
  • 델타 타임(DeltaTime) 빼먹기: 처음 회전 로직을 짤 때 DeltaTime 곱하는 걸 잊었다. 내 컴퓨터에서는 적당히 도는 것 같았는데, 프레임이 다른 환경에서는 엄청나게 빨리 돌아서 깜짝 놀랐다. 움직임에는 DeltaTime이 필수라는 걸 뼈저리게 느꼈다.
  • C++ 클래스 파일만 삭제하기: 필요 없어진 C++ 클래스를 지울 때, Visual Studio 솔루션 탐색기에서만 Remove 했다. 하지만 실제 파일은 폴더에 그대로 남아있어서 계속 빌드 에러가 발생했다. 솔루션에서 제거 -> 실제 폴더에서 파일 삭제 -> .uproject 우클릭 -> Generate... 순서를 꼭 지켜야 한다.

✅ 핵심 요약

개념설명비고
액터 (Actor)언리얼 월드에 배치할 수 있는 모든 객체의 기본 단위.캐릭터, 아이템, 이펙트 등 눈에 보이는 대부분의 것.
컴포넌트 (Component)액터에 부착하여 특정 기능(외형, 소리, 충돌 등)을 부여하는 부품.레고 블록처럼 조립해서 액터를 완성한다.
루트 컴포넌트 (Root Comp)액터의 기준점이자 트랜스폼(위치/회전/크기) 정보를 담당하는 핵심 컴포넌트.SetRootComponent() 함수로 지정해야 한다.
라이프사이클 (Lifecycle)액터가 생성되고 소멸되기까지의 전 과정. 각 단계별로 특정 함수가 호출된다.생성자, BeginPlay, Tick, EndPlay 등.
Tick 함수매 프레임마다 호출되는 함수로, 지속적인 업데이트가 필요할 때 사용한다.성능에 영향을 많이 주므로 꼭 필요할 때만 bCanEverTick = true로 설정.
델타 타임 (Delta Time)한 프레임이 실행되는 데 걸린 시간. 프레임에 독립적인 로직을 만들 때 필수.Tick 함수에서 움직임을 구현할 땐 반드시 곱해줘야 한다.
profile
Shin Ji Yong // Unreal Engine 5 공부중입니다~

0개의 댓글