Unreal/Unreal Engine 5

[Unreal] UClass, UObject을 컴파일 타임과 런타임에서 초기화

suppresswisely 2025. 4. 11. 18:28

개요

언리얼에서는 컴포넌트, UClass, UObject, Actor와 같은 객체들을 초기화하는 방법이 서로 달라 코딩을 하면서 어려움이 발생한다. 이러한 문제를 해결하기 위해 각종 초기화 방법을 정리하여 조금이나마 이해하고자 한다.

CDO (Class Default Object)

언리얼에서 클래스를 다룰 때 중요한 분기점은 컴파일 타임과 런타임으로 나눌 수 있다. 실제로 언리얼에서는 생성자 함수를 CDO를 만드는 데 사용하며, 이는 언리얼 오브젝트를 생성할 때마다 매번 초기화하지 않고, 기본 인스턴스를 미리 만들어 복제하기 위해 만든 것이다. 실제로 블루프린트는 CDO 기반으로 뷰포트가 생성되는 것을 볼 수 있다.

생성과 복제의 차이

오브젝트를 생성하는 것과 복제하는 것은 차이가 없어 보인다. 실제로 생성하나 복제하나 메모리에 할당되는 크기는 같기 때문이다. 다만 중점적으로 봐야 할 것은 크기가 아니라 시간이다.

예를 들어, 공장에서 조립한 가구를 집으로 배달해 준다면 집에서 바로 설치된다. 그러나 집에서 가구를 조립하여 설치한다면 시간이 걸린다. 즉, CDO는 컴파일 타임(공장)에서 생성(조립)하여 런타임(집)에서 복제(설치)하기 위해 필요하다.

실제로 언리얼에서 생성자는 CDO를 제작하기 위한 목적으로 사용되며, 게임 플레이에서 사용할 일이 없다. 만약 게임 플레이에서 사용하고 싶다면 초기화 함수인 BeginPlay 함수가 존재한다.


UClass & UObject

스크립트에서 UClass와 UObject를 초기화하고 싶으면 ConstructorHelpers를 사용하면 된다. 해당 사용법을 설명하기 전에 먼저 UClass와 UObject에 대해 이해해야 한다.

UClass는 메타데이터로, 클래스가 어떤 역할을 하는지 알려주기 위해 작성된다. 언리얼 오브젝트에 대한 클래스 계층 구조 정보와 멤버 변수, 함수에 대한 정보가 모두 기록되어 있다.

UObject는 언리얼 엔진의 모든 객체의 기반 클래스이다. 이를 상속받지 않으면 언리얼에서 사용이 제한될 수 있다. 실제로 흔히 사용되는 ACharacter를 거슬러 올라가면 찾을 수 있다.

 

ConstructorHelpers

생성자 코드에서 에셋에 관련된 정보를 불러올 때 사용된다. ConstructorHelpers::FClassFinder는 UClass를 가져오고, ConstructorHelpers::FObjectFinder는 UObject를 가져온다.

 

ConstructorHelpers::FObjectFinder VS ConstructorHelpers::FClassFinder

// FClassFinder 사용 예시 (블루프린트 클래스)
static ConstructorHelpers::FClassFinder<APawn> BPClass(TEXT("/Game/Characters/BP_Hero"));
if (BPClass.Succeeded()) PawnClass = BPClass.Class;

// FObjectFinder 사용 예시 (텍스처 애셋)
static ConstructorHelpers::FObjectFinder<UTexture2D> Texture(TEXT("/Game/UI/Icons/HealthIcon"));
if (Texture.Succeeded()) IconTexture = Texture.Object;

 

이 둘의 역할은 결국 내가 가져오고 싶은 목적에 따라 다르게 사용된다. 좀 더 정확하게 살펴보면, FClassFinder는 블루프린트인 메타데이터를 가져오고, FObjectFinder는 리소스인 오브젝트를 가져온다. 즉, ClassFinder는 설계도를, FObjectFinder는 제작 재료를 가져온다고 이해해도 좋다.


컴포넌트 | 서브 오브젝트

컴포넌트와 서브 오브젝트를 초기화하기 위해서는 CreateDefaultSubobject와 NewObject를 사용하면 된다.

 

CreateDefaultSubobject

이 함수는 클래스의 생성자 내에서만 호출 가능하며, 런타임 중 생성이 아닌 CDO(Class Default Object) 초기화 단계에서 사용된다.

// 생성자 내에서 서브오브젝트 생성
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
RootComponent = MeshComponent;

 

NewObject 

