먼저 언리얼엔진에서 로그출력창에 로그를 남기는 방법에 대해 알아보겠습니다.
- UE_LOG(LogTemp, Warning, TEXT("My Item appears!!"))
- 로그 카테고리 (Log Category): 여기서는 LogTemp라는 임시 카테고리를 사용했습니다.
- 로그 수준 (Log Level): Warning을 사용하면, 노란색 글씨로 강조되어 출력됩니다. 이 외에도 Log, Display, Error 등 다양한 수준이 있습니다.
- Display: 일반적인 실행 흐름이나 상태 확인 메시지 (흰색)
- Warning: 예상치 못한 동작이나 잠재적인 문제 (노란색)
- Error: 즉시 수정이 필요한 심각한 문제 (빨간색)
- 출력할 메시지: My Item appears!!라는 문자열이 출력됩니다.
이렇게 원래 언리얼에서 기본적으로 만들어준 LogTemp 를 사용할수도 있지만 프로젝트가 규모가 커질수록 고유한 카테고리를 만들어 사용하는 것이 좋습니다.
고유 카테고리 헤더파일에 선언하는 방법
// "LogSparta"라는 이름으로 로그 카테고리 선언
DECLARE_LOG_CATEGORY_EXTERN(LogSparta, Warning, All);
- DECLARE_LOG_CATEGORY_EXTERN(LogSparta, Warning, All);
- 헤더 파일에서 로그 카테고리를 선언합니다.
- LogSparta: 카테고리 이름 (사용자가 지정).
- Warning: 이 카테고리를 사용할 때 기본적으로 Warning 이상의 로그만 출력하도록 설정
- All: 필요하면 나중에 모든 로그를 활성화할 수 있도록 허용
이제 cpp파일에 실제로 구현도 해주어야합니다.
// "LogSparta" 카테고리 정의 (헤더에서 선언한 것을 실제로 구현)
DEFINE_LOG_CATEGORY(LogSparta);
- DEFINE_LOG_CATEGORY(LogSparta);
- .cpp 파일에서 로그 카테고리를 구현(정의)합니다.
- 이제 LogSparta라는 카테고리를 통해 로그 메시지를 좀 더 체계적으로 구분할 수 있습니다.
이제 로그를 활용하여 actor의 라이프 사이클을 이해해 보겠습니다.
액터 라이프 사이클을 알아야 하는 이유
- 초기화 시점 결정
- 생성자 (Constructor), PostInitializeComponents, BeginPlay 등이 각각 언제 호출되는지 알아야 적절한 곳에 코드를 배치할 수 있습니다.
- 예) 컴포넌트 생성(CreateDefaultSubobject)은 생성자에서, 다른 액터 참조나 월드 접근은 BeginPlay에서 처리.
- 성능 관리
- 매 프레임마다 호출되는 Tick 함수는 비용이 클 수 있습니다.
- 따라서 필요한 액터만 Tick을 활성화하거나 이벤트 기반으로 전환해 최적화해야 합니다.
- 리소스 정리
- 액터가 사라질 때 (EndPlay, Destroyed 등) 메모리를 해제하거나 특정 상태를 저장해야 할 수 있습니다.
- 적절한 시점에 필요한 정리 작업을 하지 않으면 메모리 누수나 예외 상황이 발생할 수 있습니다.
- 언리얼 엔진의 Actor는 생성 → 초기화 → 월드 배치 → Tick(실행) → 제거 순으로 동작하며, 이를 지원하기 위해 여러 함수가 자동 호출됩니다.
- 생성자 (Constructor)
- C++ 클래스 객체가 메모리에 생성될 때 단 한 번 호출됩니다.
- 아직 월드에 완전히 등록된 상태가 아니므로, 다른 액터나 월드 관련 기능은 안전하게 호출하기 어렵습니다.
- 보통 컴포넌트 생성(CreateDefaultSubobject) 및 기본 변수 초기화에 사용합니다.
- PostInitializeComponents()
- 액터의 모든 컴포넌트가 생성·초기화를 마친 뒤 자동으로 호출됩니다.
- 컴포넌트들이 이미 준비된 상태이므로, 컴포넌트 간 상호작용 초기화 코드를 넣기 좋습니다.
- BeginPlay()
- 게임이 시작 (Play 모드)되거나, 런타임 중 액터가 새로 생성 (Spawn)되는 순간에 한 번 호출됩니다.
- 이 시점에서는 월드와 다른 액터들이 준비된 상태이므로, 자유롭게 상호작용 코드를 작성할 수 있습니다.
- AI, 게임 모드, 플레이어 컨트롤러 등 다른 시스템과의 연동도 보통 이 시점에 처리합니다.
- Tick(float DeltaTime)
- 매 프레임마다 반복 호출되며, 실시간 업데이트가 필요한 로직 (캐릭터 이동, 물리 연산 등)을 넣습니다.
- 불필요한 액터에는 Tick을 끄고, 이벤트 기반으로 전환하면 성능을 절약할 수 있습니다.
- Destroyed()
- Destroy() 함수를 직접 호출해 액터를 제거할 때 직전에 호출됩니다. (단, 게임 종료나 레벨 전환 시에는 호출되지 않을 수 있음)
- 보통 EndPlay에서 주요 정리를 마치고, Destroyed()에서 메모리 해제나 사운드/파티클 정리 등 최종 작업을 수행합니다.
- Destroyed가 불리면 마지막에 EndPlay도 같이 호출됩니다.
- EndPlay(const EEndPlayReason::Type EndPlayReason)
- 액터가 더 이상 월드에서 활동하지 않을 때 (파괴, 게임 종료, 레벨 전환 등) 호출됩니다.
- EEndPlayReason::Type은 언리얼 엔진에서 EndPlay 함수가 호출되는 이유를 나타내는 열거형(enum) 타입입니다.
- 이 함수에서 자원 해제나 상태 저장을 처리합니다.
- 생성자 (Constructor)
이제 각각의 함수에 로그를 넣어주어 실제로 출력로그의 순서를 확인하기 위해 게임을 실행하면
다음과 같이 뜨고 이제 게임을 종료하면
다음과 같이 endplay가 호출됩니다. 그럼 destroy는 언제 호출되는 걸까요?
게임 도중 Shift + F1로 마우스를 에디터로 가져온 후, Outliner 창에서 Item 액터를 삭제하면 Destroyed 로그가 출력되고 EndPlay가 출력된 다는 것을 확인할 수 있습니다.
이제 thick 함수도 한번 알아보겠습니다.
게임 프레임 업데이트와 Tick
- 언리얼 엔진은 게임 실행 중 매 프레임마다 여러 작업을 수행합니다.
- 렌더링(Rendering): 화면 그리기 (일반적으로 1초에 60프레임, 120프레임 등)
- 물리 연산: 충돌·중력·마찰 등 물리 엔진 처리
- 오브젝트 업데이트: 게임 내 액터들의 상태 갱신
- 특정 액터가 “매 프레임마다” 수행할 로직이 있다면, 언리얼 엔진은 그 액터의 Tick(float DeltaTime) 함수를 매 프레임 호출해줍니다.
thick 함수를 호출해주기 위해서는 다음과 생성자에서 다음과 같이 설정해줘야합니다.
PrimaryActorTick.bCanEverTick = true;
하지만 이렇게 thick 타임만 사용하면 컴퓨터마다 프레임이 다르기에 보여지는게 달라지게 됩니다.이를 위한 것으로 DeltaTime 이란 개념이 필요합니다.
DeltaTime이란?
- Tick(float DeltaTime) 함수에서 DeltaTime은 “직전 프레임부터 현재 프레임까지 걸린 시간(초)”입니다.
- 60 FPS 경: DeltaTime ≈ 1/60초 ≈ 0.0167초
- 120 FPS 환경: DeltaTime ≈ 1/120초 ≈ 0.0083초
- 프레임 레이트가 높을수록 DeltaTime이 작아지고, 낮을수록 DeltaTime이 커집니다.
DeltaTime을 활용한 프레임 독립적인 움직임
- 단순히 “매 프레임마다 X 좌표를 1씩 증가”시키면, FPS가 높을수록 더 빨리 움직여 게임 체감 속도가 달라집니다.
- 이를 방지하려면, DeltaTime을 곱해서 초 단위 기준으로 이동·회전을 계산해야 합니다.
- 예) 초당 100만큼 이동하고 싶다면: 100 * DeltaTime을 매 프레임마다 더해줌
- 60 FPS → 한 프레임당 1.67씩 이동, 60 프레임 곱하면 100
- 120 FPS → 한 프레임당 0.83씩 이동, 120 프레임 곱하면 100
- 이처럼 어느 FPS 환경에서도 동일한 실제 속도를 유지할 수 있습니다.
- 예) 초당 100만큼 이동하고 싶다면: 100 * DeltaTime을 매 프레임마다 더해줌
델타타임을 사용한 회전 예시
#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;
// 회전 속도를 나타내는 변수(초당 도(degrees) 단위)
float RotationSpeed;
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
};
헤더 파일에 이렇게 해준뒤
cpp파일의 thick 함수에서 로직을 추가해주겠습니다.
#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);
}
// Tick 함수를 켜줍니다.
PrimaryActorTick.bCanEverTick = true;
// 기본 회전 속도 (1초에 90도 회전)
RotationSpeed = 90.0f;
}
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));
}
void AItem::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// RotationSpeed가 0이 아니라면 회전 처리
if (!FMath::IsNearlyZero(RotationSpeed))
{
// 초당 RotationSpeed만큼, 한 프레임당 (RotationSpeed * DeltaTime)만큼 회전
AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaTime, 0.0f));
}
}
- FMath::IsNearlyZero는 부동소수점 비교에서 안전하게 0에 가까운지 확인해주는 함수입니다.
- RotationSpeed = 360.0f면 1초에 한 바퀴(360도) 회전, 180.0f면 2초에 한 바퀴 회전하게 됩니다.
- AddActorLocalRotation()은 액터의 로컬 기준으로 회전을 추가해주는 함수입니다.
- 만약 월드 좌표 기준으로 회전하고 싶다면 AddActorWorldRotation()을 사용할 수도 있습니다
이제 빌드 후 게임을 플레이 해보면 actor가 회전함을 볼 수 있습니다.
'언리얼(Unreal) > 엔진' 카테고리의 다른 글
25.01.21 언리얼 Actor 클래스의 이해 (0) | 2025.01.21 |
---|---|
25.01.10 깃허브데스크탑 사용법 (0) | 2025.01.10 |
24.01.07 언리얼에서의 cpp(언리얼과 비쥬얼 스튜디오 호환 에러 해결법) (0) | 2025.01.07 |
25.01.06 언리얼 cpp 디자인패턴 (0) | 2025.01.06 |
25.01.02 언리얼 cpp STL에 대하여 (0) | 2025.01.02 |