언리얼 엔진 Actor의 라이프 사이클 이해하기
목차
위의 목차를 클릭하면 해당 글로 자동 이동 합니다.
Actor클래스에 로그(Log) 추가하기
게임 개발 중 특정 함수가 제대로 호출되는지, 변수에 어떤 값이 들어있는지를 빠르게 확인해야 할 때는 로그 메시지가 큰 도움이 됩니다. 언리얼 엔진에서는 UE_LOG 매크로를 사용해 Output Log 창에 메시지를 남길 수 있습니다.
1.BeginPlay() 함수에 로그 추가하기
아래는 액터의 BeginPlay() 함수가 호출되는 시점을 확인하기 위해 로그를 출력하는 코드 예시입니다.
void AItem::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Warning, TEXT("My Item appears!!"));
}
- UE_LOG(LogTemp, Warning, TEXT("My Item appears!!"))
- 로그 카테고리 (Log Category): 여기서는 `LogTemp`라는 임시 카테고리를 사용했습니다.
- 로그 수준 (Log Level): `Warning`을 사용하면, 노란색 글씨로 강조되어 출력됩니다. 이 외에도 `Log`, `Display`, `Error` 등 다양한 수준이 있습니다.
- Display: 일반적인 실행 흐름이나 상태 확인 메시지 (흰색)
- Warning: 예상치 못한 동작이나 잠재적인 문제 (노란색)
- Error: 즉시 수정이 필요한 심각한 문제 (빨간색)
- 출력할 메시지: `My Item appears!!`라는 문자열이 출력됩니다.
- 이 코드를 작성한 뒤 빌드하고, 언리얼 에디터로 돌아오면 설정 완료입니다.
2.고유한 카테고리 정의하고 로그 추가하기
프로젝트 규모가 커질수록 모든 로그를 LogTemp로 찍으면 구분이 어렵습니다. 이럴 때는 DEFINE_LOG_CATEGORY를 사용해 고유한 카테고리를 만들어 사용하는 것이 좋습니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h"
// "LogSparta"라는 이름으로 로그 카테고리 선언
DECLARE_LOG_CATEGORY_EXTERN(LogSparta, Warning, All);
UCLASS()
class TESTPROJECT_API AItem : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AItem();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
USceneComponent* SceneRoot; // 루트 컴포넌트를 나타내는 Scene Component포인터
UStaticMeshComponent* StaticMeshComp; // Static Mesh Component 포인터
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
- DECLARE_LOG_CATEGORY_EXTERN(LogSparta, Warning, All);
- 헤더 파일에서 로그 카테고리를 선언합니다.
- LogSparta: 카테고리 이름 (사용자가 지정).
- Warning: 이 카테고리를 사용할 때 기본적으로 `Warning` 이상의 로그만 출력하도록 설정
- All: 필요하면 나중에 모든 로그를 활성화할 수 있도록 허용
- 이후 다른 클래스에서도 LogSparta 카테고리를 사용하고 싶다면 이 헤더 파일(`Item.h`)을 포함해야 합니다. 보통은 이런 로그 카테고리를 여럿이 공유하기 때문에, 별도의 공용 헤더에 선언해 두는 경우가 많습니다.
#include "Item.h"
// "LogSparta" 카테고리 정의 (헤더에서 선언한 것을 실제로 구현)
DEFINE_LOG_CATEGORY(LogSparta);
// Sets default values
AItem::AItem()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot")); //Scene Component를 생성
SetRootComponent(SceneRoot); //SceneRoot를 루트로 설정
StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh")); //Static Mesh Component를 생성
StaticMeshComp->SetupAttachment(SceneRoot); //Scene Component에 Attach
static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/Resources/Props/SM_Chair.SM_Chair"));
if (MeshAsset.Succeeded())
{
StaticMeshComp->SetStaticMesh(MeshAsset.Object);
}
static ConstructorHelpers::FObjectFinder<UMaterial> MaterialAsset(TEXT("/Game/Resources/Materials/M_Metal_Gold.M_Metal_Gold"));
if (MaterialAsset.Succeeded())
{
StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
}
}
// Called when the game starts or when spawned
void AItem::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Warning, TEXT("My Item appears!!"));
UE_LOG(LogSparta, Error, TEXT("My Log!!"));
}
- DEFINE_LOG_CATEGORY(LogSparta);
- .cpp 파일에서 로그 카테고리를 구현(정의)합니다.
- 이제 `LogSparta`라는 카테고리를 통해 로그 메시지를 좀 더 체계적으로 구분할 수 있습니다.
언리얼 에디터에서 로그 확인하기
로그를 출력하는 코드를 작성했다면, 언리얼 에디터에서 실제로 그 메시지가 잘 나타나는지 확인해야 합니다.
1.Output Log 창 열기
언리얼 에디터 상단 메뉴에서 Window -> Output Log를 클릭하면,하단이나 측면에 Output Log창이 열립니다.
이 창에는 엔진 전체에서 발생하는 로그 메시지가 실시간으로 표시됩니다.
너무 많은 기존 로그가 있다면 우클릭 -> Clear Log로 한 번 지우고, 새로 발생하는 로그만 모아서 보는 것이 편리합니다.
2.게임 시작 후 로그 확인
-에디터 상단의 Play버튼을 눌러 게임(플레이 모드)을 시작합니다.
- BeginPlay()가 호출되는 순간, 우리가 작성한 "My Item appears!!" 메시지가 노란색, "My Log!!" 메시지가 빨간색으로 표시됩니다.
- 대략 아래와 같은 형태로 로그 메시지가 표시됩니다.
3.로그 필터링
-로그가 너무 많다면,Output Log 창 상단 혹은 우측 상단의 Filters 메뉴를 이용해 카테고리별로 로그를 걸러낼 수 있습니다.
-예를 들어 Showw All 옵션을 끄고 LogTemp만 활성화하면 LogTemp카테고리에 해당하는 로그만 볼수 있습니다.
-만약 필터 목록에 LogTemp 자체가 보이지 않는다면, 아직 한 번도 해당 카테고리의 로그가 출력되지 않았을 수 있습니다. 반드시 로그가 최소 한 번 발생해야 목록에서 필터링할 수 있습니다.
언리얼 엔진 Actor의 라이프 사이클 이해하기
언리얼 엔진에서 Actor는 게임도중 언제든지 생성(Spawn)될 수 있고, 필요 없어지면 파괴(Destroy)될 수 있습니다. 이를 Actor 라이프 사이클 이라고 부르며, 이 과정을 이해하면 게임 로직을 보다 효율적이고 안정적으로 작성할 수 있습니다.
1. 액터 라이프 사이클을 알아야 하는 이유
- 초기화 시점 결정
- 생성자 (Constructor), `PostInitializeComponents`, `BeginPlay` 등이 각각 언제 호출되는지 알아야 적절한 곳에 코드를 배치할 수 있습니다.
- 예) 컴포넌트 생성(`CreateDefaultSubobject`)은 생성자에서, 다른 액터 참조나 월드 접근은 BeginPlay에서 처리.
- 성능 관리
- 매 프레임마다 호출되는 `Tick` 함수는 비용이 클 수 있습니다.
- 따라서 필요한 액터만 `Tick`을 활성화하거나 이벤트 기반으로 전환해 최적화해야 합니다.
- 리소스 정리
- 액터가 사라질 때 (`EndPlay`, `Destroyed` 등) 메모리를 해제하거나 특정 상태를 저장해야 할 수 있습니다.
- 적절한 시점에 필요한 정리 작업을 하지 않으면 메모리 누수나 예외 상황이 발생할 수 있습니다.
2. 주요 라이프 사이클 함수
언리얼 엔진의 Actor는 생성 → 초기화 → 월드 배치 → Tick(실행) → 제거 순으로 동작하며, 이를 지원하기 위해 여러 함수가 자동 호출됩니다.
1. 생성자 (Constructor)
- C++ 클래스 객체가 메모리에 생성될 때 단 한 번 호출됩니다.
- 아직 월드에 완전히 등록된 상태가 아니므로, 다른 액터나 월드 관련 기능은 안전하게 호출하기 어렵습니다.
- 보통 컴포넌트 생성(`CreateDefaultSubobject`) 및 기본 변수 초기화에 사용합니다.
2. PostInitializeComponents()
- 액터의 모든 컴포넌트가 생성·초기화를 마친 뒤 자동으로 호출됩니다.
- 컴포넌트들이 이미 준비된 상태이므로, 컴포넌트 간 상호작용 초기화 코드를 넣기 좋습니다.
3. BeginPlay()
- 게임이 시작 (Play 모드)되거나, 런타임 중 액터가 새로 생성 (Spawn)되는 순간에 한 번 호출됩니다.
- 이 시점에서는 월드와 다른 액터들이 준비된 상태이므로, 자유롭게 상호작용 코드를 작성할 수 있습니다.
- AI, 게임 모드, 플레이어 컨트롤러 등 다른 시스템과의 연동도 보통 이 시점에 처리합니다.
4. Tick(float DeltaTime)
- 매 프레임마다 반복 호출되며, 실시간 업데이트가 필요한 로직 (캐릭터 이동, 물리 연산 등)을 넣습니다.
- 불필요한 액터에는 `Tick`을 끄고, 이벤트 기반으로 전환하면 성능을 절약할 수 있습니다.
5. Destroyed()
- Destroy() 함수를 직접 호출해 액터를 제거할 때 직전에 호출됩니다. (단, 게임 종료나 레벨 전환 시에는 호출되지 않을 수 있음)
- 보통 `EndPlay`에서 주요 정리를 마치고, `Destroyed()`에서 메모리 해제나 사운드/파티클 정리 등 최종 작업을 수행합니다.
- Destroyed가 불리면 마지막에 EndPlay도 같이 호출됩니다.
6. EndPlay(const EEndPlayReason::Type EndPlayReason)
- 액터가 더 이상 월드에서 활동하지 않을 때 (파괴, 게임 종료, 레벨 전환 등) 호출됩니다.
- `EEndPlayReason::Type`은 언리얼 엔진에서 `EndPlay` 함수가 호출되는 이유를 나타내는 열거형(enum) 타입입니다.
- 이 함수에서 자원 해제나 상태 저장을 처리합니다.
라이프 사이클 함수에 로그 추가하기
위에서 살펴본 라이프 사이클 함수를 직접 Override 하고, 각 시점에 로그 를 찍어서 실제 호출 순서를 확인해 봅시다.
1.헤더에 라이프 사이클 함수 선언
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Item.generated.h"
// "LogSparta"라는 이름으로 로그 카테고리 선언
DECLARE_LOG_CATEGORY_EXTERN(LogSparta, Warning, All);
UCLASS()
class TESTPROJECT_API AItem : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AItem();
protected:
USceneComponent* SceneRoot; // 루트 컴포넌트를 나타내는 Scene Component포인터
UStaticMeshComponent* StaticMeshComp; // Static Mesh Component 포인터
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void PostInitializeComponents() override;
virtual void Tick(float DeltaTime) override;
virtual void Destroyed() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
};
2.CPP파일에서 각 함수 구현 및 로그 출력
// Fill out your copyright notice in the Description page of Project Settings.
#include "Item.h"
// "LogSparta" 카테고리 정의 (헤더에서 선언한 것을 실제로 구현)
DEFINE_LOG_CATEGORY(LogSparta);
// Sets default values
AItem::AItem()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot")); //Scene Component를 생성
SetRootComponent(SceneRoot); //SceneRoot를 루트로 설정
StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh")); //Static Mesh Component를 생성
StaticMeshComp->SetupAttachment(SceneRoot); //Scene Component에 Attach
static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/Resources/Props/SM_Chair.SM_Chair"));
if (MeshAsset.Succeeded())
{
StaticMeshComp->SetStaticMesh(MeshAsset.Object);
}
static ConstructorHelpers::FObjectFinder<UMaterial> MaterialAsset(TEXT("/Game/Resources/Materials/M_Metal_Gold.M_Metal_Gold"));
if (MaterialAsset.Succeeded())
{
StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
}
UE_LOG(LogSparta, Warning, TEXT("%s Constructor"), *GetName());
}
// Called when the game starts or when spawned
void AItem::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogSparta, Warning, TEXT("%s BeginPlay"), *GetName());
}
void AItem::PostInitializeComponents()
{
Super::PostInitializeComponents();
UE_LOG(LogSparta, Warning, TEXT("%s PostInitializeComponents"), *GetName());
}
// Called every frame
void AItem::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AItem::Destroyed()
{
UE_LOG(LogSparta, Warning, TEXT("%s Destroyed"), *GetName());
Super::Destroyed();
}
void AItem::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
UE_LOG(LogSparta, Warning, TEXT("%s EndPlay"), *GetName());
Super::EndPlay(EndPlayReason);
}
- GetName()
- 현재 액터의 이름을 문자열로 반환해 줍니다. 인스턴스마다 다른 이름이 자동으로 붙기 때문에 어떤 액터가 로그를 찍는지 식별하기 좋습니다.
- Tick 메시지 주의
- `Tick` 함수는 매 프레임 호출되므로, 로그를 찍으면 메시지가 빗발칠 수 있습니다.
- 디버깅이 필요한 순간에만 제한적으로 사용하거나, 조건문을 통해 특정 상황에서만 로그를 찍는 것이 좋습니다.
- EndPlay vs. Destroyed
- `EndPlay`는 액터가 월드에서 사라지는 모든 상황 (게임 종료, 레벨 전환, Destroy 호출 등)에 대해 호출됩니다.
- `Destroyed`는 보통 `Destroy()` 함수가 명시적으로 불렸을 때만 호출되며, 게임 종료나 레벨 언로드 시에는 호출되지 않을 수 있습니다.
3.라이프 사이클 로깅 결과
위 코드를 빌드 후,언리얼 에디터에서 플레이를 시작해보면 아래와 같은 순서로 로그가 표시됩니다.
- LogSparta 로그만 출력되도록 로그 필터링을 설정해놓습니다.
- 게임이 진행되는 동안 매 프레임마다 Tick함수가 호출됩니다.
게임을 종료하면, EndPlay가 호출되는 것을 확인할 수 있습니다.
이번에는 다시 게임을 실행한 후, 게임 도중 Shift + F1로 마우스를 에디터로 가져온 후, Outliner 창에서 Item 액터를 삭제하면 Destroyed 로그가 출력되고 EndPlay가 출력된 다는 것을 확인할 수 있습니다.
이처럼 액터가 월드에 등장하여 → 동작하고 → 제거되는 흐름을 로그로 확인할 수 있으므로, 각 단계에서 무엇을 초기화/해제해야 할지 명확히 파악하는 데 큰 도움이 됩니다.
'Unreal' 카테고리의 다른 글
C++ 클래스와 리플렉션 시스템 활용하기 (0) | 2025.01.23 |
---|---|
Actor와 Transform (2) | 2025.01.22 |
Actor 클래스에 컴포넌트 추가하기 (1) | 2025.01.22 |
C++ Actor 클래스 생성 및 삭제하기 (2) | 2025.01.21 |
빌드 프로세스 이해하기 (0) | 2025.01.21 |