Game Programming/Unreal Engine

1. UObject에 대해 알아보자

타자치는 문돌이 2024. 5. 13. 18:41

언리얼 엔진에서 C++ 클래스를 생성할 때, 기본이 되는 클래스를 선택할 수 있다. 새로 생성한 클래스는 선택한 클래스를 상속받는다.


모든 클래스에서 언리얼 엔진의 모든 클래스의 부모 클래스인 UObject 클래스를 찾을 수 있다.

여기서 든 의문점은 "클래스를 만들면 UObject를 상속받아야 하는가?"였다.
일단 Object.h에서 UObject를 찾아보았다.

/*=============================================================================
    Object.h: Direct base class for all UE objects
=============================================================================*/
/**
 * The base class of all UE objects. The type of an object is defined by its UClass.
 * This provides support functions for creating and using objects, and virtual functions that should be overridden in child classes.
 *
 * @see https://docs.unrealengine.com/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/Objects
 */

주석은 "모든 UE Object의 Base Class"라고 소개한다.
자세한 내용을 위한 링크도 적혀있다.
https://docs.unrealengine.com/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/Objects

 

https://dev.epicgames.com/documentation/en-us/unreal-engine/objects-in-unreal-engine/?application_version=5.4

 

dev.epicgames.com

 

언리얼에는 게임 오브젝트를 처리하기 위한 강력한 시스템이 있습니다. 언리얼의 오브젝트에 대한 베이스 클래스는 UObject 입니다. UCLASS 매크로는 UObject 에서 파생된 클래스에 태그를 지정하여 UObject 처리 시스템이 인식하도록 할 수 있습니다.

UObject를 상속받은 클래스는 UCLASS 매크로를 사용해 언리얼 엔진이 인식하는 클래스가 된다.
UObject가 아닌 클래스는 언리얼 엔진이 인식하지 못해 블루프린트에서 사용할 수 없다.

아무것도 상속받지 않는 NonUEClass라는 클래스를 만들어보자.

#include "CoreMinimal.h"

class TEST_UNREAL5_API NonUEClass
{
public:
    NonUEClass();
    ~NonUEClass();
};


언리얼 엔진이 인식하는 클래스가 아니어서 이 클래스를 기반으로 하는 블루프린트를 만들 수 없다.

이번에는UObject를 상속받는 UEClass를 만들어보자

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "UEClass.generated.h"

UCLASS(Blueprintalbe)
class TEST_UNREAL5_API UUEClass : public UObject
{
    GENERATED_BODY()

};

이 클래스로는 UCLASSBlueprintable을 넣어 블루프린트를 만들 수 있다.

UEClassNonUEClass를 변수로 가지는 클래스를 만들어보자.

#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "NonUEClass.h"
#include "UEClass.h"
#include "HasNonUEPawn.generated.h"

UCLASS()
class TEST_UNREAL5_API AHasNonUEPawn : public APawn
{
    GENERATED_BODY()

public:
    AHasNonUEPawn();

protected:
    virtual void BeginPlay() override;

public:
    virtual void Tick(float DeltaTime) override;
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
    NonUEClass NonUEclass;

    UPROPERTY()
    UUEClass* UEclass;
};

NonUEClass는 언리얼 클래스가 아니기 때문에 UPROPERTY 매크로를 사용할 수 없다. 따라서 블루프린트에서 사용할 수 없다.

UEClassUPROTERTY를 사용할 수 있으나NonUEClass처럼 변수로 선언할 수 없고, 포인터만 선언할 수 있다.

Fatal error:
UObject(const FObjectInitializer&) constructor called but it's not the object 
that's currently being constructed with NewObject. 
Maybe you are trying to construct it on the stack, which is not supported.

변수로 선언하면 이런 에러 메시지와 함께 크러시가 일어난다.

그러면 UObject는 어떻게 초기화해야 하지?

UObject는 절대로 new 연산자를 사용하면 안 됩니다. 모든 UObject는 언리얼 엔진으로 관리되는 메모리이며 가비지 컬렉션됩니다. new 또는 delete를 사용하여 메모리를 수동으로 관리하면 메모리가 손상될 수 있습니다.

UObject는 생성자 실행 인자를 지원하지 않습니다. 모든 C++ UObject는 엔진 시작 시 초기화되며, 엔진은 디폴트 생성자를 호출합니다. 디폴트 생성자가 없으면UObject가 컴파일되지 않습니다.
UObject 생성자는 가벼워야 하고 디폴트값과 서브 오브젝트를 구성하는 데에만 사용되어야 하며, 생성 시 다른 함수 기능을 호출해서는 안 됩니다. 액터 및 액터 컴포넌트의 경우, 대신에 초기화 함수 기능을 BeginPlay() 메서드에 넣어야 합니다.
UObject는 런타임에 NewObject를 사용하거나 생성자의 경우CreateDefaultSubobject를 사용하여 생성해야 합니다.

UObjectnew대신 NewObjectCreateDefaultSubobject를 사용해 생성해야 한다.
이렇게 생성해야 하는 이유는 다음에 알아보자. (언리얼에서 클래스를 관리하는 CDO와 관련이 있다.)

결론

UObject를 상속받아 클래스를 언리얼 클래스로 사용할 수 있다.
언리얼 클래스는

  • 가비지 컬렉션
  • 블루프린트 사용 가능
  • 레퍼런스 업데이트
    • 리플렉션
    • 직렬화
    • 디폴트 프로퍼티 변경 사항의 자동 업데이트
    • 자동 프로퍼티 초기화
    • 자동 에디터 통합
    • 런타임에 사용할 수 있는 타입 정보
    • 네트워크 리플리케이션

라는 장점이 있다.
사용할 때는 생성자로 new 대신 NewObject를 사용해야한다.
블루프린트를 사용하지 않는다면 필요해 보이지는 않으나 가비지 컬렉션이 없어 메모리 관리에 유의해야 할 것이다.