게임 개발 중 특정 함수가 제대로 호출되는지, 변수에 어떤 값이 들어있는지를 빠르게 확인해야 할 때는 로그 메시지가 큰 도움이 됩니다. 언리얼 엔진에서는 UE_LOG 매크로를 사용해 Output Log 창에 메시지를 남길 수 있습니다.
ex. 어떤 로직에서 if문 안에 로그를 찍었다 -> 그럼 지금 그 if문 안에 들어왔는지 안 들어왔는지 확인할 수 있는 거임.
BeginPlay : 액터가 생성된 시점에 바로 호출이 되는 함수
<로그 C++ 코드로 작성하는 법>
window -> output log 체크
<Item.h>
virtual void BeginPlay() override;
<Item.cpp>
void AItem::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Warning, TEXT("My Log!"))
}
- 로그 카테고리 : LogTemp
- 로그 레벨 :
Warning : 경고 메시지 (노란색으로 뜸)
Display : 평범한 메시지 (하얀색으로 뜸)
Error : 심각한 오류를 알릴 때 (빨간색으로 뜸) - TEXT("") : 입력하고 싶은 텍스트 입력
우클릭 후 clear log 하면 로그창 비워짐.

필터에서 원하는 로그만 체크하면 그것만 볼 수 있음!
Filter의 카테고리에 들어오려면 한 번은 출력이 됐어야 함!
<고유한 카테고리 정의하고 로그 추가하기>
<Item.h>
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogTest, Warning, All);
UCLASS()
class TEST01_API AItem : public AActor
{
GENERATED_BODY()
public:
AItem();
protected:
USceneComponent* SceneRoot;
UStaticMeshComponent* StaticMeshComp;
virtual void BeginPlay() override;
};
DECLARE_LOG_CATEGORY_EXTERN(만들 카테고리 이름, 로그 레벨, All);
-> All : 필요하면 나중에 모든 로그를 활성화할 수 있다고 일단 열어두는 것.
<Item.cpp>
#include "Item.h"
DEFINE_LOG_CATEGORY(LogTest);
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_Burnished_Steel.M_Metal_Burnished_Steel"));
if (MaterialAsset.Succeeded())
{
StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
}
}
void AItem::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Warning, TEXT("My Log!"));
UE_LOG(LogTest, Error, TEXT("My TEST!"));
}
DEFINE_LOG_CATEGORY(LogTest);
cpp에 선언함!
만약 이 카테고리를 다른 클래스에서 사용하고 싶다면, 정의되어 있는 헤더파일을 include 해서 쓰면 됨.
(그래서 보통은 로그 카테고리를 선언해놓은 헤더 파일을 따로 만들어서 사용함.)
그리고 이제 .cpp에서 만들어놓은 로그 카테고리 쓰면 됨.
UE_LOG(LogTest, Error, TEXT("My TEST!"));

