Unreal

언리얼 엔진 Actor의 라이프 사이클 이해하기

kdi6316 2025. 1. 22. 17:55

언리얼 엔진 Actor의 라이프 사이클 이해하기

 

목차

 

1. Actor클래스에 로그(Log) 추가하기

2. 언리얼 에디터에서 로그 확인하기

3. 어리얼 엔진 Actor의 라이프 사이클 이해하기

4. 라이프 사이클 함수에 로그 추가하기

위의 목차를 클릭하면 해당 글로 자동 이동 합니다.

 

Actor클래스에 로그(Log) 추가하기

게임 개발 중 특정 함수가 제대로 호출되는지, 변수에 어떤 값이 들어있는지를 빠르게 확인해야 할 때는 로그 메시지가 큰 도움이 됩니다. 언리얼 엔진에서는 UE_LOG 매크로를 사용해 Output Log 창에 메시지를 남길 수 있습니다.

 

1.BeginPlay() 함수에 로그 추가하기

아래는 액터의 BeginPlay() 함수가 호출되는 시점을 확인하기 위해 로그를 출력하는 코드 예시입니다.

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

	UE_LOG(LogTemp, Warning, TEXT("My Item appears!!"));
	
}

- UE_LOG(LogTemp, Warning, TEXT("My Item appears!!"))
    - 로그 카테고리 (Log Category): 여기서는 `LogTemp`라는 임시 카테고리를 사용했습니다.
    - 로그 수준 (Log Level): `Warning`을 사용하면, 노란색 글씨로 강조되어 출력됩니다. 이 외에도 `Log`, `Display`, `Error` 등 다양한 수준이 있습니다.
        - Display: 일반적인 실행 흐름이나 상태 확인 메시지 (흰색)
        - Warning: 예상치 못한 동작이나 잠재적인 문제 (노란색)
        - Error: 즉시 수정이 필요한 심각한 문제 (빨간색)
    - 출력할 메시지: `My Item appears!!`라는 문자열이 출력됩니다.
- 이 코드를 작성한 뒤 빌드하고, 언리얼 에디터로 돌아오면 설정 완료입니다.

 

2.고유한 카테고리 정의하고 로그 추가하기

프로젝트 규모가 커질수록 모든 로그를 LogTemp로 찍으면 구분이 어렵습니다. 이럴 때는 DEFINE_LOG_CATEGORY를 사용해 고유한 카테고리를 만들어 사용하는 것이 좋습니다.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h"

// "LogSparta"라는 이름으로 로그 카테고리 선언
DECLARE_LOG_CATEGORY_EXTERN(LogSparta, Warning, All);

UCLASS()
class TESTPROJECT_API AItem : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AItem();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	USceneComponent* SceneRoot; // 루트 컴포넌트를 나타내는 Scene Component포인터
	UStaticMeshComponent* StaticMeshComp; // Static Mesh Component 포인터

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

};

- DECLARE_LOG_CATEGORY_EXTERN(LogSparta, Warning, All);
    - 헤더 파일에서 로그 카테고리를 선언합니다.
    - LogSparta: 카테고리 이름 (사용자가 지정).
    - Warning: 이 카테고리를 사용할 때 기본적으로 `Warning` 이상의 로그만 출력하도록 설정
    - All: 필요하면 나중에 모든 로그를 활성화할 수 있도록 허용
- 이후 다른 클래스에서도 LogSparta 카테고리를 사용하고 싶다면 이 헤더 파일(`Item.h`)을 포함해야 합니다. 보통은 이런 로그 카테고리를 여럿이 공유하기 때문에, 별도의 공용 헤더에 선언해 두는 경우가 많습니다.

#include "Item.h"

// "LogSparta" 카테고리 정의 (헤더에서 선언한 것을 실제로 구현)
DEFINE_LOG_CATEGORY(LogSparta);

// Sets default values
AItem::AItem()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot")); //Scene Component를 생성
	SetRootComponent(SceneRoot); //SceneRoot를 루트로 설정

	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh")); //Static Mesh Component를 생성
	StaticMeshComp->SetupAttachment(SceneRoot); //Scene Component에 Attach

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

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

	UE_LOG(LogTemp, Warning, TEXT("My Item appears!!"));

	UE_LOG(LogSparta, Error, TEXT("My Log!!"));
	
}

- DEFINE_LOG_CATEGORY(LogSparta);
    - .cpp 파일에서 로그 카테고리를 구현(정의)합니다.
- 이제 `LogSparta`라는 카테고리를 통해 로그 메시지를 좀 더 체계적으로 구분할 수 있습니다.

 

 

언리얼 에디터에서 로그 확인하기

로그를 출력하는 코드를 작성했다면, 언리얼 에디터에서 실제로 그 메시지가 잘 나타나는지 확인해야 합니다.

1.Output Log 창 열기