이 함수는 런타임에 동적으로 컴포넌트나 서브 오브젝트를 생성할 때 사용된다.

// 엔진 초기화 후 런타임에서 객체 생성
NewTraceCollision = NewObject<UBoxComponent>(this);

UClass 포인터

특정 객체의 클래스 정보를 알기 위해 사용된다.

 

StaticClass

컴파일 타임에서 생성된 UClass 타입의 정보를 얻어 온다.

GameStateClass = AUS_GameState::StaticClass();
PlayerStateClass = AUS_PlayerState::StaticClass();
PlayerControllerClass = AUS_PlayerController::StaticClass();

 

GetClass

런타임에서 실제 객체의 클래스를 조회할 때 사용된다.

// 예시: 액터 인스턴스의 클래스 확인
AActor* SpawnedActor = GetWorld()->SpawnActor(...);
UClass* ActorClass = SpawnedActor->GetClass(); // 인스턴스의 클래스 반환

Acter 월드 스폰

Actor 클래스를 월드에 동적으로 생성하기 위한 핵심 함수이다. 주로 게임 플레이 중에 액터를 실시간으로 스폰할 때 사용되며, 위치 및 회전값 지정과 다양한 생성 파라미터 설정이 가능하다.

// C++ 클래스
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;

FVector SpawnLocation = GetActorLocation() + GetActorForwardVector() * 500;
AMyProjectile* Projectile = GetWorld()->SpawnActor<AMyProjectile>(
    AMyProjectile::StaticClass(),
    SpawnLocation,
    GetActorRotation(),
    SpawnParams
);

// 블루프린트 클래스
FName Path = TEXT("Blueprint'/Game/Characters/BP_Enemy.BP_Enemy_C'");
UClass* BPClass = Cast<UClass>(StaticLoadObject(UClass::StaticClass(), NULL, *Path.ToString()));

if(BPClass) {
    GetWorld()->SpawnActor<AActor>(BPClass, SpawnTransform, SpawnParams);
}

결론

조사하면서 아직까지 많이 부족하다는 생각이 든다. 실제로 ConstructorHelpers::FObjectFinder와 CreateDefaultSubobject의 차이점, ConstructorHelpers::FClassFinder와 StaticClass의 차이점이 와닿지가 않는다. 물론 조사를 끝까지 안 해본 것은 아니다. 지금부터 서술하는 것은 잘못된 내용이 포함될 수도 있다.

 

ConstructorHelpers::FObjectFinder와 CreateDefaultSubobject의 차이점

둘 다 UObject를 가져온다는 점은 동일하다. 실제로 CreateDefaultSubobject에 컴포넌트가 아닌 UObject가 상속된 클래스를 사용할 수 있다. 위에 서술한 내용은 주로 컴포넌트를 초기화할 때 사용된다는 내용이 많았기 때문이다.

차이점이라 하면, CreateDefaultSubobject는 컴포넌트를 추가하는 역할을 하고, ConstructorHelpers::FObjectFinder는 추가된 컴포넌트에 데이터를 추가하는 역할을 하는 것처럼 보인다.

Mesh = CreateDefaultSubobject<UStaticMeshComponent>("Mesh");
Mesh->SetupAttachment(RootComponent);
Mesh->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);
Mesh->SetRelativeLocation(FVector(-40.f, 0.f, 0.f));
Mesh->SetRelativeRotation(FRotator(-90.f, 0.f, 0.f));
static ConstructorHelpers::FObjectFinder<UStaticMesh> StaticMesh(TEXT("/Game/KayKit/DungeonElements/dagger_common"));
if (StaticMesh.Succeeded())
{
    GetMesh()->SetStaticMesh(StaticMesh.Object);
}

 

위 코드를 보면 CreateDefaultSubobject로 컴포넌트를 추가함으로써 GetMesh() 함수를 사용할 수 있게 되어 코드상에서 메쉬를 추가할 수 있게 되었다.


ConstructorHelpers::FClassFinder와 StaticClass의 차이점

이 둘의 차이점은 더욱 확인하기 어려웠다. ConstructorHelpers::FClassFinder는 블루프린트 클래스와 같은 컨트롤 가능성을 제공하는 반면, StaticClass는 클래스의 정보만을 확인하기 위해 사용하는 느낌이었다.

결론적으로, 내가 작성한 말에 객관적인 문서나 글을 제시하지 못하는 점이 매우 아쉽게 생각된다. 앞으로 언리얼을 다루면서 차츰 알아간 사실들을 계속 정리해 나갈 생각이기에 이 글을 읽는 사람은 참고 용도로만 사용하길 바란다.