본문 바로가기

[C++과 Unreal Engine으로 3D 게임 개발] Tick 함수로 Transform 조정

@iamrain2025. 9. 17. 09:59

Transform 속성

Actor와 Transform

Actor는 언리얼 엔진에서 월드에 존재하는 모든 오브젝트를 의미한다.

액터에는 중요한 세 가지 속성이 있는데, 이 세 가지 속성을 Transform이라고 부르고, 뷰포트 상에서 x축, y축, z축로 표시된다.

  • 위치 Location
    • 액터가 월드의 어느 지점에 있는지를 나타낸다.
  • 회전 Rotation
    • 액터가 어느 방향을 바라보는지, 어떤 각도로 기울어 있는지 나타낸다.
    • 언리얼 C++에서는 주로 `FRotator(Pitch, Yaw, Roll)` 형태로 표현한다.
      • Pitch : 앞뒤 방향의 기울어짐 (y축 기준)
      • Yaw : 좌우 방향 회전 (z축 기준)
      • Roll : 좌우로 기울어짐 (x축 기준)
  • 스케일 Scale
    • 액터의 크기 비율. `FVector(X, Y, Z)` 형태로 표현된다.

FTransform 자료형

Transform을 하나로 묶어 효율적으로 관리하기 위한 구조체.

내부적으로 다음 세 요소를 보관한다.

  • Translation : 위치를 표현
  • Rotation : 회전을 표현
  • Scale 3D : 스케일을 표현

좌표계의 개념

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

부모-자식 컴포넌트 관계

액터에는 여러 컴포넌트가 붙을 수 있으며, 최상위에 있는 루트 컴포넌트(Root Component)를 기준으로 다른 컴포넌트들이 부착(Attach) 관계를 맺을 수 있다.

부모 액터(또는 부모 컴포넌트)가 이동하거나 회전, 스케일이 변경되면 자식들 또한 상대 좌표값에 따라 변경된다.

 

부모-자식 관계가 맺어져 있다면

  • `GetRelativeTransform()` : 부모 기준의 상대 위치 • 회전 • 스케일을 가져온다.
  • `SetRelativeLocation()`, `SetRelativeRotation()` : 부모 기준으로 자식의 위치 • 회전을 조정한다.

 

C++ 코드로 Transform 다루기

Transform 조정 함수

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

`Begin()` 함수에서 Transform 변경

`.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;

	virtual void BeginPlay() override;
};

`.cpp`

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

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

	SetActorLocation(FVector(300.0f, 200.0f, 100.0f));
	SetActorRotation(FRotator(0.0f, 45.0f, 0.0f));
	SetActorScale3D(FVector(2.0f));
}

실행 전 무작위로 배치한 Actor의 Transform
실행 후 코드에 설정된 값으로 변경된 Transform

 

Tick 함수와 프레임 독립적인 로직

게임 프레임 업데이트와 Tick

특정 액터가 "매 프레임마다" 수행할 로직이 있으면 → `Tick(float DeltaTime)`을 매 프레임 호출.

Tick 함수 활성화

`PrimaryActorTick.bCanEverTick = true;`를 생성자에서 설정해야 `Tick()` 함수를 사용할 수 있다.

DeltaTime

직전 프레임부터 현재 프레임까지 걸린 시간(초)를 뜻한다.

  • 60FPS : `DeltaTime` ≈ 1/60초 ≈ 0.0167초
  • 120FPS : `DeltaTime` ≈ 1/120초 ≈ 0.0083초

프레임 레이트가 높을수록 DeltaTime이 작아지고, 낮을수록 커진다.

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

매 프레임마다 X 좌표를 1씩 증가 → FPS이 높을수록 더 빨리 움직여 체감 속도가 달라짐

이를 방지하기 위해, DeltaTime을 곱해서 초 단위 기준으로 보정한다.

 

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

회전 속도 변수와 Tick 함수 선언

`.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;
};

Tick 함수에서 DeltaTime으로 회전 구현

`.cpp`

#include "Item.h"

AItem::AItem()
{
	...

	PrimaryActorTick.bCanEverTick = true;

	RotationSpeed = 90.0f;
}

void AItem::BeginPlay()
{
	...
}

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

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

 

 

iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차