언리얼 엔진 Actor의 라이프 사이클, Log 사용
Log 사용
- UE_LOG(LogTemp, Warning, TEXT("My Item appears!!"))
- 로그 카테고리 (Log Category): 여기서는 LogTemp라는 임시 카테고리를 사용했습니다.
- 로그 수준 (Log Level): Warning을 사용하면, 노란색 글씨로 강조되어 출력됩니다. 이 외에도 Log, Display, Error 등 다양한 수준이 있습니다.
- Display: 일반적인 실행 흐름이나 상태 확인 메시지 (흰색)
- Warning: 예상치 못한 동작이나 잠재적인 문제 (노란색)
- Error: 즉시 수정이 필요한 심각한 문제 (빨간색)
- 출력할 메시지: My Item appears!!라는 문자열이 출력됩니다.
- 이 코드를 작성한 뒤 빌드하고, 언리얼 에디터로 돌아오면 설정 완료입니다.
고유한 카테고리 정의하고 로그 추가하기
프로젝트 규모가 커질수록 모든 로그를 LogTemp로 찍으면 구분이 어렵습니다. 이럴 때는 DEFINE_LOG_CATEGORY를 사용해 고유한 카테고리를 만들어 사용하는 것이 좋습니다.
- `DECLARE_LOG_CATEGORY_EXTERN( CustomName , Warning, All);`
- 헤더 파일에서 로그 카테고리를 선언합니다.
- ` CustomName `: 카테고리 이름 (사용자가 지정).
- `Warning`: 이 카테고리를 사용할 때 기본적으로 `Warning` 이상의 로그만 출력하도록 설정
- `All`: 필요하면 나중에 모든 로그를 활성화할 수 있도록 허용
- 이후 다른 클래스에서도 ` CustomName ` 카테고리를 사용하고 싶다면 이 헤더 파일(`Item.h`)을 *포함*해야 합니다. 보통은 이런 로그 카테고리를 여럿이 공유하기 때문에, 별도의 *공용 헤더*에 선언해 두는 경우가 많습니다.
!!! 중요 Cpp 파일에 가서도 선언해줘야한다.
- DEFINE_LOG_CATEGORY( CustomName );
보통은 공용 유틸리티 헤더를 만들고, 그쪽에 로그 카테고리를 선언해서 따로 관리한다
로그 필터링 (Filtering)
- 로그가 너무 많다면, Output Log 창 상단 혹은 우측 상단의 Filters 메뉴를 이용해 카테고리별로 로그를 걸러낼 수 있습니다.
- 예를 들어, Show All 옵션을 끄고 LogTemp만 활성화하면 LogTemp 카테고리에 해당하는 로그만 볼 수 있습니다.