언리얼 에디터 상단 메뉴에서 Window -> Output Log를 클릭하면,하단이나 측면에 Output Log창이 열립니다.

 

이 창에는 엔진 전체에서 발생하는 로그 메시지가 실시간으로 표시됩니다.

너무 많은 기존 로그가 있다면 우클릭 -> Clear Log로 한 번 지우고, 새로 발생하는 로그만 모아서 보는 것이 편리합니다.

 

2.게임 시작 후 로그 확인

-에디터 상단의 Play버튼을 눌러 게임(플레이 모드)을 시작합니다.

- BeginPlay()가 호출되는 순간, 우리가 작성한 "My Item appears!!" 메시지가 노란색, "My Log!!" 메시지가 빨간색으로 표시됩니다.
- 대략 아래와 같은 형태로 로그 메시지가 표시됩니다.

 

 

3.로그 필터링

-로그가 너무 많다면,Output Log 창 상단 혹은 우측 상단의 Filters 메뉴를 이용해 카테고리별로 로그를 걸러낼 수 있습니다.

-예를 들어 Showw All 옵션을 끄고 LogTemp만 활성화하면 LogTemp카테고리에 해당하는 로그만 볼수 있습니다.

-만약 필터 목록에 LogTemp 자체가 보이지 않는다면, 아직 한 번도 해당 카테고리의 로그가 출력되지 않았을 수 있습니다. 반드시 로그가 최소 한 번 발생해야 목록에서 필터링할 수 있습니다.

언리얼 엔진 Actor의 라이프 사이클 이해하기

언리얼 엔진에서 Actor는 게임도중 언제든지 생성(Spawn)될 수 있고, 필요 없어지면 파괴(Destroy)될 수 있습니다. 이를 Actor 라이프 사이클 이라고 부르며, 이 과정을 이해하면 게임 로직을 보다 효율적이고 안정적으로 작성할 수 있습니다.

 

1. 액터 라이프 사이클을 알아야 하는 이유

- 초기화 시점 결정
    - 생성자 (Constructor), `PostInitializeComponents`, `BeginPlay` 등이 각각 언제 호출되는지 알아야 적절한 곳에 코드를 배치할 수 있습니다.
    - 예) 컴포넌트 생성(`CreateDefaultSubobject`)은 생성자에서, 다른 액터 참조나 월드 접근은 BeginPlay에서 처리.
- 성능 관리
    - 매 프레임마다 호출되는 `Tick` 함수는 비용이 클 수 있습니다.
    - 따라서 필요한 액터만 `Tick`을 활성화하거나 이벤트 기반으로 전환해 최적화해야 합니다.
- 리소스 정리
    - 액터가 사라질 때 (`EndPlay`, `Destroyed` 등) 메모리를 해제하거나 특정 상태를 저장해야 할 수 있습니다.
    - 적절한 시점에 필요한 정리 작업을 하지 않으면 메모리 누수예외 상황이 발생할 수 있습니다.

 

2. 주요 라이프 사이클 함수

언리얼 엔진의 Actor는 생성 → 초기화 → 월드 배치 → Tick(실행) → 제거 순으로 동작하며, 이를 지원하기 위해 여러 함수가 자동 호출됩니다.

 

1. 생성자 (Constructor)
    - C++ 클래스 객체가 메모리에 생성될 때 단 한 번 호출됩니다.
    - 아직 월드에 완전히 등록된 상태가 아니므로, 다른 액터나 월드 관련 기능은 안전하게 호출하기 어렵습니다.
    - 보통 컴포넌트 생성(`CreateDefaultSubobject`) 및 기본 변수 초기화에 사용합니다.
2. PostInitializeComponents()
    - 액터의 모든 컴포넌트가 생성·초기화를 마친 뒤 자동으로 호출됩니다.
    - 컴포넌트들이 이미 준비된 상태이므로, 컴포넌트 간 상호작용 초기화 코드를 넣기 좋습니다.
3. BeginPlay()
    - 게임이 시작 (Play 모드)되거나, 런타임 중 액터가 새로 생성 (Spawn)되는 순간에 한 번 호출됩니다.
    - 이 시점에서는 월드와 다른 액터들이 준비된 상태이므로, 자유롭게 상호작용 코드를 작성할 수 있습니다.
    - AI, 게임 모드, 플레이어 컨트롤러 등 다른 시스템과의 연동도 보통 이 시점에 처리합니다.
4. Tick(float DeltaTime)
    - 매 프레임마다 반복 호출되며, 실시간 업데이트가 필요한 로직 (캐릭터 이동, 물리 연산 등)을 넣습니다.
    - 불필요한 액터에는 `Tick`을 끄고, 이벤트 기반으로 전환하면 성능을 절약할 수 있습니다.

