KoreanFoodie's Study

이득우의 언리얼 C++ 3 : 로그, 액터 움직이기, 액터 삭제 본문

Game Dev/Unreal C++ : Tutorial

이득우의 언리얼 C++ 3 : 로그, 액터 움직이기, 액터 삭제

GoldGiver 2022. 2. 16. 22:43

이득우님의 "이득우의 언리얼 C++ 게임 개발의 정석" 책을 따라가며 실습한 내용을 정리한 포스팅입니다. 실습에 필요한 자료들은 이 링크에서, 제가 작업한 예제 소스 완성본은 여기에서 찾아보실 수 있습니다. (저는 언리얼 4.27.2 버전 기준으로 작업하였습니다)

로깅 환경 설정

언리얼은 로깅 환경을 위해 UE_LOG 라는 매크로를 제공한다.

UE_LOG(카테고리, 로깅 수준, 형식 문자열, 인자.. )

로깅 수준은 크게 메시지(Log), 경고(Warning), 에러(Error) 이렇게 세 가지로 나뉜다.

빨간색이 에러, 노랑이 경고, 흰색이 메시지

 

언리얼 형식 문자열은 FString 을 이용해서 처리하는데, 다음과 같은 형식을 띤다.

FString::Printf(TEXT("Actor Name : %s, ID : %d, Location X : %.3f"), *GetName(), ID, GetActorLocation().X);

/* 출력 결과 - Actor Name : Fountain_1, ID : 0, Location X : 410.000 */

이때, FString 타입의 문자열을 가져오는데 * 연산자를 사용한다(여기서는 GetName 앞에 * 연산자를 붙임).

 

 

로깅을 위한 공용 매크로 설정

더 편한 로깅을 위해 로그 카테고리를 직접 선언해 보자. 시험삼아 ArenaBattle 이라는 로그 카테고리를 새롭게 정의할 것이다.

 

먼저 ArenaBattle.h 에 로그 카테고리를 새롭게 선언한다.

// 이건 다른 모듈이 ArenaBattle.h 를 상속할 때, EngineMinimal.h 가 포함되도록 만들기 위해서임
#define "EngineMinimal.h"
DECLARE_LOG_CATEGORY_EXTERN(ArenaBattle, Log, All);

#define ABLOG_CALLINFO (FString(__FUNCTION__) + TEXT("(") + FString::FromInt(__LINE__) + TEXT(")"))
#define ABLOG_S(Verbosity) UE_LOG(ArenaBattle, Verbosity, TEXT("%s"), *ABLOG_CALLINFO)
#define ABLOG(Verbosity, Format, ...) UE_LOG(ArenaBattle, Verbosity, TEXT("%s %s"), *ABLOG_CALLINFO, *FString::Printf(Format, ##__VA_ARGS__))

두 종류의 로그 매크로를 추가로 선언했는데, 각 기능은 다음과 같다.

  • ABLOG_S : 코드가 들어 있는 파일 이름과 함수, 그리고 라인 정보를 추가한다.
  • ABLOG : ABLOG_S 정보에 형식 문자열로 추가 정보를 지정해 로그를 남긴다.

 

그 다음, ArenaBattle.cpp 에 해당 카테고리를 정의한다.

DEFINE_LOG_CATEGORY(ArenaBattle);

 

그 후, Fountain.h 의 #define "EngineMinimal.h" 를 "ArenaBattle.h" 로 변경한다.

마지막으로, Fountain.cpp 의 BeginPlay 부분에서 로그 매크로를 실행시킨다.

// Called when the game starts or when spawned
void AFountain::BeginPlay()
{
	Super::BeginPlay();

	ABLOG_S(Warning);
	ABLOG(Warning, TEXT("Actor Name : %s, ID : %d, Location X : %.3f"), *GetName(), ID, GetActorLocation().X);
}

컴파일 후 플레이 버튼을 누르면, Fountain 액터가 생성될 때 적절한 로그가 출력되게 된다.

 

 

어설션

프로그래밍에서 어설션은 반드시 확인하고 넘어가는 점검 코드를 의미한다.

언리얼에서는 check 와 ensure 이 있는데, check는 에디터를 종료하고 띄우는 방식이고, ensure 은 가볍게 경고를 띄운다.

 

 

액터의 주요 이벤트 함수

액터에 속한 컴포넌트의 세팅이 완료되면 언리얼 엔진은 액터의 PostInitializeComponents 함수를 호출한다. 퇴장시에는 EndPlay 를 호출하는데, 이렇듯 엔진에 의해 자동으로 호출되는 함수를 이벤트 함수라고 부르도록 하겠다.

작동을 확인하기 위해 분수대 액터에서 함수를 추가로 선언해 보자.

	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
	virtual void PostInitializeComponents() override;

 

