언리얼엔진

[C++] Logger(UE_LOG, GEngine)

찬이2 2023. 9. 7. 21:46

Log 다뤄보기

이번엔 Log에 관해 배워볼 것이다. 먼저 언리얼 에디터에서 Logger 클래스(부모 클래스 없음)를 만들어준다.

만들어진 Logger 클래스 안에 있는  생성자와 소멸자는 지워준다.

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"

class GPC_CPP_12_API Logger
{
public:
	/* 
	@param Data Integer Data To Be Logged
	@param Test Just For Test
	*/

	static void Log(int32     const& Data);
	static void Log(float     const& Data);
	static void Log(FString   const& Data);
	static void Log(FVector   const& Data);
	static void Log(FRotator  const& Data);
	static void Log(UObject*  const& Data);
};

Log() 함수를 만드는데 6가지 형식으로 오버로드한 상태이다. 그리고 이 함수들을 Logger.cpp에 정의해준다.

 

@param Data Integer Data To Be Logged
@param Test Just For Test

여기서 이러한 형식으로 주석을 추가했는데, 이는 Log()함수 매개변수에 대한 인텔리센스 설명을 추가를 할 수 있다.


UE_LOG()

저번에 Log을 출력하기 위해 UE_LOG()를 사용했었다.

UE_LOG(카테고리 이름, Log 종류, 형식 문자열, 가변인수 매크로)

- 카테고리 이름(CategoryName) : Log 카테고리의 이름

- Log 종류(Verbosity) : Display, Log 등 종류를 말한다.

- 형식 문자열(Format) : 형식 지정자를 사용한 문자열

- 가변인수 매크로(__VA_ARGS__)

 

해서 아래와 같이 Log() 오버라이드 함수들을 정의해둔다.

void Logger::Log(int32 const& Data)
{
	UE_LOG(LogTemp, Log, TEXT("%i"), Data);
}

void Logger::Log(float const& Data)
{
	UE_LOG(LogTemp, Log, TEXT("%f"), Data);
}

void Logger::Log(FString const& Data)
{
	UE_LOG(LogTemp, Log, TEXT("%s"), *Data); 
    //*Data로 해줘야 내부 문자열 반환
}

void Logger::Log(FVector const& Data)
{
	UE_LOG(LogTemp, Log, TEXT("%s"), *Data.ToString());
}
 
void Logger::Log(FRotator const& Data)
{
	UE_LOG(LogTemp, Log, TEXT("%s"), *Data.ToString());
}
 
void Logger::Log(UObject* const& Data)
{
	UE_LOG(LogTemp, Log, TEXT("%s"), *Data->GetFName().ToString());
}

다음으로 Cube.cpp에 와서 #include "Logger.h"를 포함해준다.

그리고 기존에 있던 LogMeshName() 함수 안에서 Logger안에 있는 Log()함수를 호출해준다.

void ACube::LogMeshName()
{
	Logger::Log(Mesh->GetStaticMesh());
	//UE_LOG(LogTemp, Log, TEXT("%s"), *Mesh->GetStaticMesh()->GetFName().ToString());
}

이제 빌드 후에, 언리얼 에디터에서 출력 로그를 보면 LogTemp : Cube가 정상적으로 출력되는 것을 볼 수 있다.


Log의 카테고리 정의하기

앞서 카테고리로 LogTemp를 사용해왔는데, 여기저기 같은 카테고리를 사용하게되면 나중에 구분하기 어려워질 수 있다.

따라서 임의로 카테고리를 정의해볼 것이다.

 

UE_LOG를 사용중인 Logger.cpp에서 매크로 하나를 추가한다.

DEFINE_LOG_CATEGORY_STATIC(GPC, Log, All)

* 여기서 정의한 카테고리는 해당 매크로를 정의한 곳에서만 사용 가능하다.

그리고 Logger.cpp 문서에서 사용중인 LogTemp를 전부 GPC로 바꿔준다.

* Ctrl + H 단축키를 이용해, 현재 문서에서 LogTemp를 GPC로 바꿔주면 된다.

 

이후 빌드하고 언리얼 에디터의 출력 로그를 보면 LogTemp가 GPC로 바뀌어서 출력된 것을 볼 수 있다.

언리얼 에디터의 출력 로그


LogCategory(GPC)를 정의한 문서 외부에서도 LogCategory(GPC)를 사용해보기

이번엔 Logger.cpp에서 기존에 추가했던 카테고리 매크로는 지우고, 새 Log Category 매크로를 정의해준다.

