본문 바로가기

Unreal Engine

Unreal Engine CDO, FObjectInitializer

CDO(Class Default Object)

언리얼 엔진에서는 모든 UClass에 대해 해당 클래스의 유일하고 영구적인 인스턴스를 자동으로 생성하는데, 이것이 클래스의 기본 객체인 CDO이다. 즉 해당 클래스에 정의된 모든 속성의 기본값을 담고 있는 유일 객체라고 볼 수 있다.

 

CDO의 특징

1. 각 UClass에는 기본 값을 갖는 단 하나의 CDO만 존재한다.

2. 언리얼 엔진 초기화 단계에서 CDO를 자동으로 생성하고 코드에서 명시적으로 CDO를 생성할 필요가 없다.

3. CDO는 루트 오브젝트로 가비지 컬렉터에 의해 수집되지 않으며 엔진 인스턴스가 존재하는 동안 계속 같이 존재한다.

4. 클래스에 선언된 모든 속성(UPROPERTY를 포함하여)의 기본 값은 CDO에 저장된다.

5. CDO는 게임 플레이 로직에 직접 사용되거나 월드 내의 오브젝트로 사용되지 않는다. 런타임에 사용이 불가능하다.

 

Q. CDO를 왜 만드는가?

A. 효율적인 오브젝트 인스턴스화 때문이다.

새로운 UObject를 생성할 때(NewObject<T>(), SpawnActor<T>() 등) 언리얼 엔진에서는 모든 속성을 처음부터 초기화할 필요가 없다. 엔진이 초기화될 때 미리 생성해둔 CDO에서 빠르게 메모리 복사를 수행하여 효율적으로 오브젝트를 복사 생성한다. 즉 생성을 요청받으면 처음부터 끝까지 만들지 않고 CDO에서 엔진 초기화 당시 만들어 놓은 CDO를 복사한 뒤, 바뀐 값만 처리하는 방식을 사용한다.

 

 

FObjectInitializer

FObjectInitializer는 UObject 파생 클래스의 생성자에 전달되는 인스턴스이다. C++ 생성자가 호출된 후 UObject의 생성을 마무리 한다. CDO가 처음 생성될 때 한 번 호출되며 FObjectInitializer의 인스턴스에 사용된 함수들이 있다면 반영하여 CDO를 생성한다.

 

주요 함수는 다음과 같다.

  • CreateDefaultSubobject<T>(FName SubobjectName)
    가장 많이 보는 함수로 클래스의 CDO에 포함될 기본 서브 오브젝트를 생성하고 등록한다. 이 함수로 생성된 컴포넌트들은 해당 클래스의 모든 인스턴스에 자동으로 포함된다.
  • DoNotCreateDefaultSubobject(FName SubobjectName)
    현재 클래스가 상속받은 부모 클래스에서 특정 SubobjectName을 가진 기본 서브오브젝트를 생성하지 않도록 지시한다.
    부모 클래스에서 기본적으로 생성되는 컴포넌트가 자식 클래스에서는 필요 없거나 다른 방식으로 처리하고 싶을 때 주로 사용한다. 예를들어 ACharacter를 상속받으면 UCharacterMovementComponent가 기본적으로 생성되는데 게임에 따라 이러한 기능이 필요 없을 때 사용하면 된다.
  • SetDefaultSubobjectClass<T>(FName SubobjectName, const UClass* Class)
    부모 클래스에서 정의된 특정 서브 오브젝트의 클래스를 자식 클래스에서 다른 클래스로 오버라이드할 때 사용된다. 
    예를들어 ACharacter를 상속받으면 UCharacterMovementComponent가 기본적으로 생성되는데 게임에 따라 이러한 기능을 커스터마이징할 필요가 있다고 할 때 내가 만든 클래스로 대체 가능하다.
    텔레포트로 이동, 그리드 기반 이동과 같이 기존의 CharacterMovement가 아예 필요없는 경우가 존재할 수 있는데 이런 경우에 유용하게 사용된다.
  • CreateOptionalDefaultSubobject<T>(FName SubobjectName)
    CreateDefaultSubobject와 유사하지만 이 함수로 생성된 서브 오브젝트는 선택적으로 간주될 수 있다. 특정한 상황에 엔진이 이 서브 오브젝트의 생성을 건너뛸 수 있는 유연성을 제공한다.
  • CreateEditorOnlyDefaultSubobject<T>(FName SubobjectName)
    에디터에서만 사용될 서브 오브젝트를 생성한다. 빌드된 게임에서는 이 컴포넌트가 존재하지 않는다. 주로 에디터에서 시각적인 도움을 주거나 특정 에디터의 전용 기능을 제공하는 컴포넌트를 만들때 사용된다.

실제 사용 예시

 

언리얼에서는 CreateDefaultSubobject와 같이 FObjectInitializer에 있는 함수를 사용하는 경우에 UCLASS(), GENERATED_BODY() 같은 언리얼 매크로가 자동으로 FObjectInitializer을 생성자에 삽입하는 기능을 제공한다.

마치 C++에서 비정적 멤버함수의 첫번째 숨겨진 인자로 자기 자신의 this 포인터를 삽입하는 것과 같이 생성자에서 FObjectInitializer을 매개변수로 넣지 않았음에도 여태까지 CreateDefaultSubobject 을 사용할 수 있었던 이유는 이러한 자동 삽입 매커니즘이 숨어있었기 때문이다.

이 과정은 DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL 매크로에 의해 자동 처리된다.

 

추가적으로 FObjectInitializer의 함수를 생성자에서 사용하지 않는 경우에는 생성자에 FObjectInitializer을 자동으로 삽입하지 않는다.

AMyActor::AMyActor(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    // StaticMeshComponent 컴포넌트를 생성하고 CDO에 등록한다.
    // 이는 이 액터의 모든 인스턴스가 기본적으로 이 컴포넌트를 가지도록 한다.
    // CreateDefaultSubobject는 내부적으로 FObjectInitializer::Get()를 호출해 적절히 처리한다. 따라서 이 경우에는 앞에 ObjectInitializer를 붙이지 않아도 생성이 된다.
    //StaticMeshComponent = ObjectInitializer.CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComponent"));
	StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComponent"));


    // 만약 부모 클래스에서 'SomeOtherComponent'라는 이름의 컴포넌트가 자동으로 생성되는데,
    // 이 자식 클래스에서는 생성하고 싶지 않다면 다음과 같이 할 수 있다.
    Super(ObjectInitializer.DoNotCreateDefaultSubobject(TEXT("SomeOtherComponent")));

    // 만약 부모 클래스에서 'CharacterMovement'라는 이름의 컴포넌트가 UCharacterMovementComponent 타입으로 생성되는데,
    // 이를 커스텀 파생 클래스로 바꾸고 싶다면 다음과 같이 할 수 있다.
    Super(ObjectInitializer.SetDefaultSubobjectClass<UMyCustomMovementComponent>(ACharacter::CharacterMovementComponentName));
}