본문 바로가기

[C++과 Unreal Engine으로 3D 게임 개발] Actor의 라이프 사이클

@iamrain2025. 9. 16. 12:41

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

언리얼 엔진에서 `UE_LOG` 매크로를 사용해 Output Log 창에 메시지를 남길 수 있다.

`BeginPlay()` 함수에 로그 추가

`Item.h`

...

UCLASS()
class SPARTAPROJECT_API AItem : public AActor
{
	...

protected:
	...

	virtual void BeginPlay() override;
};

`Item.cpp`

...

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

	UE_LOG(LogTemp, Warning, TEXT("My Item appears!!"));
}
  • `UE_LOG(LogTemp, Warning, TEXT(" "))`
    • 로그 카테고리 (Log Category) : `LogTemp`라는 임시 카테고리 사용.
    • 로그 수준 (Log Level) : 다양한 로그 수준이 존재.
      • `Display` : 일반적인 실행 흐름이나 상태 확인 메시지 (흰색)
      • `Warning` : 예상치 못한 동작이나 잠재적인 문제 (노란색)
      • `Error` : 즉시 수정이 필요한 심각한 문제 (빨간색)
    • 출력할 메시지 : 원하는 문자열 입력.

 

고유한 카테고리 정의 후 로그 추가

프로젝트 규모가 커져 로그를 세세하게 분류해야 할 경우 `DEFINE_LOG_CATEGORY`를 사용해 고유한 카테고리를 정의할 수 있다.

`.h`

#pargma once

...

DECLARE_LOG_CATEGORY_EXTERN(LogMine, Warning, All);

UCLASS()
class ...
{
	...
};
  • `DECLARE_LOG_CATEGORY_EXTERN(LogMine, Warning, All);`
    • 헤더 파일에서 로그 카테고리를 선언
    • `LogMine` : 카테고리 이름 (사용자 지정)
    • `Warning` : 이 카테고리 사용 시 `Warning` 이상 로그만 출력
    • `All` : 필요하면 모든 로그 활성화 허용

`.cpp`

#include "Item.h"

DEFINE_LOG_CATEGORY(LogMine);

AItem::AItem()
{
	...
}

void AItem::BeginPlay()
{
	...

	UE_LOG(LogMine, Error, TEXT("This is Mine!!"));
}

 

빌드 후 언리얼 에디터에서 Actor가 배치된 상태로 실행하면 아래와 같이 로그가 출력되는 것을 볼 수 있다.

 

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

액터는 언제든 생성되고 파괴될 수 있다. 이를 라이프 사이클이라 부르며, 이해하면 게임 로직을 좀 더 효율적이고 안정적으로 작성할 수 있다.

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

  • 초기화 시점 결정
    • 생성자, `PostInitializeComponents`, `BeginPlay` 등 언제 호출되는지 알아야 적절한 곳에 코드를 배치할 수 있다.
  • 성능 관리
    • 매 프레임마다 호출되는 `Tick` 함수는 비용이 크다. 따라서 필요한 액터만 활성화하거나 이벤트 기반으로 전환해 최적화해야 한다.
  • 리소스 정리
    • 액터가 사라질 때 메모리를 해제하거나 특정 상태를 저장해야 할 수 있다. 적절한 시점에 정리 작업을 하지 않으면 메모리 누수나 예외가 발생할 수 있다.

주요 라이프 사이클 함수

