언리얼 엔진에서 Actor는 게임 도중에 언제든지 생성(Spawn)될 수 있고, 필요 없어지면 파괴(Destroy)될 수 있다. 이를 Actor 라이프 사이클이라 부르며, 이 과정을 이해하면 게임 로직을 보다 효율적이고 안정적으로 작성할 수 있다.
1. 액터 라이프 사이클을 알아야 하는 이유
1. 초기화 시점 결정
- 생성자(Constructor), PostInitailizeComponents, BeginPlay 등이 각각 언제 호출되는지 알아야 적절한 곳에 코드를 배치할 수 있다.
- 예) 컴포넌트 생성(CreateDefaultSubobject)은 생성자에서, 다른 액터 참조나 월드 접근은 BeginPlay에서 처리.
2. 성능 관리
- 매 프레임마다 호출되는 Tick 함수는 비용이 클 수 있다.
- 따라서 필요한 액터만 Tick을 활성화하거나 이벤트 기반으로 전환해 최적화해야 한다.
3. 리소스 정리
- 액터가 사라질 때(EndPlay, Destroyed 등) 메모리를 해제하거나 특정 상태를 저장해야 할 수 있다.
- 적절한 시점에 필요한 정리 작업을 하지 않으면 메모리 누수나 예외 상황이 발생할 수 있다.
2. 주요 라이프 사이클 함수
언리얼 엔진의 Actor는
- 생성
- 초기화
- 월드 배치
- Tick(실행)
- 제거
순으로 동작하며, 이를 지원하기 위해 여러 함수가 자동 호출된다.
2 - 1. 생성자(Constructor)
- C++ 클래스 객체가 메모리에 생성될 때 단 한 번 호출한다.
- 아직 월드에 완전히 등록된 상태가 아니므로, 다른 액터나 월드 관련 기능은 안전하게 호출하기 어렵다.
- 보통 컴포넌트 생성(CreateDefaultSubobject) 및 기본 변수 초기화에 사용한다.
2 - 2. PostInitializeComponents()
- 액터의 모든 컴포넌트가 생성/초기화를 마친 뒤 자동으로 호출된다.
- 컴포넌트들이 이미 준비된 상태이므로, 컴포넌트 간 상호작용 초기화 코드를 넣기 좋다.
2 - 3. BeginPlay()
- 게임이 시작되거나 런타임 중 액터가 새로 생성(Spawn)되는 순간에 한 번 호출된다.
- 이 시점에서는 월드와 다른 액터들이 준비된 상태이므로 자유롭게 상호작용 코드를 작성할 수 있다.
- AI, 게임 모드, 플레이어 컨트롤러 등 다른 시스템과의 연동도 보통 이 시점에 처리한다.
2 - 4. Tick(float DeltaTime)
- 매 프레임마다 반복 호출되며, 실시간 업데이트가 필요한 로직(캐릭터 이동, 물리 연산 등)을 넣는다.
- 불필요한 액터에는 Tick을 끄고, 이벤트 기반으로 전환하면 성능을 절약할 수 있다.
2 - 5. Destroyed
- Destroy() 함수를 직접 호출해 액터를 제거하기 직전에 호출된다. (단 게임 종료나 레벨 전환 시에는 호출되지 않을 수 있음.)
- 보통 EndPlay에서 주요 정리를 마치고 Destroyed()에서 메모리 해제나 사운드/파티클 정리등 최종 작업을 수행한다.
- Destroyed가 불리면 마지막에 EndPlay도 같이 호출된다.
2 - 6. EndPlay(const EEndPlayReason::Type EndPlayReason)
- 액터가 더 이상 월드에서 활동하지 않을 때 (파괴, 게임 종료, 레벨 전환 등) 호출된다.
- EEndPlayReason::Type은 언리얼 엔진에서 EndPlay 함수가 호출되는 이유를 나타내는 열거형 타입이다.
- 이 함수에서 자원 해제나 상태 저장을 관리한다.
심화
위 그림을 보면 시작점이 Play in Editor, LoadMap(AddToWorld), SpawnActor, SpawnActorDeferred로 4개가 있다. 이들을 하나씩 자세히 알아보자.
Play in Editor (에디터에서 실행 시)
- Editor에 있는 Actor들은 New World로 복사되고, UObject::PostDuplicate가 호출된다.
- 이후 Actor들이 gameplay를 시작하기 위한 UAISystemBase::InitializeActorsForPlay가 world에 의해 호출된다.
- 초기화되지 않은 Actor에 ULevel::RouteActorInitialize를 호출한다.
- 레벨이 시작되었다는 AActor::BeginPlay를 호출한다.
초기화 순서
- AActor::PreInitializeComponents
- UActorComponent::InitializeComponent
- AActor::PostInitializeComponents
Load from Disk (파일로 게임 실행 시)
UEngine::LoadMap 또는 동적으로 맵의 일부분을 로드하고 언로드하는 레벨 스트리밍에서 UWorld::AddToWorld를 호출할 때 발생한다.
패키지/레벨에 있는 Actor가 Disk에서 로드되고, PostLoad를 호출한다.
전체적인 흐름은 에디터에서 플레이와 매우 유사하다.
Spawn& Deferred Spawn
미리 레벨에 배치되지 않은 Actor를 UWorld::SpawnActor로 Spawn 할 때의 LifeCycle이다.
- Actor가 World에 Spawn된 이후 AActor::PostSpawnInitialize를 호출한다.
- Spawn된 Actor의 생성 이후 AActor::PostActorCreated를 호출한다. 생성자와 관련된 구현을 담는 함수로 PostLoad와 배타적이다.
- AActor::ExecuteConstruction -> AActor::OnConstruction(블루 프린트 Actor의 컴포넌트 생성, 변수 초기화 시점) -> AActor::PostActorConstuction
- 미리 배치된 Actor와 마찬가지로 컴포넌트 초기화
- UWorld::OnActorSpawned로 Delegate Broadcast 이후 AActor::BeginPlay를 호출한다.
End of Actor Lifecycle
Destroy, Lifetime 종료, Level Transition, Editor에서 플레이 종료, Game End, applicaition closing에 의해 EndPlay가 호출되고 Actor는 RF_PendingKill로 마킹된다.
이후 ULevel의 Actor Array에서 해당 Actor는 제거되고 다음 garbage collection cycle에 마킹된 Actor의 메모리를 할당 해제 한다.
garbage collection의 과정에서 UObject::BeginDestroy, UObject::IsReadyForFinishDestroy(준비가 되지 않았다면 다음 cycle에서 진행한다.), UObject::FinisihDestroy의 함수가 추가로 호출된다.
참고 블로그
'Unreal' 카테고리의 다른 글
Unreal에서 Tick의 DeltaTime과 Transform 조정 (0) | 2025.01.23 |
---|---|
Class Default Object(CDO)에 대한 간단한 이해 (0) | 2025.01.22 |
언리얼에서 컴포넌트란? (0) | 2025.01.22 |
기본 Actor 클래스 코드 구조 (0) | 2025.01.22 |
Object와 Actor 차이점 정리 (0) | 2025.01.21 |