액터 초기화와 종료 이벤트
Construction Script와 BeginPlay·EndPlay·Destroyed의 호출 시점을 구분해 생성부터 제거까지 수명 주기를 다룹니다.
문의 종류를 바꾸면 에디터 뷰포트에서 바로 모양이 바뀌었으면 좋겠고, 플레이가 시작되면 체력을 100으로 맞췄으면 좋겠고, 적이 사라질 때는 켜 두었던 타이머를 멈췄으면 좋겠습니다. 세 가지 모두 "언제 실행되는가"가 다른 일입니다. 이 시점을 헷갈려 초기화 로직을 엉뚱한 이벤트에 두면, 에디터에서 배치만 했는데 무언가 실행되거나, 레벨을 바꿀 때마다 보상 아이템이 떨어지는 문제가 생깁니다.
이 글에서는 액터가 구성되고, 플레이를 시작하고, 플레이를 끝내는 시점에 맞춰 호출되는 블루프린트 이벤트를 살펴봅니다. 이 이벤트들은 C++ 객체의 생성자(constructor)나 소멸자(destructor)와 같은 것이 아니므로, 각 이벤트가 정확히 언제 오는지를 구분해 두는 것이 목표입니다.
에디터에서도 다시 실행되는 Construction Script
컨스트럭션 스크립트(Construction Script)는 액터가 구성되는 과정에서 실행되는 그래프입니다. 블루프린트 에디터에서 이벤트 그래프 옆의 Construction Script 탭에 있으며, 에디터에서 액터를 배치하거나, 속성을 바꾸거나, 옮기는 등의 작업에 따라 여러 번 다시 실행될 수 있습니다. 다만 모든 편집 동작마다 정확히 한 번씩 실행된다고 기대할 수 있는 이벤트는 아닙니다.
그래서 Construction Script는 에디터에서 액터의 외형이나 구성을 바로 확인하고 싶을 때 씁니다. 문의 방향을 바꾸면 문틀이 함께 돌아가게 하거나, 아이템의 색을 바꾸면 바로 재질에 반영되게 하는 미리보기 기능이 대표적입니다. 예를 들어 DoorType이라는 열거형 변수의 값에 따라 문의 스태틱 메시를 바꾸도록 Construction Script를 만들어 두면, 레벨에 놓인 문의 디테일 패널에서 DoorType을 바꾸자마자 뷰포트의 문 모양이 바뀝니다. 조건에 따라 컴포넌트를 추가하거나 빼는 에디터 시점의 로직에도 쓸 수 있습니다.
플레이 중에 스폰되는 액터도 구성 단계를 거칩니다. 공식 문서의 액터 수명 주기 설명에 따르면, 스폰 경로에서는 구성 단계에서 Construction Script가 실행되어 블루프린트의 컴포넌트와 변수가 준비된 뒤 BeginPlay로 넘어갑니다. 반면 레벨에 미리 배치해 저장한 액터를 불러오는 경로나 PIE(Play In Editor)를 위해 에디터 월드를 복제하는 경로는 스폰 경로와 단계가 다르므로, 스폰 경로의 실행 횟수를 모든 경우에 그대로 적용하지는 않습니다. 구성 단계에서 정한 값은 이후 BeginPlay의 로직에서 다시 바꿀 수 있습니다.
플레이가 시작될 때의 BeginPlay
Event BeginPlay는 액터가 플레이를 시작할 때 호출됩니다. 레벨이 시작될 때부터 배치되어 있던 액터뿐 아니라, 플레이 도중에 스폰된 액터도 구성과 초기화를 마친 뒤 이 이벤트에 도착합니다. 반대로 에디터에서 액터를 배치하거나 고치는 동안에는 실행되지 않습니다. 게임이 실제로 시작되어야 발생하는 이벤트이기 때문입니다.
그래서 BeginPlay는 게임 플레이에 필요한 초기화에 가장 흔히 쓰입니다. 플레이어의 현재 체력을 100으로 맞추고 시작 메시지를 출력하거나, UI를 준비하거나, 적 AI의 첫 행동을 시작하거나, 다른 액터나 컴포넌트의 참조를 찾아 변수에 저장하는 일이 여기에 들어갑니다. 에디터의 미리보기와 플레이의 초기 상태를 나누어, 보이는 모습은 Construction Script에, 게임 상태는 BeginPlay에 두면 두 작업이 서로 간섭하지 않습니다.
플레이가 끝날 때의 EndPlay
Event EndPlay는 액터가 플레이를 끝낼 때 호출됩니다. 공식 문서는 EndPlay가 호출되는 경우로 Destroy Actor를 명시적으로 호출했을 때, PIE를 끝냈을 때, 레벨을 전환할 때, 그 액터가 든 스트리밍 레벨을 언로드할 때, 액터의 수명(life span)이 다했을 때, 애플리케이션을 종료할 때를 듭니다. 이벤트의 End Play Reason 핀으로 이 가운데 어떤 이유로 끝났는지 구분할 수 있습니다.
EndPlay는 액터가 사라지기 전에 필요한 마무리 작업을 하는 곳입니다. 재생 중인 사운드를 멈추거나, 켜 둔 이펙트를 없애거나, 직접 등록한 이벤트 구독과 타이머를 해제하는 일이 여기에 들어갑니다. 레벨을 바꾸거나 게임을 끝낼 때 화면에 띄워 둔 UI 위젯을 제거하는 것도 EndPlay에서 처리하기 좋습니다.
EndPlay가 여러 이유로 호출된다는 점은 주의할 부분이기도 합니다. 적이 죽을 때 폭발 효과와 아이템 드롭을 하고 싶다고 모든 EndPlay에 드롭을 연결하면, 레벨을 전환하거나 게임을 끝낼 때도 보상이 생깁니다. 사망 처리에서는 효과와 드롭을 한 번 실행한 뒤 Destroy Actor로 제거하고, EndPlay에는 어떤 이유로 끝나든 필요한 정리만 둡니다.
Destroyed와 메모리 회수
Event Destroyed는 명시적으로 액터를 파괴할 때 반응하는 이벤트입니다. 레벨 언로드나 플레이 종료까지 모두 포함하는 정리가 필요하다면 Destroyed가 아니라 EndPlay를 씁니다. 공식 문서도 Destroyed를 예전 방식의 반응으로 소개하며, 그 로직을 EndPlay로 옮기기를 권합니다.
또 EndPlay가 실행되었다고 그 순간 액터의 메모리가 해제되는 것은 아닙니다. 파괴된 액터는 제거 대상으로 표시되어 레벨에서 빠지고, 실제 메모리는 이후 엔진의 가비지 컬렉션(garbage collection)이 회수합니다. 그래서 EndPlay에서는 메모리를 직접 해제하려 하지 말고, 게임플레이에서 쓰던 것들을 정리하는 데 집중합니다.
세 경로를 한 그림으로 구분하기
지금까지 본 이벤트는 하나의 고정된 순서로 이어지지 않습니다. 에디터에서 편집하는 경로, 플레이 중에 스폰하는 경로, 플레이를 끝내는 경로가 각각 다르기 때문입니다.
구성 재실행과 플레이의 시작·종료를 구분합니다
런타임 스폰으로 구성·초기화→BeginPlay→종료 이유에 따른 EndPlay 정리→이후 객체 관리의 수명 상태를 보여 준다. 에디터 구성 재실행은 별도 조건이고 GC 시점을 고정하지 않는다.
EDITOR CONFIGURATION · PLAY · CLEANUP
구성 재실행과 플레이의 시작·종료를 구분합니다
Construction Script의 실행 횟수는 생성 경로와 편집 조건에 따라 달라집니다. 에디터 작업, 런타임 스폰, 플레이 종료를 하나의 고정된 순서로 묶지 않습니다.
미리 배치해 저장한 액터의 로드·PIE 복사까지 스폰 경로의 구성 횟수로 일반화하지 않습니다. Destroyed는 명시적 파괴에 반응하며, 레벨 언로드·플레이 종료까지 포괄하는 정리는 EndPlay에서 합니다. 사망 보상은 사망 조건에서 한 번 처리합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 런타임 구성 스폰한 객체를 구성·초기화한다. |
|
|
| 플레이 수명 BeginPlay 뒤 사용 가능한 실행 상태다. |
|
|
| EndPlay 정리 종료 이유에 맞게 구독·자원을 정리한다. |
|
|
구성 재실행과 플레이의 시작·종료를 구분합니다
런타임 스폰으로 구성·초기화→BeginPlay→종료 이유에 따른 EndPlay 정리→이후 객체 관리의 수명 상태를 보여 준다. 에디터 구성 재실행은 별도 조건이고 GC 시점을 고정하지 않는다.
EDITOR CONFIGURATION · PLAY · CLEANUP
구성 재실행과 플레이의 시작·종료를 구분합니다
Construction Script의 실행 횟수는 생성 경로와 편집 조건에 따라 달라집니다. 에디터 작업, 런타임 스폰, 플레이 종료를 하나의 고정된 순서로 묶지 않습니다.
미리 배치해 저장한 액터의 로드·PIE 복사까지 스폰 경로의 구성 횟수로 일반화하지 않습니다. Destroyed는 명시적 파괴에 반응하며, 레벨 언로드·플레이 종료까지 포괄하는 정리는 EndPlay에서 합니다. 사망 보상은 사망 조건에서 한 번 처리합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 런타임 구성 스폰한 객체를 구성·초기화한다. |
|
|
| 플레이 수명 BeginPlay 뒤 사용 가능한 실행 상태다. |
|
|
| EndPlay 정리 종료 이유에 맞게 구독·자원을 정리한다. |
|
|
위 그림은 세 경로를 나란히 놓고 각 경로의 범위를 보여 줍니다. 에디터 편집 경로에서는 Construction Script가 DoorType 같은 값으로 외형을 구성하며 조건에 따라 다시 실행될 수 있지만, 편집 자체가 BeginPlay를 시작하지는 않습니다. 플레이 중 스폰 경로에서는 액터가 생성되고 Construction Script를 포함한 구성과 초기화를 거친 뒤 BeginPlay에서 플레이 상태를 초기화합니다. 플레이 종료 경로에서는 Destroy, 언로드, PIE 종료 같은 종료 이유로 EndPlay가 실행되어 구독과 타이머를 정리하며, 메모리 회수는 그 뒤의 객체 수명 관리인 가비지 컬렉션 등이 따로 맡습니다.
단계 그림은 스폰된 액터 하나의 수명을 세 단계로 따라갑니다. 먼저 런타임에 스폰한 객체를 구성하고 초기화하며, 다음으로 BeginPlay를 지나 플레이에서 쓸 수 있는 상태가 되고, 마지막으로 EndPlay에서 종료 이유에 맞게 구독과 자원을 정리합니다. 그림 아래의 설명처럼 미리 배치해 저장한 액터를 불러오는 경로와 PIE의 복사 경로는 이 스폰 경로의 구성 횟수로 일반화하지 않으며, Destroyed는 명시적 파괴에만 반응하고 사망 보상은 사망 조건에서 한 번 처리합니다.
시점을 나누면 좋아지는 점
초기화와 종료 시점을 구분하면 게임이 더 안정적으로 동작합니다. 생성할 때 필요한 초기화가 빠지면 값이 비어 있는 채로 로직이 실행되고, 사라질 때 타이머나 구독을 정리하지 않으면 이미 없는 대상을 계속 부르는 문제가 생깁니다. 또 쓰지 않는 사운드나 이펙트를 제때 정리하면 불필요하게 자원을 붙잡고 있지 않게 됩니다.
디버깅도 쉬워집니다. 액터가 언제 만들어지든 같은 초기화 경로를 거치고 같은 정리 경로로 끝나면, 같은 문제를 다시 재현하고 원인을 찾기가 쉬워집니다. 이벤트를 고를 때는 이벤트가 시작되는 지점부터 끝나는 지점까지 실행 경로를 소리 내어 설명해 보고, 분기가 늘어나면 함수로 나누는 기준을 먼저 정한 뒤 그래프를 정리합니다.
더 깊이: 지연 스폰과 다시 쓰이는 액터
액터 수명 주기 문서는 스폰 경로를 더 자세히 나눕니다. 일반 스폰에서는 액터가 만들어진 직후의 구성 단계(OnConstruction)에서 블루프린트의 컴포넌트가 만들어지고 변수가 초기화되며, 그 뒤 컴포넌트 초기화를 거쳐 BeginPlay가 호출됩니다. 그런데 변수 가운데 Expose on Spawn을 켠 것이 있으면 그 액터는 지연 스폰(deferred spawn)됩니다. 지연 스폰은 액터를 만든 뒤 Construction Script를 실행하기 전에 값을 먼저 설정할 수 있게 하고, 설정이 끝나면 FinishSpawning이 멈춰 두었던 구성 단계부터 이어서 진행합니다. 그래서 Spawn Actor from Class 노드의 핀으로 넘긴 값은 Construction Script에서도 이미 읽을 수 있습니다.
종료 쪽에도 알아 둘 예외가 있습니다. 같은 문서에 따르면 EndPlay가 호출되었다고 액터가 반드시 파괴되는 것은 아닙니다. s.ForceGCAfterLevelStreamedOut이 꺼져 있는 상태에서 서브레벨을 언로드했다가 곧바로 다시 불러오면, EndPlay를 받았던 바로 그 액터가 다시 쓰일 수 있고, 이때 액터의 변수는 기본값으로 다시 초기화되지 않습니다. 그러니 레벨 스트리밍으로 다시 들어올 수 있는 액터라면, 변수가 기본값에서 시작한다고 가정하지 말고 플레이를 시작할 때 필요한 상태를 직접 설정하는 편이 안전합니다.
정리
- Construction Script는 액터를 구성할 때 실행되며, 에디터에서 배치나 속성 변경에 따라 여러 번 다시 실행될 수 있어 외형 미리보기에 씁니다.
- BeginPlay는 액터가 플레이를 시작할 때 호출되며, 배치된 액터와 스폰된 액터 모두 초기화를 마친 뒤 도착합니다. 에디터 편집 중에는 실행되지 않습니다.
- EndPlay는 파괴, 레벨 전환, 스트리밍 언로드, PIE 종료, 수명 만료, 애플리케이션 종료처럼 여러 이유로 호출되며,
End Play Reason으로 이유를 구분합니다. - 사망 보상처럼 한 번만 일어나야 하는 일은 사망 조건에서 처리하고, EndPlay에는 어떤 이유로 끝나든 필요한 정리만 둡니다.
- Destroyed는 명시적 파괴에만 반응하며, EndPlay가 실행되어도 메모리는 이후 가비지 컬렉션이 회수합니다.
연습 문제
1. 레벨을 바꿀 때마다 떨어지는 보상
적 블루프린트의 EndPlay에 아이템 드롭을 연결했더니, 적을 처치하지 않았는데도 레벨을 전환할 때마다 아이템이 생긴다는 보고가 들어왔습니다. 왜 이런 일이 생기고, 어떻게 고쳐야 할까요?
해설 보기
EndPlay는 적이 죽어서 파괴될 때만이 아니라 레벨 전환, 스트리밍 언로드, 게임 종료 때도 호출되기 때문입니다. 레벨을 전환하면 그 레벨의 적 모두에서 EndPlay가 실행되므로, 거기에 연결한 드롭도 함께 실행됩니다.
드롭은 체력이 0이 되는 사망 처리 경로에서 한 번 실행하고, 그 뒤에 Destroy Actor를 호출합니다. EndPlay에는 사운드 정지나 타이머 해제처럼 어떤 이유로 끝나든 필요한 정리만 남깁니다. 꼭 EndPlay에서 구분해야 한다면 End Play Reason으로 종료 이유를 나누어 확인합니다.
2. 에디터에서 바로 바뀌는 문
DoorType 열거형에 따라 문의 메시를 바꾸는 로직을 BeginPlay에 넣었더니, 레벨에서 DoorType을 바꿔도 뷰포트의 문 모양이 그대로입니다. 어디로 옮겨야 할까요?
해설 보기
Construction Script로 옮깁니다. BeginPlay는 플레이가 시작되어야 실행되므로 에디터에서 값을 바꿀 때는 실행되지 않습니다. Construction Script는 에디터에서 속성을 바꾸면 다시 실행되므로, DoorType을 바꾸자마자 메시가 바뀐 모습을 뷰포트에서 볼 수 있습니다.
플레이 중에 스폰한 문도 구성 단계에서 Construction Script를 거치므로 같은 메시 선택 규칙이 적용됩니다.
3. 타이머를 정리할 위치
적이 BeginPlay에서 1초마다 주변을 확인하는 반복 타이머를 켭니다. 이 타이머를 정리하는 로직은 Event Destroyed와 Event EndPlay 가운데 어디에 두는 편이 좋을까요?
해설 보기
EndPlay에 둡니다. Destroyed는 명시적으로 파괴할 때만 반응하므로, 레벨 전환이나 스트리밍 언로드, PIE 종료로 적이 플레이를 끝낼 때는 정리 로직이 실행되지 않을 수 있습니다.
EndPlay는 이런 경우를 모두 포함하므로, 어떤 이유로 끝나든 타이머가 남아 있지 않게 할 수 있습니다. 공식 문서도 Destroyed의 로직을 EndPlay로 옮기기를 권합니다.
액터가 불러와지거나 스폰되고 제거되는 전체 과정은 언리얼 엔진 공식 문서의 액터 수명 주기 설명에서 확인할 수 있습니다.
이것으로 3장을 마칩니다. 3장에서는 월드에 놓이는 액터와 그 설계도인 액터 블루프린트에서 시작해, 컴포넌트로 기능을 조립하는 방법, 상속과 오버라이드로 공통 로직을 재사용하는 방법, 인터페이스로 상속 없이 같은 기능을 공유하는 방법, 그리고 액터의 구성과 플레이 시작, 종료 시점에 맞는 이벤트까지 블루프린트의 객체 지향 설계를 익혔습니다. 다음 장의 직접 참조를 통한 블루프린트 통신부터는 직접 참조, 이벤트 디스패처, 인터페이스, 게임 인스턴스, 레벨 블루프린트로 블루프린트끼리 정보를 주고받는 방법을 다룹니다.