언리얼 - 엔진 9 : Actor 생명주기

김정환·2025년 4월 9일
0

Unreal Engine

목록 보기
10/24

Actor 생명주기 Life Cycle

  • 게임 도중에 Actor는 언제든지 생성(Spawn)되고, 필요없어지면 파괴(Destroy)될 수 있음.
  • Actor가 생성되고 파괴되기까지의 과정을 생명주기 Life Cycle이라 함.
  • 이 과정을 이해하면 게임 로직을 보다 효율적이고 안정적으로 작성할 수 있음.

생명주기를 알아야하는 이유

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

주요 생명주기 함수

언리얼 엔진의 Actor 동작 순서
1. 생성 -> 2. 초기화 -> 3. 월드 배치 -> 4. Tick(실행) -> 5. 제거 순으로 동작

// 헤더
#pragma once

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

DECLARE_LOG_CATEGORY_EXTERN(LogItem, Display, All);

UCLASS()
class STUDY_API AItem : public AActor	
{
	GENERATED_BODY()
	
public:	
	AItem();	// 생성자. 초기화 계열.

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 파일
#include "Item.h"

DEFINE_LOG_CATEGORY(LogItem);

AItem::AItem()
{
	// Tick을 활성화해, 매 프레임 Tick 함수가 호출되도록 설정
	PrimaryActorTick.bCanEverTick = true;
}

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

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

void AItem::BeginPlay()
{
	Super::BeginPlay();		// 반드시 부모의 함수도 호출해줄 것.

	UE_LOG(LogItem, Warning, TEXT("%s BeginPlay"), *GetName());
}

void AItem::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);		// 반드시 부모의 함수도 호출해줄 것.
}

// 소멸 이벤트 함수들은 부모 함수 호출 전에 로그를 찍도록 함
// 상위 함수 호출과 함께 사라지는 것을 방지하기 위함.
void AItem::Destroyed()
{
	UE_LOG(LogItem, Warning, TEXT("%s Destroyed"), *GetName());

	Super::Destroyed();
}

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

	Super::EndPlay(EndPlayReason);
}

1. 생성자 Constructor

AItem();
  • 호출 시점 : C++ 클래스 객체가 메모리에 생성될 때 딱 한 번 호출.
  • 이 시점에선 아직 액터가 월드에 완전히 등록되지 않은 상태이므로
    다른 액터월드 관련 기능을 안전하게 호출하기 어려움.
  • 주로 CreateDefaultSubobject 등으로 컴포넌트 생성변수 초기화를 수행

2. PostInitializeComponents()

virtual void PostInitializeComponents() override;
  • 호출 시점 : 액터의 모든 컴포넌트가 생성 및 초기화된 뒤 자동 호출.
  • 각 컴포넌트가 이미 준비된 상태이므로, 컴포넌트 간 상호작용이 필요한 코드를 배치하기 좋음.
    • 컴포넌트끼리 데이터 주고받기, 서로 다른 컴포넌트 참조 설정 등등
  • 보통 생성자에선 단순한 생성, 할당만 하고,
    PostInitializeComponents()에서 컴포넌트들 간 의존 관계를 설정.

3. BeginPlay()

virtual void BeginPlay() override;
  • 호출 시점 :
    Play In Editor (PIE)런타임에서 게임이 시작될 때, 또는 SpawnActor 등으로 새 액터가 생성될 때 한 번 호출.
  • 이 시점에는 이미 월드와 다른 액터들이 준비된 상태이므로 자유롭게 상호작용 가능.
  • AI, 게임 모드, 플레이어 컨트롤러 등 다른 시스템과 연동도 주로 BeginPlay에서 초기화.
  • 타이머, Delegate(Event) 바인딩 등을 시작하기에도 적합.

4. Tick(float DeltaTime)

virtual void Tick(float DeltaTime) override;
  • 호출 시점 : 매 프레임마다 호출.
    • 액터의 PrimaryActorTick.bCanEverTick = true; 설정 필요.
  • 실시간 업데이트가 필요한 로직에 사용.
    • 캐릭터 이동, 물리 연산, 카메라 추적 등
  • 성능저하가 클 수 있어 신중히 사용
  • 이벤트 기반으로 전환할 수 있는 부분은 Tick을 쓰지 않는 것이 성능에 유리.

5. Destroyed()

virtual void Destroyed() override;
  • 호출 시점 : Destroy() 함수를 직접 호출하여 액터를 제거할 때 직전에 호출됩니다.
  • 다만, 레벨 전환이나 게임 종료 시에는 종종 건너뛰어지기도 하므로(상황마다 엔진 동작이 다름.) 절대적으로 보장되는 것은 아님.
  • Destroyed()가 불린 뒤에는 최종적으로 EndPlay()함께 호출됩니다.
  • 수동으로 액터를 제거할 때, 마지막 정리 코드를 넣을 수 있는 곳입니다.
    • 단, 위에서 말한 상황들에는 호출되지 않으므로
      모든 중요 정리를 여기에 배치하면 놓치는 케이스가 생길 수 있음.
  • 정리할 자원 예시
    • 수동 할당한 메모리: new 또는 동적 할당한 오브젝트가 있다면 여기서 delete하거나 해제합니다.
    • 스폰된 자식 액터: 이 액터가 생성한 다른 액터나 컴포넌트 중, 자동으로 해제되지 않는 것이 있다면 제거 처리합니다.
    • Delegate / Event 바인딩: 게임 전역적 또는 외부 클래스에 바인딩해둔 델리게이트가 있다면 해제합니다.
    • 사운드/파티클 등: 필요 시 이 액터가 재생 중인 사운드나 파티클을 수동으로 정리합니다.

6. EndPlay(const EEndPlayReason::Type EndPlayReason)

virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
  • 호출 시점 : 액터가 더 이상 월드에서 활동하지 않게 될 때 호출됩니다.
    • 파괴, 레벨 전환, 게임 종료 등
  • EEndPlayReason::Type : 어떤 이유로 EndPlay가 호출됐는지 구분.
  • 게임 종료레벨 언로드 같은 상황에서도 EndPlay()는 상대적으로 호출 보장이 높음.
    • 단, Destroyed()는 건너뛸 수 있음.
    • 그래서 중요한 정리 로직EndPlay()에서 수행하는 것이 안전.
      • 자원 해제, Timer 해제, 상태 저장 등
  • 정리할 자원 예시
    • 타이머: GetWorldTimerManager().ClearTimer(…) 와 같이 타이머를 정리합니다.
    • 동적 할당 리소스: 여전히 해제되지 않은 동적 메모리가 남아 있다면 여기서 정리합니다.
    • 데이터 저장: 게임 진행 상황 (점수, 인벤토리 등)을 파일/DB에 저장하거나, 상위 시스템에 콜백을 보내는 로직도 EndPlay에서 처리 가능합니다.
profile
만성피로 개발자

0개의 댓글