5. Destroyed()
    - Destroy() 함수를 직접 호출해 액터를 제거할 때 직전에 호출됩니다. (단, 게임 종료나 레벨 전환 시에는 호출되지 않을 수 있음)
    - 보통 `EndPlay`에서 주요 정리를 마치고, `Destroyed()`에서 메모리 해제나 사운드/파티클 정리 등 최종 작업을 수행합니다.
    - Destroyed가 불리면 마지막에 EndPlay도 같이 호출됩니다.
6. EndPlay(const EEndPlayReason::Type EndPlayReason)
    - 액터가 더 이상 월드에서 활동하지 않을 때 (파괴, 게임 종료, 레벨 전환 등) 호출됩니다.
    - `EEndPlayReason::Type`은 언리얼 엔진에서 `EndPlay` 함수가 호출되는 이유를 나타내는 열거형(enum) 타입입니다.
    - 이 함수에서 자원 해제상태 저장을 처리합니다.

 

라이프 사이클 함수에 로그 추가하기

위에서 살펴본 라이프 사이클 함수를 직접 Override 하고, 각 시점에 로그 를 찍어서 실제 호출 순서를 확인해 봅시다.

1.헤더에 라이프 사이클 함수 선언

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h"

// "LogSparta"라는 이름으로 로그 카테고리 선언
DECLARE_LOG_CATEGORY_EXTERN(LogSparta, Warning, All);

UCLASS()
class TESTPROJECT_API AItem : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AItem();

protected:
	USceneComponent* SceneRoot; // 루트 컴포넌트를 나타내는 Scene Component포인터
	UStaticMeshComponent* StaticMeshComp; // Static Mesh Component 포인터


	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	virtual void PostInitializeComponents() override;
	virtual void Tick(float DeltaTime) override;
	virtual void Destroyed() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
};

 

2.CPP파일에서 각 함수 구현 및 로그 출력

// Fill out your copyright notice in the Description page of Project Settings.


#include "Item.h"

// "LogSparta" 카테고리 정의 (헤더에서 선언한 것을 실제로 구현)
DEFINE_LOG_CATEGORY(LogSparta);

// Sets default values
AItem::AItem()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot")); //Scene Component를 생성
	SetRootComponent(SceneRoot); //SceneRoot를 루트로 설정

	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh")); //Static Mesh Component를 생성
	StaticMeshComp->SetupAttachment(SceneRoot); //Scene Component에 Attach

	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);
	}
    UE_LOG(LogSparta, Warning, TEXT("%s Constructor"), *GetName());
}

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

	UE_LOG(LogSparta, Warning, TEXT("%s BeginPlay"), *GetName());
	
}
void AItem::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	UE_LOG(LogSparta, Warning, TEXT("%s PostInitializeComponents"), *GetName());
}

// Called every frame
void AItem::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}
void AItem::Destroyed()
{
	UE_LOG(LogSparta, Warning, TEXT("%s Destroyed"), *GetName());

	Super::Destroyed();
}
void AItem::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	UE_LOG(LogSparta, Warning, TEXT("%s EndPlay"), *GetName());

	Super::EndPlay(EndPlayReason);
}

- GetName()
    - 현재 액터의 이름을 문자열로 반환해 줍니다. 인스턴스마다 다른 이름이 자동으로 붙기 때문에 어떤 액터가 로그를 찍는지 식별하기 좋습니다.
- Tick 메시지 주의
    - `Tick` 함수는 매 프레임 호출되므로, 로그를 찍으면 메시지가 빗발칠 수 있습니다.
    - 디버깅이 필요한 순간에만 제한적으로 사용하거나, 조건문을 통해 특정 상황에서만 로그를 찍는 것이 좋습니다.
- EndPlay vs. Destroyed
    - `EndPlay`는 액터가 월드에서 사라지는 모든 상황 (게임 종료, 레벨 전환, Destroy 호출 등)에 대해 호출됩니다.
    - `Destroyed`는 보통 `Destroy()` 함수가 명시적으로 불렸을 때만 호출되며, 게임 종료나 레벨 언로드 시에는 호출되지 않을 수 있습니다.

 

3.라이프 사이클 로깅 결과

위 코드를 빌드 후,언리얼 에디터에서 플레이를 시작해보면 아래와 같은 순서로 로그가 표시됩니다.

- LogSparta 로그만 출력되도록 로그 필터링을 설정해놓습니다.
- 게임이 진행되는 동안 매 프레임마다 Tick함수가 호출됩니다.

 

게임을 종료하면, EndPlay가 호출되는 것을 확인할 수 있습니다.

이번에는 다시 게임을 실행한 후, 게임 도중 Shift + F1로 마우스를 에디터로 가져온 후, Outliner 창에서 Item 액터를 삭제하면 Destroyed 로그가 출력되고 EndPlay가 출력된 다는 것을 확인할 수 있습니다.

이처럼 액터가 월드에 등장하여 → 동작하고 → 제거되는 흐름을 로그로 확인할 수 있으므로, 각 단계에서 무엇을 초기화/해제해야 할지 명확히 파악하는 데 큰 도움이 됩니다.