액터는 생성 → 초기화 → 월드 배치 → 실행 → 제거 순으로 동작하며, 이를 지원하기 위해 여러 함수가 자동 호출된다.

  1. 생성자 (Constructor)
    • 호출 시점 : C++ 클래스 객체가 메모리에 생성될 때 한 번만 호출
    • 액터가 월드에 완전히 등록되지 않은 상태로, 다른 액터나 월드 관련 기능을 안전하게 호출하기 어렵다.
    • 주로 `CreateDefaultSubobject` 등을 사용해 컴포넌트 생성 및 초기 변수 세팅에 활용한다.
  2. PostInitializecomponents()
    • 호출 시점 : 액터의 모든 컴포넌트가 생성 및 초기화된 후 자동 호출
    • 각 컴포넌트가 준비된 상태 → 컴포넌트 간 상호작용이 필요한 코드를 배치하기 좋다.
    • 보통 생성자에서 단순 "생성 / 할당"만 하고 `PostInitializecomponents()`에서 컴포넌트 사이의 의존 관계를 설정한다.
  3. BeginPlay()
    • 호출 시점 : Play In Editor(PIE)나 런타임에서 게임이 시작될 때, 혹은 이미 실행 중인 게임에 `SpawnActor` 등 새 액터가 생성될 때 한 번만 호출
    • 이미 월드와 다른 액터들이 준비된 상태이므로, 자유롭게 상호작용이 가능하다.
    • AI, 게임 모드, 플레이어 컨트롤러 등 다른 시스템과 연동도 주로 BeginPlay에서 초기화하며, 타이머, Delegate(Event) 바인딩 등을 시작하기에도 적합하다.
  4. Tick(float DeltaTime)
    • 호출 시점 : 매 프레임마다 호출 (액터의 `PrimaryActorTick.bCanEverTick = true;` 설정 필요
    • 실시간 업데이트가 필요한 로직을 처리한다.
    • 이벤트 기반으로 전환할 수 있는 부분은 `Tick`을 사용하지 않는 편이 성능에 유리하다.
  5. Destroyed()
    • 호출 시점 : `Destroy()` 함수를 직접 호출하여 액터를 제거할 때 직전에 호출 (단, 레벨 전환이나 게임 종료 시 종종 건너뛰어짐)
    • `Destroyed()`가 불린 뒤 최종적으로 `EndPlay()`도 함께 호출된다.
    • 수동으로 액터를 제거할 때, 마지막 정리 코드를 넣을 수 있는 곳이다. 하지만 종종 건너뛰는 케이스가 있기 때문에 중요한 정리를 모두 의존하면 안된다.
    • 정리할 자원 예시
      • 수동으로 할당한 메모리 : `new` 또는 동적 할당한 오브젝트가 있다면 여기서 `delete` 하거나 해제
      • 스폰된 자식 액터 : 이 액터가 생성한 다른 액터나 컴포넌트 중 자동으로 해제되지 않는 것이 있다면 제거
      • Delegate / Event 바인딩 : 게임 전역적 또는 외부 클래스에 바인딩해 둔 델리게이트가 있다면 해제
      • 사운드 / 파티클 등 : 필요 시 이 액터가 재생 중인 사운드나 파티클을 수동으로 정리
  6. EndPlay(const EEndPlayReason::Type EndPlayReason)
    • 호출 시점 : 액터가 더 이상 월드에서 활동하지 않을 때 호출 (파괴, 레벨 전환, 게임 종료 등)
    • `EEndPlayReason::Type`으로 어떤 이유로 EndPlay가 호출되었는지 구분한다.
    • 게임 종료나 레벨 언로드 같은 상황에서도 `EndPlay()`는 호출 보장이 높지만, `Destroyed()`는 건너뛸 수 있다.
      • 따라서 중요한 정리 로직은 `EndPlay()`에 넣는 것이 보다 안전하다.
    • 정리할 자원 예시
      • 타이머 : `GetWorldTimeManager().ClearTime(...)`와 같이 타이머를 정리
      • 동적 할당 리소스 : 해제되지 않은 동적 메모리가 있다면 정리
      • 데이터 저장 : 진행 상황을 파일 혹은 DB에 저장하거나, 상위 시스템에 콜백을 보내는 로직도 처리 가능

 

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

헤더에 함수 선언

...

UCLASS()
class SPARTAPROJECT_API AItem : public AActor
{
	...

protected:
	...

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

CPP 파일에 구현 및 출력

...

AItem::AItem()
{
	PrimaryActorTick.bCanEverTick = true;

	...

	UE_LOG(LogMine, Warning, TEXT("%s Constructor"), *GetName());
}

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

	UE_LOG(LogMine, Warning, TEXT("%s PostInitializeComponets"), *GetName());
}

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

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

	UE_LOG(LogMine, Error, TEXT("This is Mine!!"));
}

void AItem::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
}

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

	Super::Destroyed();
}

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

	Super::EndPlay(EndPlayReason);
}

 

빌드를 하고 언리얼 에디터에서 Actor를 추가한 후 실행해보면 아래와 같은 로그를 볼 수 있다.

iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차