언리얼 C++ 프로그래밍

[언리얼 C++] 언리얼 오브젝트(UObject)

언린이 2021. 7. 3. 14:13

언리얼 엔진에는 게임 오브젝트를 처리하는 견고한 시스템이 존재합니다.

바로 언리얼 엔진 오브젝트의 베이스 클래스인 UObject 클래스입니다.

UObject 클래스에서 파생되는 클래스에 UCLASS 매크로를 사용하여 태그를 해주면 해당 클래스는 UObject 처리 시스템에서 인식되게 됩니다.

 

 

1. UCLASS 매크로

UCLASS 매크로는 UObject에게 자신의 언리얼에서 기반으로 삼은 유형에 대해 설명해 주는 UClass로의 레퍼런스를 넘겨줍니다. 각각의 UClass는 Class Default Object, 줄여서 CDO라 불리는 오브젝트를 하나 유지합니다.

CDO는 본질적으로 기본 "템플릿" 오브젝트로, 클래스 생성자에 의해 생성된 이후 변경되지 않습니다.

오브젝트 인스턴스에 대해 UClass와 CDO 둘 다 접근할 수는 있지만, 일반적으로 읽기 전용으로 간주되어야 합니다.

오브젝트 인스턴스에 대한 UClass는 GetClass() 함수를 사용하여 언제든 접근할 수 있습니다.

 

 

2. 프로퍼티와 함수 유형

UObject는 멤버 변수와 어떤 유형의 함수도 가질 수 있습니다. 그러나, 언리얼 엔진에서 그러한 변수나 함수를 인식하고 조작할 수 있도록 하기 위해서는, 반드시 특수한 매크로로 마킹하여 특정 유형 표준에 맞춰줘야 합니다.

 

- 프로퍼티 선언

UPROPERTY([specifier, specifier, ...], [meta(key=value, key=value, ...)])
Type VariableName;

프로퍼티는 UPROPERTY 매크로를 사용하여 프로퍼티 메타데이터와 변수 지정자를 붙여 선언합니다.

프로퍼티 유형에 대한 설명은 언리얼 엔진 프로퍼티 문서를 참고하시기 바랍니다. (프로퍼티 | 언리얼 엔진 문서 (unrealengine.com))

 

- 함수 유형 선언

UFUNCTION([specifier, specifier, ...], [meta(key=value, key=value, ...)])
ReturnType FunctionName([Parameter, Parameter, ...])

프로퍼티 선언과 비슷한 방식으로, 함수 유형은 UFUNCTION 매크로를 사용하여 선언합니다.

함수 유형에 대한 설명 또한 언리얼 엔진 UFunction 문서를 참고하시기 바랍니다. (UFunction | 언리얼 엔진 문서 (unrealengine.com))

 

 

3. 오브젝트 생성

함수 설명
NewObject<class>() 자동 생성되는 이름으로 새 인스턴스를 만듭니다. 단순한 클래스를 생성할 때 자주 쓰이는 방법입니다.
new 생성자가 인수를 필요로 할 때와 같이, 특정 로우 레벨 환경에서 오브젝트를 생성할 때 사용합니다.

위 함수들은 게임플레이용 UObject 클래스 인스턴스 생성을 위한 함수들입니다.

new 함수는 C++에서 사용하신 것처럼 쓰면 되기 때문에, NewObject() 함수에 대해 자세히 알아보겠습니다.

 

- NewObject

template<class T>
T* NewObject
(
    UObject* Outer=(UObject*)GetTransientPackage(),
    UClass* Class=T::StaticClass() 
)

NewObject() 함수는 UObjectGlobals.h 파일에 다양한 형태로 오버로딩되어 있습니다.

위 코드는 NewObject() 함수의 가장 간단한 구현부입니다.

선택사항으로 outer 오브젝트와 클래스 정보를 받은 다음 자동 생성된 이름으로 인스턴스를 새로 만듭니다.

 

Outer : 생성될 오브젝트를 소유하는 UObject 객체

Class : 생성될 오브젝트의 클래스를 나타내는 UClass 정보

 

UItemData* ItemData = NewObject<UItemData>();

UItemData* ItemDataWithParam = NewObject<UItemData>(this, UItemData::StaticClass());

위 예제 코드처럼 NewObject() 함수를 사용하여 오브젝트를 생성해줄 수 있으며, 파라미터는 선택사항입니다.

 

