일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- RNN
- 언리얼엔진
- multimodal
- Generative Model
- 디퓨전모델
- motion matching
- 오블완
- cv
- Few-shot generation
- BERT
- animation retargeting
- dl
- 모션매칭
- WinAPI
- Unreal Engine
- Font Generation
- deep learning
- Stat110
- 딥러닝
- UE5
- CNN
- ddpm
- WBP
- NLP
- 생성모델
- userwidget
- 폰트생성
- Diffusion
- ue5.4
- GAN
- Today
- Total
Deeper Learning
[UE5] Actor Lifecycle (액터 생애주기) 본문
Actor Lifecycle
언리얼 엔진에서 기능을 추가할 때, 어느 클래스에 해당 기능을 추가할 것인가, 해당 클래스의 어느 함수를 Override하고 그 함수의 호출 시점은 어떻게 되는가는 항상 고민하는 부분이다.
레벨에 배치할 수 있는 오브젝트인 Actor 클래스의 사전 정의된 함수의 호출, 목적에 대한 이해가 필요하면 이를 결정하는 것이 더 수월해질 것이라고 생각하여 이번 포스팅은 Actor의 Lifecycle에 대한 포스팅이다.
언리얼 엔진 공식문서의 Actor의 Lifecycle을 도식화한 사진이다.
시작점이 Play in Editor, LoadMap(AddToWorld), SpawnActor, SpawnActorDeferred로 4개가 있다.
Empty Level을 만들고 Actor를 상속한 C++ Class에서 Log를 찍어보며 하나씩 자세히 알아보자.
호출 함수의 이름, 객체의 주소값을 로깅할 수 있도록 커스텀 로그 매크로를 정의하였다.
// ActorLifecycle.h
#pragma once
#include "CoreMinimal.h"
DECLARE_LOG_CATEGORY_EXTERN(ActorLifeCycle, Log, All);
#define CLog(Msg, Addr) UE_LOG(ActorLifeCycle, Warning, TEXT("[%s](%s): %s"), *FString(__FUNCTION__), *FString::Printf(TEXT("%p"), Addr), *FString(Msg));
// ActorLifecycle.cpp
#include "ActorLifecycle.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ActorLifecycle, "ActorLifecycle" );
DEFINE_LOG_CATEGORY(ActorLifeCycle);
Play in Editor
1. Editor에 있는 Actor들은 New World로 복사되고 UObject::PostDuplicate가 호출된다.
2. 이후 Actor 들이 gameplay를 시작하기 위한 UAISystemBase::InitializeActorsForPlay가 world에 의해 호출된다.
3. 초기화되지 않은 Actor에 ULevel::RouteActorInitialize를 호출
3.1 AActor::PreInitializeComponents -> UActorComponent::InitalizeComponent -> AActor::PostInitializeComponents
4. 레벨이 시작되었다는 AActor::BeginPlay를 호출한다.
이제 아무 Actor가 배치되지 않은 Empty Level에서 Actor 클래스를 상속하여 만든 LifecycleBreakDown Actor의 Constructor, PostDuplicate, BeginPlay에 아래와 같이 함수 시작, 끝에 로깅하도록 하고 빌드를 시작해보자
void ALifecycleBreakDown::PostDuplicate(bool bDuplicateForPIE)
{
CLog("START", this);
Super::PostDuplicate(bDuplicateForPIE);
CLog("END",this);
}
빌드 후 에디터를 실행하자 로그에 이미 Actor의 생성자 함수가 호출된 것을 볼 수 있었다.
ActorLifecycle: Warning: [ALifecycleBreakDown::ALifecycleBreakDown](0000091767DEAA00): START
ActorLifecycle: Warning: [ALifecycleBreakDown::ALifecycleBreakDown](0000091767DEAA00): END
이는 Class Default Object(CDO)와 관련이 있다. 이번 포스팅에서 다루기에는 너무 방대하여 간단하게 설명하자면 언리얼 엔진의 Object인 UObject는 Default Object인 기본 세팅으로 초기화 된 CDO를 미리 만들어 두고 이를 복사하는 방식으로 다른 Instance를 만든다.
위 로그에 찍힌 주소는 에디터를 초기화하면서 Actor의 CDO의 주소값으로 이를 확인하기 위해 아래 코드를 BeginPlay 함수 가장 윗부분에 추가하였다.
* 0000091767DEAA00 주소를 기억하자
UE_LOG(LogTemp, Error, TEXT("[%s]: %s"), TEXT("CDO"), *FString::Printf(TEXT("%p"), GetDefault<ALifecycleBreakDown>()));
만든 Actor를 Viewport로 Drag&Drop 후 로그는 아래와 같다
ActorLifeCycle: Warning: [ALifecycleBreakDown::ALifecycleBreakDown](000009177AA8DC00): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::ALifecycleBreakDown](000009177AA8DC00): END
ActorLifeCycle: Warning: [ALifecycleBreakDown::ALifecycleBreakDown](000009177AADCD00): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::ALifecycleBreakDown](000009177AADCD00): END
Viewport로 Drag 할 때, 생성자가 한 번 호출되고 Drop 하면 다른 Instance가 생성된다.
이에 대해 찾아보니 Preview Asset과 배치하였을 때 Asset이 다르기 때문이라고 한다. 이에 대해 더 자세히 보려면 Editor 내에서 동작에 따라 호출되는 함수들에 Log를 찍어보면 확인이 가능하다.
ActorLifeCycle: Warning: [ALifecycleBreakDown::ALifecycleBreakDown](000009177D157300): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::ALifecycleBreakDown](000009177D157300): END
ActorLifeCycle: Warning: [ALifecycleBreakDown::PostDuplicate](000009177D157300): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::PostDuplicate](000009177D157300): END
ActorLifeCycle: Warning: [ALifecycleBreakDown::PreInitializeComponents](000009177D157300): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::PreInitializeComponents](000009177D157300): END
ActorLifeCycle: Warning: [ALifecycleBreakDown::PostInitializeComponents](000009177D157300): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::PostInitializeComponents](000009177D157300): END
LogTemp: Error: [CDO]: 0000091767DEAA00
ActorLifeCycle: Warning: [ALifecycleBreakDown::BeginPlay](000009177D157300): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::BeginPlay](000009177D157300): END
공식문서의 설명대로 생성자 -> PostDuplicate -> PreInitialize Components -> PostInitializeComponents -> BeginPlay 순으로 실행되는 것을 확인할 수 있다.
추가로 Actor CDO의 주소가 앞서 에디터를 실행할 때 확인한 주소와 같은 것을 볼 수 있다.
앞서 에디터에서 배치한 Actor와 에디터에서 레벨을 플레이하고 나서 생성된 Actor가 다른 주소를 가진 것으로 Editor에 있는 Actor들이 New World로 복사된 것을 확인할 수 있다.
Load from Disk
이미 레벨에 있는 Actor에 대해 발생하는 루트로 UEngine::LoadMap 또는 동적으로 맵의 일부분을 로드하고 언로드하는 레벨 스트리밍에서 UWorld::AddToWorld를 호출할 때 발생한다.
패키지/레벨에 있는 Actor가 Disk에서 로드되고 PostLoad를 호출. 전체적인 흐름은 에디터에서 플레이와 매우 유사하기에 따로 다루지는 않으려한다.
Spawn & Deferred Spawn
미리 레벨에 배치되지 않은 Actor를 UWorld::SpawnActor로 Spawn할 때의 Lifecycle이다.
1. Actor가 World에 Spawn된 이후 AActor::PostSpawnInitialize를 호출
2. Spawn된 Actor의 생성 이후 AActor::PostActorCreated를 호출. 생성자와 관련된 구현을 담는 함수로 PostLoad와 배타적이다 (Disk에서 로드하거나 생성되거나)
3. AActor::ExecuteConstruction -> AActor::OnConstruction(블루프린트 Actor의 컴포넌트 생성, 변수 초기화 시점) -> AActor::PostActorConstruction
4. 미리 배치된 Actor와 마찬가지로 컴포넌트 초기화
4.1 AActor::PreInitializeComponents -> UActorComponent::InitalizeComponent -> AActor::PostInitializeComponents
5. UWorld::OnActorSpawned로 Delegate Broadcast 이후 AActor::BeginPlay를 호출한다.
주요 함수들을 Override하여 마찬가지로 로깅하도록 하였고 블루프린트로 아래와 같이 Spawner Actor를 만들고 Level에 배치 후 실행하였다.
LogBlueprintUserMessages: Spawner PreSpawnActor
ActorLifeCycle: Warning: [ALifecycleBreakDown::ALifecycleBreakDown](0000066595360000): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::ALifecycleBreakDown](0000066595360000): END
LogBlueprintUserMessages: Construction Script
ActorLifeCycle: Warning: [ALifecycleBreakDown::PreInitializeComponents](0000066595360000): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::PreInitializeComponents](0000066595360000): END
ActorLifeCycle: Warning: [ALifecycleBreakDown::PostInitializeComponents](0000066595360000): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::PostInitializeComponents](0000066595360000): END
LogTemp: Error: [CDO]: 00000665A639A000
ActorLifeCycle: Warning: [ALifecycleBreakDown::BeginPlay](0000066595360000): START
ActorLifeCycle: Warning: [ALifecycleBreakDown::BeginPlay](0000066595360000): END
LogBlueprintUserMessages: Spawner PostSpawnActor
블루프린트에서 Construction Scirpt에 Log String 노드를 추가하여 Construction 시점도 공식문서의 도식과 일치함을 확인하였다.
Deferred Spawn은 Spawn과 거의 동일하지만 PostActorCreated 이후, 블루프린트 Consturction 전에 초기화 함수를 구성한 뒤 AActor::FinishingSpawning을 호출하여 Spawn을 마무리하는 형태이다. 변수의 초기화 값의 변경이 동적으로 필요한 Actor를 Spawn할 때 유용하다.
End of Actor Lifecycle
Destroy, Lifetime 종료, Level Transition, Editor에서 플레이 종료, Game End, application closing 에 의해 EndPlay가 호출되고 Actor는 RF_PendingKill로 마킹된다.
이후 ULevel의 Actor Array에서 해당 Actor는 제거되고 다음 garbage collection cycle에 마킹된 Actor의 메모리를 할당해제 한다.
garbage collection의 과정에서 UObject::BeginDestroy, UObject::IsReadyForFinishDestroy(준비가 되지 않았다면 다음 cycle에 다시 진행한다), UObject::FinishDestroy 의 함수가 추가로 호출된다.
Actor Lifecycle에 대해 찾아보고 학습하고나서 Actor의 다른 클래스와 상호작용 관련 로직을 생성자에 넣지 않는 이유(넣을 수 없는 이유), Actor의 Component를 다루는 로직을 BeginPlay 또는 PostInitializeComponents를 Override하는 방식으로 구현하는지에 대해 어느정도 알게 되었다.
'Game Development > Unreal Engine' 카테고리의 다른 글
[UE5] 언리얼 엔진 클래스 선택 및 설계 (0) | 2024.05.22 |
---|---|
[UE5] 물체 옮기기 기능 구현 (Grabber) (0) | 2024.05.19 |
[UE5] Revision Control - Git (0) | 2024.05.15 |
[UE5] Behavior Tree로 순찰, 추적 AI 만들기 (0) | 2024.05.11 |
[UE5] Delegate(델리게이트) (0) | 2024.05.08 |