엔진으로 개발을 하다보면 가장 중요한 것이 그 엔진의 라이플사이클이다.
객체의 초기화나 오브젝트 할당, 파괴 등의 중요한 역할을 하기 때문이다.
그래서 오늘은 언리얼 엔진의 라이플 사이클에 대해 먼저 알아보았다.
그리고 관련 함수들을 사용하여 C++로 액터를 동적으로 이동, 회전, 크기변화까지 시켜볼 생각이다.
언리얼엔진의 라이플사이클
생성자
- 액터가 생성되기 전에 호출됨.
- 객체 메모리에 생성될 때 1번 호출됨(주로 초기화 작업을 활용)
- 객체에 컴포넌트를 추가할 때? 사용하는 것 같음
PostInitializeComponents()
- 컴포넌트가 완성된 직후 호출
- 월드에 객체가 등장하기 직전에 컴포넌트끼리 상호작용할 때 활용
BeginPlay()
- 월드에 스폰된 직후에 호출됨
- 다른 액터들도 이미 준비되어있어서 자유롭게 상호작용이 가능
Tick(float DeltaTime)
- 매 프레임마다 호출됨(무거운 로직을 담으면 성능이 떨어질 수도 있기 때문에 조심해서 사용)
- 캐릭터 이동, 물리 계산 같은 부분들에 활용
Destroyed()
- 삭제되기 직전에 호출됨
- 보통 리소스 정리같은 부분을 담당
Endplay(const EEndPlayReason::Type EndPlayReason )
- 게임 종료의 상황에서 호출
- 이 함수는 Enum 타입으로, 어떤 이유로 EndPlay 함수가 호출되었는지 구분할 수 있다고 한다!
한줄요약(결론)
생성자() -> PostInitializeComponents() -> BeginPlay() -> (TIck()) -> Destory() -> EndPlay() 순으로 호출된다.
+Desrtory와 EndPlay()의 차이
(언리얼 초심자는 이 두 함수를 굉장히 혼란스러워한다고 하길래 나만의 이해팁을 공유하고자 한다)
내가 이해하기로는 Destroy()는 크롬창의 각 탭에 달려있는 x의 느낌으로 이해했다.

근데 이제 EndPlay()는 크롬창의 전체닫기 같은 느낌으로 이해했다.

대강 요런 느낌??
그리고 일단 각 함수들이 언제 호출되는지 좀 명확하게 보고싶어서 언리얼 엔진의 로그시스템을 활용해주었다.
먼저 플레이 버튼을 누른 직후이다.

이렇게 생성자(Constructor) -> PostInitializeComponents -> BeginPlay 순으로 로그가 찍히는 것을 볼 수 있다.
플레이를 종료했을 때

플레이 종료를 하면 EndPlay가 호출되는 것을 볼 수 있다.
액터를 삭제했을 때

플레이 도중에 액터를 삭제해보았다. 그러니까 바로 Destory 함수가 호출이 된 것을 볼 수 있었다!
이제 이 액터를 C++ 코드로 Transform을 건드려볼려고 한다.
C++로 액체 Transform 동적으로 변화하게하기
플레이를 눌렀을 때, 위로 이동하면서 회전도 하고 크기도 계속 변화하는 별을 만드려고 했다.
그래서 일단은 위치지정을 해줬고, 그 다음에 이동값과 회전값 등을 더하는 형식으로 진행했다.
크기 변화는 초기크기와 최대크기를 지정해줬고, 특정 시간에 맞춰서 크기 변화를 계속 시켜줬다.
사용한 코드
#include "Item.h"
AItem::AItem()
{
// 씬 컴포넌트를 루트로 지정하는 과정
SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
SetRootComponent(SceneRoot);
StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
StaticMeshComp->SetupAttachment(SceneRoot);
static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/Resources/Props/SM_Star_C.SM_Star_C"));
if (MeshAsset.Succeeded())
{
StaticMeshComp->SetStaticMesh(MeshAsset.Object);
}
static ConstructorHelpers::FObjectFinder<UMaterial> MaterialAsset(TEXT("/Game/Resources/Materials/M_Power_Sphere_Inst.M_Power_Sphere_Inst"));
if (MaterialAsset.Succeeded())
{
StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
}
PrimaryActorTick.bCanEverTick = true;
RotationSpeed = 90.0f;
SpeedZ = 200.0f;
TimeAccumulator = 0.0f;
ChangeScaleTime = 2.0f;
MaxScale = 5.0f;
}
void AItem::BeginPlay()
{
Super::BeginPlay();
//SetActorLocation(FVector(300.0f, 200.0f, 100.0f));
//SetActorRotation(FRotator(0.0f, 90.0f, 0.0f)); // FRoatator의 형태 - pitch(y축), yaw(z축), roll(x축) 순
//SetActorScale3D(FVector(2.0f));
FVector NewLocation(300.0f, 200.0f, 100.0f);
FRotator NewRotation(0.0f, 90.0f, 0.0f);
FVector NewScale(2.0f);
InitialScale = NewScale;
FTransform NewTransform(NewRotation, NewLocation, NewScale);
SetActorTransform(NewTransform);
}
void AItem::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 부동소수점은 오차가 굉장히 크기 때문에 불필요한 0.00000001에서도 회전을 한다.
// 그래서 언리얼에서 제공하는 FMath의 0에 근접한지 계산하는 함수를 사용해준다.
if (!FMath::IsNearlyZero(RotationSpeed))
{
AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaTime, 0.0f));
}
AddActorLocalOffset(FVector(0.0f, 0.0f, SpeedZ * DeltaTime));
TimeAccumulator += DeltaTime;
if (TimeAccumulator > ChangeScaleTime)
TimeAccumulator -= ChangeScaleTime;
float ScaleFactor = InitialScale.X + MaxScale * FMath::Sin((2 * PI / ChangeScaleTime) * TimeAccumulator);
SetActorScale3D(FVector(ScaleFactor, ScaleFactor, ScaleFactor));
}
언리얼에서 제공해주는 유용한 함수가 많아 신기했다. 특히 FMath 클래스의 Sin 함수에서 되게 간편하게 구현할 수 있다는 느낌을 받았다.
결과

어디까지 올라가니..?
아주 그냥 잘 날아다니고, 제일 왕 큰 별이 되어라 내 스타야...
잘 보이진 않지만 자세히보면 별이 회전하는 부분도 보인다..! ㅎㅎ
마지막 말
아직 언리얼 문법에 익숙치 않아 스케일 변화할 때는 gpt의 도움을 살짝 받았다..
메서드 이름에 집착하지말고 전체적인 개발 흐름에 대한 느낌을 익혀야할 것 같다...
천천히 조금씩 꾸준히하면 무엇이든 성장할 것이다!!!! 파이팅~~!!!!
'Unreal Engine' 카테고리의 다른 글
[ UE, C++ ] Actor를 다양하게 회전시켜보자 (0) | 2025.01.27 |
---|---|
[ UE ] 퍼즐스테이지 설계 및 구상 (0) | 2025.01.24 |
[ UE ] C++ 코드로 RootComponent와 StaticMesh 생성하기 (0) | 2025.01.22 |
[ UE ] 언리얼 엔진 프로젝트 구조 및 빌드 방법 (+꿀팁) (0) | 2025.01.20 |
[UE5] UPROPERTY(), UFUNCTION()을 꼭 붙여야 할까? (1) | 2025.01.09 |