언리얼 엔진이 제공하는 액터 클래스에는 BeginPlay, Tick 같은 이벤트 함수들이 선언되어 있고, 자식이 상속받을 수 있도록 가상 함수로 선언되어 있다. 부모클래스인 액터의 가상함수 로직에는 반드시 실행해야 하는 것들이 들어있다. 따라서 다음과 같이 각 함수마다 로그를 남기는 코드를 추가한 후 플레이 버튼을 누르면 다음과 같은 로그가 생성된다.

void AFountain::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason);
	ABLOG_S(Warning);
}

void AFountain::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	ABLOG_S(Warning);
}

PostInitializeComponents, BeginPlay, EndPlay 순으로 로그가 출력되는 것을 확인해 볼 수 있다.

 

 

움직이는 액터의 설계

액터의 Tick 함수를 사용해 액터의 움직임을 구현해 보자.

언리얼 엔진에서 액터의 틱 함수는 화면을 만들어내는 렌더링 프레임 단위로 동작한다. 이전 렌더링 프레임부터 현재 렌더링 프레임까지 걸린 시간은 Tick 함수의 DeltaSeconds 를 통해 알 수 있다(레벨의 복잡도와 컴퓨터의 성능에 따라 불규칙함).

회전시키고자 할 각속도의 값을 변수로 지정하고, 분수대를 Z 축 기준으로 회전시킨다. 값 유형이므로 에디터에서 편집할 수 있게 UPROPERTY 매크로에 EditAnywhere 키워드를 넣는다.

private 으로 선언 후, UPROPERTY 매크로에 AllowPrivateAccess 라는 META 키워드를 추가하면 에디터에서는 이를 편집함과 동시에 변수 데이터를 은닉할 수 있게 돼 캡슐화(Encapsulation) 이 가능해진다.

private:
	UPROPERTY(EditAnywhere, Category=Stat, Meta = (AllowPrivateAccess = true))
	float RotateSpeed;

 

RotateSpeed 기본값을 생성자에서 초기화하고, Tick 함수에서 AddActorLocalRotation 함수를 사용해 분수대가 틱마다 회전하도록 코드를 제작한다.

AFountain::AFountain()
{
    ...
    RotateSpeed = 30.0f;
}

// Called every frame
void AFountain::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	AddActorLocalRotation(FRotator(0.0f, RotateSpeed * DeltaTime, 0.0f));  
}

 

이동과 스케일을 위해서는 FVector 를, 회전은 FRotator 를 이용한다. Pitch 는 Y축 회전(좌우)를, Yaw 는 Z축 회전(상하)를, Roll 은 X축 회전(정면)을 표현할 때 사용한다.

 

 

무브먼트 컴포넌트의 활용

언리얼 엔진에서는 움직임이라는 요소를 분리해 액터와 별도로 관리하도록 프레임워크를 구성했는데, 이것이 무브먼트 컴포넌트이다(액터의 움직임에 대한 것을 담당). 이를 이용하면 Tick 함수를 구현하지 않고도 액터를 움직일 수 있다. 무브먼트 컴포넌트들은 다음과 같다.

  • FloatingPawnMovement : 중력의 영향을 받지 않는 액터의 움직임 제공
  • RotatingMovement : 지정한 속도로 액터를 회전
  • InterpMovement : 지정한 위치로 액터를 이동
  • ProjectileMovement : 액터에 중력의 영향을 받아 포물선을 그리는 발사체의 움직임을 제공(총알, 미사일 등)

 

따라서 코드에 다음을 추가해 보자.

Fountain.h : 

#include "GameFramework/RotatingMovementComponent.h"

UCLASS()
class ARENABATTLE_API AFountain : public AActor
{
	GENERATED_BODY()

    ...

	UPROPERTY(VisibleAnywhere)
	URotatingMovementComponent* Movement;
    
    ...
};

 

Fountain.cpp

#include "Fountain.h"

// Sets default values
AFountain::AFountain()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false; // Tick 작동 필요 없음

	RotateSpeed = 30.0f;
	Movement->RotationRate = FRotator(0.0f, RotateSpeed, 0.0f);
}

 

에디터 상으로도 Movement 라는 컴포넌트가 추가된 것을 확인할 수 있다.

 

 

프로젝트 재구성 (feat. 액터 삭제)

우리가 만든 Fountain 액터를 삭제해 보자.

1. 에디터를 닫는다.

2. 게임 프로젝트의 Source 폴더 내에 위치한 ArenaBattle 폴터의 Fountain.h 와 Fountain.cpp 파일을 탐색기와 비주얼 스튜디오에서 삭제한다.

3. 게임 프로젝트의 uproject 파일을 우클릭하고 "Generate Visual Studio project files" 메뉴를 선택해 비주얼 스튜디오 프로젝트를 재생성한다. / 혹은 비주얼 스튜디오에서 "솔루션 다시 빌드"를 실행한다.

 

Comments