<언리얼 엔진 Actor의 라이프 사이클 이해하기>
언리얼 엔진에서 Actor는 게임 도중에 언제든지 생성(Spawn)될 수 있고, 필요 없어지면 파괴(Destroy)될 수 있습니다. 이를 Actor 라이프 사이클이라 부르며, 이 과정을 이해하면 게임 로직을 보다 효율적이고 안정적으로 작성할 수 있습니다.
생성자 - 오브젝트가 메모리에 생기면서 딱 한 번 호출됨.
PostInitializeComponents() - 컴포넌트가 완성된 직후 호출. 컴포넌트끼리 데이터 주고받기, 상호작용
BeginPlay - 배치(spawn) 직후
Tick - 매 프레임마다 호출
destroyed() - 삭제 되기 직전에 호출된다.
endplay() - 게임 종료, 파괴(distroyed()), 레벨 전환
destroyed 를 부르면 endplay도 호출됨
endplay 부르면 endplay만 호출됨
1. 액터 라이프 사이클을 알아야 하는 이유
- 초기화 시점 결정
- 생성자 (Constructor), PostInitializeComponents, BeginPlay 등이 각각 언제 호출되는지 알아야 적절한 곳에 코드를 배치할 수 있습니다.
- 예) 컴포넌트 생성(CreateDefaultSubobject)은 생성자에서, 다른 액터 참조나 월드 접근은 BeginPlay에서 처리.
- 성능 관리
- 매 프레임마다 호출되는 Tick 함수는 비용이 클 수 있습니다.
- 따라서 필요한 액터만 Tick을 활성화하거나 이벤트 기반으로 전환해 최적화해야 합니다.
- 리소스 정리
- 액터가 사라질 때 (EndPlay, Destroyed 등) 메모리를 해제하거나 특정 상태를 저장해야 할 수 있습니다.
- 적절한 시점에 필요한 정리 작업을 하지 않으면 메모리 누수나 예외 상황이 발생할 수 있습니다.
2. 주요 라이프 사이클 함수
- 언리얼 엔진의 Actor는 생성 → 초기화 → 월드 배치 → Tick(실행) → 제거 순으로 동작하며, 이를 지원하기 위해 여러 함수가 자동 호출됩니다.
- 생성자 (Constructor)
- 호출 시점: C++ 클래스 객체가 메모리에 생성될 때 딱 한 번 호출됩니다.
- 아직 액터가 월드 (World)에 완전히 등록되지 않은 상태이므로, 다른 액터나 월드 관련 기능을 안전하게 호출하기 어렵습니다.
- 주로 CreateDefaultSubobject 등을 사용해 컴포넌트 생성 및 초기 변수 세팅에 활용합니다.
- PostInitializeComponents()
- 호출 시점: 액터의 모든 컴포넌트가 생성·초기화된 뒤 자동 호출됩니다.
- 각 컴포넌트가 이미 준비된 상태이므로, 컴포넌트 간 상호작용(예: 서로 다른 컴포넌트 참조 설정)이 필요한 코드를 배치하기 좋습니다.
- 보통 생성자에서는 단순한 ‘생성/할당’만 하고, PostInitializeComponents()에서 컴포넌트들 사이의 의존 관계를 설정합니다.
- BeginPlay()
- 호출 시점: Play In Editor (PIE)나 런타임에서 게임이 시작될 때, 혹은 이미 실행 중인 게임에 SpawnActor 등으로 새 액터가 생성될 때 한 번 호출됩니다.
- 이 시점에는 이미 월드와 다른 액터들이 준비된 상태이므로, 자유롭게 상호작용이 가능합니다.
- AI, 게임 모드, 플레이어 컨트롤러 등 다른 시스템과 연동도 주로 BeginPlay에서 초기화합니다.
- 타이머, Delegate(Event) 바인딩 등을 시작하기에도 적합합니다.
- Tick(float DeltaTime)
- 호출 시점: 매 프레임마다 호출됩니다. (액터의 PrimaryActorTick.bCanEverTick = true; 설정 필요)
- 실시간 업데이트가 필요한 로직 (캐릭터 이동, 물리 연산, 카메라 추적 등)을 처리합니다.
- 이벤트 (Event) 기반으로 전환할 수 있는 부분은 Tick을 사용하지 않는 편이 성능에 유리합니다.
- Destroyed()
- 호출 시점: Destroy() 함수를 직접 호출하여 액터를 제거할 때 직전에 호출됩니다. 다만 레벨 전환이나 게임 종료 시에는 종종 건너뛰어지기도 하므로 (상황마다 엔진 동작이 달라짐), 절대적으로 보장되는 것은 아닙니다.
- Destroyed()가 불린 뒤에는 최종적으로 EndPlay()도 함께 호출됩니다.
- 수동으로 액터를 제거할 때, 마지막 정리 코드를 넣을 수 있는 곳입니다.
- But, 게임 종료나 레벨 언로드 시에는 호출되지 않을 수 있으므로, 모든 중요한 정리를 Destroyed()에만 의존하면 놓치는 케이스가 생길 수 있습니다.
- 정리할 자원 예시
- 수동 할당한 메모리: new 또는 동적 할당한 오브젝트가 있다면 여기서 delete하거나 해제합니다.
- 스폰된 자식 액터: 이 액터가 생성한 다른 액터나 컴포넌트 중, 자동으로 해제되지 않는 것이 있다면 제거 처리합니다.
- Delegate / Event 바인딩: 게임 전역적 또는 외부 클래스에 바인딩해둔 델리게이트가 있다면 해제합니다.
- 사운드/파티클 등: 필요 시 이 액터가 재생 중인 사운드나 파티클을 수동으로 정리합니다.
- EndPlay(const EEndPlayReason::Type EndPlayReason)
- 호출 시점: 액터가 더 이상 월드에서 활동하지 않게 될 때 호출됩니다. (파괴, 레벨 전환, 게임 종료 등)
- EEndPlayReason::Type으로 어떤 이유로 EndPlay가 호출되었는지(파괴, 레벨 언로드, 게임 종료 등)를 구분합니다.
- 게임 종료나 레벨 언로드 같은 상황에서도 EndPlay()는 상대적으로 호출 보장이 높지만, Destroyed()는 건너뛸 수 있습니다.
- 따라서 중요한 정리 로직 (자원 해제, Timer 해제, 상태 저장 등)은 EndPlay()에 넣는 것이 보다 안전합니다.
- 정리할 자원 예시
- 타이머: GetWorldTimerManager().ClearTimer(…) 와 같이 타이머를 정리합니다.
- 동적 할당 리소스: 여전히 해제되지 않은 동적 메모리가 남아 있다면 여기서 정리합니다.
- 데이터 저장: 게임 진행 상황 (점수, 인벤토리 등)을 파일/DB에 저장하거나, 상위 시스템에 콜백을 보내는 로직도 EndPlay에서 처리 가능합니다.
<Item.h>
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogTest, Warning, All);
UCLASS()
class TEST01_API AItem : public AActor
{
GENERATED_BODY()
public:
AItem();
protected:
USceneComponent* SceneRoot;
UStaticMeshComponent* StaticMeshComp;
virtual void PostInitializeComponents() override;
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
virtual void Destroyed() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
};
<Item.cpp>
#include "Item.h"
DEFINE_LOG_CATEGORY(LogTest);
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_Burnished_Steel.M_Metal_Burnished_Steel"));
if (MaterialAsset.Succeeded())
{
StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
}
UE_LOG(LogTest, Warning, TEXT("%s Constructor"), *GetName());
}
void AItem::PostInitializeComponents()
{
Super::PostInitializeComponents();
UE_LOG(LogTest, Warning, TEXT("%s PostInitializeComponents"), *GetName());
}
void AItem::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTest, Warning, TEXT("%s BeginPlay"), *GetName());
}
void AItem::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Tick 함수는 매 프레임마다 호출되므로
// 여기서 로그를 찍으면 방대한 양의 메시지가 쌓일 수 있음.
// 필요시 디버깅용 코드만 간단히 작성하거나, 별도 조건을 걸어 사용.
}
void AItem::Destroyed()
{
UE_LOG(LogTest, Warning, TEXT("%s Destroyed"), *GetName());
Super::Destroyed();
// Destroyed와 EndPlay는 함수가 호출되면서 삭제될 수도 있기 때문에 부모 클래스 위에다가 로그 찍어줘야 함.
}
void AItem::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UE_LOG(LogTest, Warning, TEXT("%s EndPlay"), *GetName());
Super::EndPlay(EndPlayReason);
}
언리얼 에디터 상에서 play 중에 마우스 커서 나오게 하고 싶으면 Shift + F1 누르면 됨.

'학습 > Unreal' 카테고리의 다른 글
| C++ 클래스와 리플렉션 시스템 활용하기 (0) | 2026.04.10 |
|---|---|
| Tick 함수로 Actor의 Transform 조정하기 (1) | 2026.04.10 |
| Actor 클래스에 컴포넌트 추가하기 (0) | 2026.04.08 |
| C++로 Actor 클래스 생성 및 삭제하기 (0) | 2026.04.08 |
| 언리얼 엔진 C++ 빌드 프로세스 이해하기 (0) | 2026.04.07 |
