Skip to content

UE5 Actor & Component 생명주기

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

Section titled “개요 — 생명주기를 알아야 하는 이유”

UE5에서 Actor와 Component 초기화 순서를 잘못 이해하면 NullPointerException, 초기화 시점 오류, 메모리 릭이 발생합니다. 각 함수가 어느 시점에 호출되는지, 어디서 무엇을 초기화해야 하는지 명확히 이해하는 것이 안정적인 코드의 기초입니다.


[에디터/스폰]
UObject::UObject()
│ (메모리 할당, 기본 초기화)
AActor::AActor() ← 생성자
│ (컴포넌트 생성, 기본값 설정)
PostLoad() / PostActorCreated()
│ (에디터 로드 or 런타임 스폰 직후)
PostInitializeComponents()
│ (모든 컴포넌트 RegisterComponent 완료 후)
PreInitializeComponents() ← [내부 호출 순서상 위치 다름, 참고용]
BeginPlay()
│ (게임 시작 시점 — 레벨 시작 or 스폰 후)
Tick(float DeltaTime) ← 매 프레임 (활성화된 경우)
[Actor 제거 요청: Destroy() / 레벨 언로드]
EndPlay(EEndPlayReason)
│ (리소스 해제, 구독 해제)
BeginDestroy()
│ (GC 수거 시작)
FinishDestroy()
│ (완전 소멸)
[메모리 해제]

생성자는 에디터에서도 호출됩니다. 월드 접근이나 GetWorld()는 사용할 수 없습니다.

AMyCharacter::AMyCharacter()
{
// Tick 활성화 여부 설정
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = false; // 기본 비활성화
// 컴포넌트 생성 (반드시 생성자에서)
HealthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("HealthComponent"));
WeaponComponent = CreateDefaultSubobject<UWeaponComponent>(TEXT("WeaponComponent"));
// SceneComponent 계층 설정
RootComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleComponent"));
MeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("MeshComponent"));
MeshComponent->SetupAttachment(RootComponent);
// UPROPERTY 기본값 설정
MaxHealth = 100.f;
MoveSpeed = 400.f;
// !! 금지: GetWorld(), GetController(), 다른 Actor 접근
}

생성자에서 해야 할 것:

  • CreateDefaultSubobject<T>() — 컴포넌트 생성
  • SetupAttachment() — 컴포넌트 계층
  • 기본값 설정 (UPROPERTY 변수)
  • PrimaryActorTick 설정

생성자에서 하면 안 되는 것:

  • GetWorld() — nullptr
  • GetController() — nullptr
  • 다른 Actor 참조

모든 컴포넌트가 등록(Register)된 후 호출됩니다. 컴포넌트 간 상호참조를 설정하기에 적합합니다.

void AMyCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents(); // 반드시 먼저 호출
// 이 시점에 모든 컴포넌트가 초기화됨
if (HealthComponent)
{
// 컴포넌트 간 델리게이트 연결
HealthComponent->OnDeath.AddUObject(this, &AMyCharacter::HandleDeath);
}
// 메시 컴포넌트 초기화 작업
if (MeshComponent)
{
MeshComponent->SetAnimationMode(EAnimationMode::AnimationBlueprint);
}
// 아직 GetWorld()는 신뢰할 수 없는 타이밍도 있음 (에디터 환경)
}

레벨 시작 시 또는 런타임 스폰 후 첫 Tick 전에 한 번 호출됩니다. 게임 로직 초기화의 주된 위치입니다.

void AMyCharacter::BeginPlay()
{
Super::BeginPlay(); // 반드시 먼저 호출
// GetWorld() 안전 사용 가능
CurrentHealth = MaxHealth;
// 컨트롤러 접근 가능
if (APlayerController* PC = Cast<APlayerController>(GetController()))
{
SetupEnhancedInput(PC);
}
// 서브시스템 접근
if (UWaveManagerSubsystem* WaveMgr = GetWorld()->GetSubsystem<UWaveManagerSubsystem>())
{
WaveMgr->OnWaveStarted.AddUObject(this, &AMyCharacter::HandleWaveStarted);
}
// Tick 활성화 (BeginPlay에서 필요할 때 켜기)
SetActorTickEnabled(true);
UE_LOG(LogTemp, Log, TEXT("%s BeginPlay"), *GetName());
}

void AMyCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime); // 반드시 호출
// DeltaTime: 이전 프레임과의 시간 간격 (초)
// 프레임 독립적 계산에 반드시 사용
RegenerationTimer += DeltaTime;
if (RegenerationTimer >= RegenerationInterval)
{
RegenerationTimer = 0.f;
Heal(RegenerationAmount);
}
}
// Tick 비활성화 — 불필요한 Tick을 끄면 성능 향상
void AMyCharacter::HandleDeath()
{
SetActorTickEnabled(false);
}
AMyCharacter::AMyCharacter()
{
PrimaryActorTick.bCanEverTick = true;
// TG_PrePhysics (기본), TG_DuringPhysics, TG_PostPhysics, TG_PostUpdateWork
PrimaryActorTick.TickGroup = TG_PostPhysics; // 물리 연산 후 실행
}

Actor가 월드에서 제거되기 직전에 호출됩니다. 구독 해제, 타이머 정리, 리소스 해제를 여기서 합니다.

void AMyCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// 타이머 정리
GetWorldTimerManager().ClearAllTimersForObject(this);
// 델리게이트 구독 해제
if (UWaveManagerSubsystem* WaveMgr = GetWorld()->GetSubsystem<UWaveManagerSubsystem>())
{
WaveMgr->OnWaveStarted.RemoveAll(this);
}
// 백그라운드 작업 정리
if (FileLoader)
{
FileLoader->EnsureCompletion();
FileLoader.Reset();
}
// 이유별 처리
switch (EndPlayReason)
{
case EEndPlayReason::Destroyed:
UE_LOG(LogTemp, Log, TEXT("%s was destroyed"), *GetName());
break;
case EEndPlayReason::LevelTransition:
SaveState(); // 레벨 전환 시 상태 저장
break;
case EEndPlayReason::EndPlayInEditor:
break;
default:
break;
}
Super::EndPlay(EndPlayReason); // 마지막에 호출
}
이유설명
DestroyedDestroy() 호출
LevelTransition레벨 전환
EndPlayInEditorPIE 종료
RemovedFromWorld월드에서 제거
Quit게임 종료

UActorComponent::UActorComponent() ← 컴포넌트 생성자
OnRegister() ← 월드에 등록 (에디터 포함)
InitializeComponent() ← bWantsInitializeComponent = true 시
BeginPlay() ← Actor의 BeginPlay 내에서
TickComponent() ← 매 프레임
EndPlay() ← Actor EndPlay 내에서
OnUnregister() ← 월드에서 해제
BeginDestroy() / FinishDestroy()
// Component 초기화 예시
void UHealthComponent::InitializeComponent()
{
Super::InitializeComponent();
CurrentHealth = MaxHealth;
}
void UHealthComponent::BeginPlay()
{
Super::BeginPlay();
// Owner Actor 접근 가능
OwnerActor = GetOwner();
// Owner의 다른 컴포넌트 접근
AnimComponent = OwnerActor->FindComponentByClass<UAnimMontageComponent>();
}

작업권장 위치
컴포넌트 생성 (CreateDefaultSubobject)생성자
기본값 설정생성자
컴포넌트 간 델리게이트 연결PostInitializeComponents
게임 로직 초기화BeginPlay
GetWorld() 사용BeginPlay 이후
타이머 설정BeginPlay
구독 해제·타이머 정리EndPlay
매 프레임 계산Tick

// SpawnActor 호출 시 순서
// 1. 생성자 호출
// 2. PostSpawnInitialize
// 3. PostActorCreated (런타임 스폰)
// 4. ExecuteConstruction (Construction Script)
// 5. PostInitializeComponents
// 6. BeginPlay (레벨 시작 또는 SpawnActor 완료 후)
FActorSpawnParameters Params;
Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
AMyActor* Actor = GetWorld()->SpawnActor<AMyActor>(AMyActor::StaticClass(),
SpawnLocation, SpawnRotation, Params);
// 이 시점에 BeginPlay까지 완료됨

  • 생성자: 컴포넌트 생성·기본값 설정만. GetWorld() 금지
  • PostInitializeComponents: 컴포넌트 간 참조·델리게이트 연결
  • BeginPlay: 게임 로직 초기화, 타이머, 서브시스템 접근
  • Tick: 매 프레임 로직. 불필요하면 비활성화
  • EndPlay: 구독 해제, 타이머 정리, 리소스 반환
  • Super::함수명()을 빠뜨리면 엔진 기본 동작이 누락되어 예측 불가능한 버그 발생