- 만약 필터 목록에 LogTemp 자체가 보이지 않는다면, 아직 한 번도 해당 카테고리의 로그가 출력되지 않았을 수 있습니다. 반드시 로그가 최소 한 번 발생해야 목록에서 필터링할 수 있습니다.
공식문서: https://dev.epicgames.com/documentation/unreal-engine/logging-in-unreal-engine?lang=ko
Logging in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community
Information on logging in Unreal Engine.
dev.epicgames.com
언리얼 엔진 Actor의 라이프 사이클
언리얼 엔진에서 Actor는 게임 도중에 언제든지 생성(Spawn)될 수 있고, 필요 없어지면 파괴(Destroy)될 수 있습니다.
액터 라이프 사이클을 알아야 하는 이유
- 초기화 시점 결정
- 생성자 (Constructor), PostInitializeComponents, BeginPlay 등이 각각 언제 호출되는지 알아야 적절한 곳에 코드를 배치할 수 있습니다.
- 예) 컴포넌트 생성(CreateDefaultSubobject)은 생성자에서, 다른 액터 참조나 월드 접근은 BeginPlay에서 처리.
- 생성자 (Constructor), PostInitializeComponents, BeginPlay 등이 각각 언제 호출되는지 알아야 적절한 곳에 코드를 배치할 수 있습니다.
- 성능 관리
- 매 프레임마다 호출되는 Tick 함수는 비용이 클 수 있습니다.
- 따라서 필요한 액터만 Tick을 활성화하거나 이벤트 기반으로 전환해 최적화해야 합니다.
- 리소스 정리
- 액터가 사라질 때 (EndPlay, Destroyed 등) 메모리를 해제하거나 특정 상태를 저장해야 할 수 있습니다.
- 적절한 시점에 필요한 정리 작업을 하지 않으면 메모리 누수나 예외 상황이 발생할 수 있습니다.
주요 라이프 사이클 함수
- 언리얼 엔진의 Actor는 생성 → 초기화 → 월드 배치 → Tick(실행) → 제거 순으로 동작하며, 이를 지원하기 위해 여러 함수가 자동 호출됩니다.
- 이 함수들은 전부다 AActor 의 부모 클래스에서 가지고 있는 것이기 때문에, 다 override 해주는 형태로 가져와야 한다.
- 생성자 (Constructor)
- 호출 시점: C++ 클래스 객체가 메모리에 생성될 때 딱 한 번 호출됩니다.
- 아직 액터가 월드 (World)에 완전히 등록되지 않은 상태이므로, 다른 액터나 월드 관련 기능을 안전하게 호출하기 어렵습니다.
- 주로 CreateDefaultSubobject 등을 사용해 컴포넌트 생성 및 초기 변수 세팅에 활용합니다.
- PostInitializeComponents()
- 호출 시점: 액터의 모든 컴포넌트가 생성·초기화된 뒤 자동 호출됩니다.
- 각 컴포넌트가 이미 준비된 상태이므로, 컴포넌트 간 상호작용(예: 서로 다른 컴포넌트 참조 설정)이 필요한 코드를 배치하기 좋습니다.
- 보통 생성자에서는 단순한 ‘생성/할당’만 하고, PostInitializeComponents()에서 컴포넌트들 사이의 의존 관계를 설정합니다.
- BeginPlay()
- 호출 시점: Play In Editor (PIE)나 런타임에서 게임이 시작될 때, 혹은 이미 실행 중인 게임에 SpawnActor 등으로 새 액터가 생성될 때 한 번 호출됩니다.
- 이 시점에는 이미 월드와 다른 액터들이 준비된 상태이므로, 자유롭게 상호작용이 가능합니다.
- AI, 게임 모드, 플레이어 컨트롤러 등 다른 시스템과 연동도 주로 BeginPlay에서 초기화합니다.
- 타이머, Delegate(Event) 바인딩 등을 시작하기에도 적합합니다.
- Tick(float DeltaTime)
- 호출 시점: 매 프레임마다 호출됩니다.
- 중요: !!! 액터의 PrimaryActorTick.bCanEverTick = true; 설정 필요
- 안쓸때는 false 해서 아에 접근 안되게 하는게 성능에 좋다.
- 실시간 업데이트가 필요한 로직 (캐릭터 이동, 물리 연산, 카메라 추적 등)을 처리합니다.
- 이벤트 (Event) 기반으로 전환할 수 있는 부분은 Tick을 사용하지 않는 편이 성능에 유리합니다.
- Destroyed()
- 호출 시점: Destroy() 함수를 직접 호출하여 액터를 제거할 때 직전에 호출됩니다. 다만 레벨 전환이나 게임 종료 시에는 종종 건너뛰어지기도 하므로 (상황마다 엔진 동작이 달라짐), 절대적으로 보장되는 것은 아닙니다.
- Destroyed()가 불린 뒤에는 최종적으로 EndPlay()도 함께 호출됩니다.
- 수동으로 액터를 제거할 때, 마지막 정리 코드를 넣을 수 있는 곳입니다.
- But, 게임 종료나 레벨 언로드 시에는 호출되지 않을 수 있으므로, 모든 중요한 정리를 Destroyed()에만 의존하면 놓치는 케이스가 생길 수 있습니다.
- 정리할 자원 예시
- 수동 할당한 메모리: new 또는 동적 할당한 오브젝트가 있다면 여기서 delete하거나 해제합니다.
- 스폰된 자식 액터: 이 액터가 생성한 다른 액터나 컴포넌트 중, 자동으로 해제되지 않는 것이 있다면 제거 처리합니다.
- Delegate / Event 바인딩: 게임 전역적 또는 외부 클래스에 바인딩해둔 델리게이트가 있다면 해제합니다.
- 사운드/파티클 등: 필요 시 이 액터가 재생 중인 사운드나 파티클을 수동으로 정리합니다.
- EndPlay(const EEndPlayReason::Type EndPlayReason)
- 호출 시점: 액터가 더 이상 월드에서 활동하지 않게 될 때 호출됩니다. (파괴, 레벨 전환, 게임 종료 등)
- EEndPlayReason::Type으로 어떤 이유로 EndPlay가 호출되었는지(파괴, 레벨 언로드, 게임 종료 등)를 구분합니다.
- 게임 종료나 레벨 언로드 같은 상황에서도 EndPlay()는 상대적으로 호출 보장이 높지만, Destroyed()는 건너뛸 수 있습니다.
- 따라서 중요한 정리 로직 (자원 해제, Timer 해제, 상태 저장 등)은 EndPlay()에 넣는 것이 보다 안전합니다.
- 정리할 자원 예시
- 타이머: GetWorldTimerManager().ClearTimer(…) 와 같이 타이머를 정리합니다.
- 동적 할당 리소스: 여전히 해제되지 않은 동적 메모리가 남아 있다면 여기서 정리합니다.
- 데이터 저장: 게임 진행 상황 (점수, 인벤토리 등)을 파일/DB에 저장하거나, 상위 시스템에 콜백을 보내는 로직도 EndPlay에서 처리 가능합니다.
!중요! EndPlay & Destroyed 의 성우 CPP 에 선언할때 부모함수 전에 로직 써두는게 좋다
- EndPlay vs. Destroyed
- EndPlay는 액터가 월드에서 사라지는 모든 상황 (게임 종료, 레벨 전환, Destroy 호출 등)에 대해 호출됩니다.
- Destroyed는 보통 Destroy() 함수가 명시적으로 불렸을 때만 호출되며, 게임 종료나 레벨 언로드 시에는 호출되지 않을 수 있습니다.
- Destoryed 함수고 호출되면 EndPlay 는 무조건 호출되지만, EndPlay 가 호출 되었다고, Destroyed 함수가 호출되는 것은 아니다.
선언 Sample, CPP 에 선언 주의 할점
CPP 에 선언 할때, 부모 (ex: Super::BeginPlay();) 를 호출 안하면 이상한 문제가 될 수 있기때문에 꼭 해야 합니다.
이런거는 자동으로 선언되게 안한이유가 무엇일까..?
핵심 이유
언리얼이 자동으로 Super::BeginPlay()를 강제하지 않는 이유는, 자식 클래스가 부모 동작을 언제 호출할지 선택해야 하는 경우가 있기 때문이에요.
비유하면 부모 함수는 기본 준비 절차, 자식 함수는 내가 추가하는 절차예요.
부모 준비를 먼저 끝내고 → 내 로직 실행이에요.혹은 내 로직을 먼저 끝내고 → 부모로직 실행 등의 자유를 준것
void AMyActor::BeginPlay()
{
Super::BeginPlay();
// 내 BeginPlay 로직
}
void AMyActor::BeginPlay()
{
// 내 로직 먼저
Super::BeginPlay();
}
또 하나의 이유
C++의 override는 기본적으로 이런 구조예요.
“부모 함수를 덮어쓴다.”
덮어쓴다는 말은, 부모 구현을 자동 포함한다는 뜻이 아니라
부모 버전 대신 자식 버전을 실행한다는 뜻에 가까워요.
그래서 부모 기능도 필요하면 자식 쪽에서 직접 호출해야 합니다.
중요! EndPlay & Destroyed 의 경우 CPP 에 선언할때 부모함수 전에 로직 써두는게 좋다
주의할 점
중요한 정리 로직은 Destroyed()에만 두면 놓칠 수 있어요.
게임 종료나 레벨 전환에서는 Destroyed()가 안 불리고 EndPlay()만 호출될 수 있기 때문이에요.
그래서 안전하게는:
- Destroyed() : 명시적으로 Destroy()된 경우 처리
- EndPlay() : 모든 종료 상황에서 필요한 정리 처리
추천
Actor Life Cycle [공식문서]: https://dev.epicgames.com/documentation/unreal-engine/unreal-engine-actor-lifecycle
Unreal Engine Actor Lifecycle | Unreal Engine 5.7 Documentation | Epic Developer Community
What actually happens when an Actor is loaded or spawned, and eventually dies.
dev.epicgames.com
[페이지] Unreal Engine | 언리얼 엔진