template<class T>
T* NewObject
(
    UObject* Outer, 
    FName Name, 
    EObjectFlags Flags = RF_NoFlags, 
    UObject const* Template=NULL
)

위 코드는 많이 사용되는 NewObject() 함수의 구현부입니다.

가장 간단한 버전과는 달리 새 인스턴스의 이름은 물론이고 오브젝트 플래그와 템플릿 오브젝트를 인수로 지정할 수도 있습니다.

파라미터에서 Outer와 Name은 필수로 넘겨줘야 하고 플래그와 템플릿 오브젝트에 대한 정보는 선택사항입니다.

 

Outer : 생성될 오브젝트를 소유하는 UObject 객체

Name : 새 오브젝트에 설정할 이름

Flags : 새 오브젝트를 설명하는 FObjectFlagsEnum 값

Template : 새 오브젝트 생성시 템플릿으로 사용할 UObject

 

위 파라미터에서 플래그에 대해 간단하게 알아보겠습니다.

 

오브젝트 플래그는 FObjectFlagsEnum 값을 사용하여 표현합니다.

FObjectFlagsEnum 값은 오브젝트를 빠르고 간단하게 기술하는데 사용됩니다.

오브젝트 유형, 가비지 컬렉션 처리 방식, 오브젝트가 수명의 어느 단계에 있는가 등을 기술하는 여러가지 플래그가 존재합니다.

 

플래그 유형 플래그 이름 설명
오브젝트 유형 RF_Public 이 오브젝트는 포함된 패키지 밖에서도 보입니다.
  RF_Standalone 이 오브젝트는 레퍼런스되지 않아도 편집 가능 상태로 유지됩니다.
가비지 컬렉션 RF_MarkAsRootSet 이 오브젝트는 어떠한 것에 레퍼런스되지 않아도 가비지 콜렉팅이 되지 않습니다.
  RF_TagGarbageTemp 이 오브젝트는 가비지 컬렉션을 사용하는 다양한 유틸리티가 사용할 수 있도록 마킹됩니다. 이 플래그는 가비지 콜렉터 자체적으로는 해석하지 않습니다.
오브젝트 수명 RF_AsyncLoading 이 오브젝트는 비동기 로딩중입니다.
  RF_NeedPostLoad 이 오브젝트는 사후 로딩이 필요합니다.

위 표에서 보시는 것처럼 플래그 유형에 따라 여러가지 플래그가 존재합니다.

더 다양한 플래그에 대한 설명을 원하신다면 언리얼 엔진 UObject 인스턴스 생성 문서를 참고하시기 바랍니다. (UObject 인스턴스 생성 | 언리얼 엔진 문서 (unrealengine.com))

 

UItemData* ItemData = NewObject<UItemData>(this, TEXT("ItemData"));

UItemData* ItemData = NewObject<UItemData>(this, TEXT("ItemData"), RF_MarkAsRootSet);

위 예제 코드처럼 Outer와 새 오브젝트에 지정할 이름만을 넘겨 생성해 줄 수도 있고, 플래그 값도 함께 넘겨서 생성해 줄 수도 있습니다.

위 예제에서처럼 RF_MarkAsRootSet 플래그를 사용하여 오브젝트를 생성하면 해당 오브젝트는 가비지 콜렉팅이 되지 않습니다. 그러므로 이렇게 생성된 오브젝트는 사용이 끝났다면 꼭 메모리를 해제해주셔야 합니다.

 

 

4. UObject 클래스 상속의 장점

마지막으로 왜 언리얼에서 UObject 클래스를 상속받고 UCLASS 매크로를 사용하는 지에 대해 알아보겠습니다.

결국엔 생성한 클래스를 UObject 처리 시스템이 인식할 수 있도록 만들어주는 것입니다. 모든 경우에 이 시스템을 사용하는 것이 필수적이지도 심지어 적절하지 않을 수도 있지만, 이 시스템을 사용하면 다음과 같은 장점이 있습니다.

 

- 가비지 컬렉션

- 레퍼런스 업데이트

- 리플렉션

- 시리얼라이제이션

- 디폴트 프로퍼티 변경사항 자동 업데이트

- 자동 프로퍼티 초기화

- 자동 에디터 통합

- 런타임에 유형 정보 사용가능

- 네트워크 리플리케이션

 

위 처럼 다양한 처리 기능들을 제공해주기 때문에 언리얼 프로젝트에서 매끄러운 프로그래밍 설계를 하고 싶으시다면 UObject 클래스를 상속받는 것이 유리할 수 있습니다.