DEFINE_LOG_CATEGORY(GPC)

 

이제 Logger.h에서 카테고리의 선언을 추가해줄 것이다. 이는 외부에 카테고리가 정의되어 있다고 알려주는 역할이다.

DECLARE_LOG_CATEGORY_EXTERN(GPC, Log, All)

* extern : 외부 파일에 있다는 뜻. 함수에는 암묵적으로 extern이 포함되어 있음

 

이렇게되면 외부 Cube.cpp에서 UE_LOG()에 GPC 카테고리를 정상적으로 사용할 수 있는 것을 볼 수 있다.


스크린 메세지로 Log 출력하기

평소 언리얼 에디터에서 플레이하여 화면 스크린으로 잘 작동하는지 출력했었다.

이를 VS를 통해 구현해볼 것이다.

 

Logger.h에서 Print()함수를 오버라이드하여 6개를 만든다.

* 여기서 변경되지 않을 값에 대해선 const를 걸어주는게 좋다.

static void Print(int32    const& Data, int32 const Key, float const Duration, FColor const Color);
static void Print(float    const& Data, int32 const Key, float const Duration, FColor const Color);
static void Print(FString  const& Data, int32 const Key, float const Duration, FColor const Color);
static void Print(FVector  const& Data, int32 const Key, float const Duration, FColor const Color);
static void Print(FRotator const& Data, int32 const Key, float const Duration, FColor const Color);
static void Print(UObject* const& Data, int32 const Key, float const Duration, FColor const Color);

이후 선언한 Print()함수 6개의 정의를 생성해준다.


GEngine->AddOnScreenDebugMessage()

스크린에 메세지를 출력하기 위해서 필요한 클래스 GEngine 이다.

해서 아래와 같이 Print()함수들을 정의했다.

void Logger::Print(int32 const& Data, int32 const Key, float const Duration, FColor const Color)
{
	GEngine->AddOnScreenDebugMessage(Key, Duration, Color, FString::FromInt(Data));
}

void Logger::Print(float const& Data, int32 const Key, float const Duration, FColor const Color)
{
	GEngine->AddOnScreenDebugMessage(Key, Duration, Color, FString::SanitizeFloat(Data));
}

void Logger::Print(FString const& Data, int32 const Key, float const Duration, FColor const Color)
{
	GEngine->AddOnScreenDebugMessage(Key, Duration, Color, Data);
}

void Logger::Print(FVector const& Data, int32 const Key, float const Duration, FColor const Color)
{
	GEngine->AddOnScreenDebugMessage(Key, Duration, Color, Data.ToString());
}

void Logger::Print(FRotator const& Data, int32 const Key, float const Duration, FColor const Color)
{
	GEngine->AddOnScreenDebugMessage(Key, Duration, Color, Data.ToString());
}

void Logger::Print(UObject* const& Data, int32 const Key, float const Duration, FColor const Color)
{
	GEngine->AddOnScreenDebugMessage(Key, Duration, Color, Data->GetFName().ToString());
}

이제 Logger.h를 포함하고있는 Cube.cpp로 돌아와서 Tick()에서 해당 Print()함수를 계속 호출할 것이다.

// Called every frame
void ACube::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
    
	Logger::Print(this, 0, 10, FColor::Red);
	Logger::Print(this->GetActorLocation(), 1, 10, FColor::Red);
}

여기서 Print()함수의 Key값은 일종의 채널이라고 볼 수 있다. 메세지마다 Key를 두어 출력될 위치를 분할해준다.

 

빌드해서 언리얼 에디터를 플레이하면 좌측 상단에 지정한 빨간색으로 메세지가 출력된 것을 볼 수 있다.

Key가 0, 1로 출력됨

 

만약 Key값으로 INDEX_NONE(-1)을 넣어주면 화면 전체를 덮어버리면 출력하는 것을 볼 수 있다.

Key가 INDEX_NONE으로 출력됨


Print()함수의 매개변수에 기본 인수 추가해주기

Print()함수를 선언한 부분 Logger.h에서 기본 인수를 추가해주어야 한다.

기본 인수 추가는 보통 오른쪽에서 왼쪽 순서대로 주어야하는 규칙이 있다.

 

여기선 매크로를 선언해두고 각각의 매크로들을 Print() 인자의 기본 인수로 넣어주었다.

#define DERAULUT_KEY (INDEX_NONE)
#define DERAULUT_DURATION (5.0f)
#define DERAULUT_COLOR (FColor::Cyan)