[Unreal] UObject
UObject
- UObject는 Unreal Engine에서 가장 기본이 되는 객체다
- UCLASS 매크로 선언으로 UObject를 관리하는 시스템이 이를 인식한다
- Reflection System을 통해 Garbage Collection을 수행하는 등의 역할
- 모든 언리얼 오브젝트는 실행 초기의 런타임 과정에서 다음 두가지를 생성한다
- UClass 인스턴스
- CDO
Note
UObject는 U 접두사로 시작하고, 일반 C++ 클래스는 F로 시작한다
ex) FVector 등
UObject의 생성 및 삭제 시점
생성 시점
// ...
// Unreal Header Tool이 빌드 시 자동 생성
#include "MyObject.generated.h"
// 클래스를 리플렉션 시스템에 등록한다
UCLASS()
// MYPROJECT_API 매크로는 해당 클래스의 기능을 다른 모듈에서 쓸 수 있게 해준다
// 요게 없으면 다른 모듈에서 사용 시 심볼을 Link할 수 없어서 Link 에러가 발생한다
class MYPROJECT_API UMyObject : public UObject
{
// MyObject.generated.h에서 이 매크로를 정의한다
GENERATED_BODY()
};
// 가장 일반적인 방법이다
UMyObject* MyObject = NewObject<UMyObject>();
// 아래 방법은 new operator를 overload해야 한다
UMyObject* Object1 = new UMyObject();
// 아래 방법은 4.8 부터 deprecated 되었다
// UMyObject* Object3 = NewNamedObject<UMyObject>();
// UMyObject* Object4 = ConstructObject<UMyObject>();
// Component 생성
UMyObject::UMyObject()
{
MyActorComponent = CreateDefaultComponent<UMyActorComponent>(TEXT("MyActorComponent"));
}
// Actor 생성
// ...
AActor* MyActor = GetWorld()->SpawnActor<UMyActor>(SpawnInfo);
- NewObject로 새로운 UObject 인스턴스 생성 시
- Actor Spawn이나 Component의 CreateComponent 등
삭제 시점
- 해당 객체의 Strong Reference 객체가 존재하지 않을 때, GC Tick에서 Delete
- 엔진이 더 이상 해당 Object를 참조하지 않을 때, Garbage Collection System에 의해 소멸처리 된다
- 마지막으로 참조하던 포인터가 null이 되는 순간
- Actor Destroy, ActorComponent의 DestroyComponent 등 명시적 호출
- Level이 Destroy될 때 잠초중인 객체들 삭제
Class Default Object
- 각 UCLASS는 CDO라는 하나의 오브젝트 객체를 유지
- CDO는 생성자에 의해 생성된 이후, 수정되지 않는 탬플릿 용도의 오브젝트
- Read-Only지만, GetClass() 메서드를 통해 접근 가능
Garbage Collector
- Reference Graph를 만들어서 오브젝트들이 Graph의 Root로부터 참조 여부를 검사한다
- 연결되지 않았다고 판단된 오브젝트들은 모두 GC 대상이 된다
MarkAsGarbage(), IsValid()의 동작 방식
MarkAsGarbage
- bPendingKillDisabled는 GPendingKillEnabled의 역 값이며, 이 값이 true인 경우 Garbage Collection에 의해 자동으로 null 처리되지 않는다
- 어떤 Object로의 reference를 모두 null로 만든다
- 다음 Garbage Collection이 작동할 때, 이 Object를 소멸한다
Note
MarkPendingKill
위 함수는 더 이상 사용하지 않고, MarkAsGarbage가 유사한 방식으로 작동하여 대체한다
IsValid
- 인자로 받은 Pointer가 현재 사용되는 Object를 가리키고 있는지 확인
- Reference Graph에서 탐색하고 결과값이 존재하는지 여부를 bool 값으로 반환
IsValid()와 IsVAalidLowLevel()의 차이점
- IsValid는 Object Pointer가 null 여부 및 Garbage collection 대상 여부인지를 확인
- IsValidLowLevel은 IsValid와 동일한 null check를 수행하지만 extra check를 수행한다
- this == nullptr -> null 상태인지 확인
- !ClassPrivate -> Reflection System에 올바르게 등록되었는지 확인
- GUObjectArray.IsValid(this) -> GUObjectArray에서 정상적인 index를 갖고 있는지, 즉 범위 안에 있는지 혹은 해당 index가 다른 Object로 override된 상태가 아닌지 확인
DefaultSubobject란?
- Subobject란 Actor를 구성하는 Component를 의미한다
- 부모 Actor에 할당되는 자식 Actor 역시 ChildActorComponent로 분류한다
- 부모 Actor가 생성 및 초기화하면서 자동으로 생성되는 Component들 역시, 생성된 이후 Default 값으로 초기화된 상태로 부모 Actor에 Attach 된다
Actor vs. Object
Actor
- AActor에서 파생된 클래스
- Actor는 그 자체로 하나의 완전하고 독립된 형태의 게임 오브젝트 단위
- World 및 레벨에 배치할 수 있는 게임 오브젝트
// World.h
class UWorld
{
// ...
/** Persistent level containing the world info, default brush and actors spawned during gameplay among other things */
UPROPERTY(Transient)
TObjectPtr<class ULevel> PersistentLevel;
// ...
}
// Level.h
class ULevel
{
// ...
/** Array of all actors in this level, used by FActorIteratorBase and derived classes */
TArray<TObjectPtr<AActor>> Actors;
/** Array of actors to be exposed to GC in this level. All other actors will be referenced through ULevelActorContainer */
TArray<TObjectPtr<AActor>> ActorsForGC;
// ...
}
- UWorld -> PersistentLevel에서 관리한다
- Actor Cluster 생성 여부에 따라 ActorsForGC 또는 Actors 리스트로 분류된다
Object
- UObject에서 파생된 클래스
- Object는 특정 요소가 강조된 일부 혹은 부분 단위
- Component가 Object를 상속하고, Actor를 구성하는 역할을 한다는 데에서 생각해보면…
월드에 독립적으로 배치 가능하다는 의미
- 레벨 에디터에 런타임에서 독립적으로 생성/관리될 수 있다는 의미
- 월드의 아웃라이너에 표시되며, 생명주기와 연동
UCLASS 매크로
- 언리얼 오브젝트에 대한 계층 구조, 멤버 변수, 함수, 정보 등을 기록한다
- 이른바 메타 정보
- 이 매크로를 선언함으로써 언리얼 엔진이 클래스에 접근해 정보를 찾아 열람하고 사용할수 있다는 뜻
- 런타임에 특정 클래스를 검색해 타입을 알아내고, 인스턴스의 멤버 변수 값을 변경하거나 특정 인스턴스의 멤버 함수를 호출하는 것이 가능
- 리플렉션 같은 거라고 생각하면 될 듯?
- 이 매크로로 UClass 클래스를 생성하고, UClass 클래스는 CDO를 생성
- UObject에게 UClass의 레퍼런스를 전달한다
UClass 클래스
- UCLASS 매크로가 선언된 UObject의 파생 클래스와는 별개로, 리플렉션을 위한 클래스
- 아래와 같은 상속 계층을 가지고 있다
- UObjectBase
- UObjectBaseUtility
- UObject
- UField 외 사용자가 생성한 C++ 클래스 등등
- UField부터는 리플렉션 데이터 관련 객체이다
- UEnum - C++ 열거형을 위한 리플렉션 테이터 클래스
- FProperty - C++ 멤버 변수, 함수 인자를 위한 리플렉션 데이터 클래스
- UStruct
- UFunction - C++ 함수를 위한 리플렉션 데이터 클래스
- UScriptStruct - C++ 구조체를 위한 리플렉션 데이터 클래스
- UClass - C++ 클래스를 위한 리플렉션 데이터 클래스
- UBlueprintGeneratedClass
- UDynamicClass
- UObject
- UObjectBaseUtility
- UObjectBase
- 컴파일 타임에는 메타 데이터를 수집, 저장하고 개별 UObject에 대한 UClass를 생성한다
- UClass의 인스턴스를 생성하지는 않는다
- 런타임에 UObject의 UClass 인스턴스를 생성하고, CDO를 생성 및 유지한다
- 이 때 런타임이란, 에디터 런타임 또는 패키징된 게임(exe)를 의미한다
- 수집된 리플렉션 정보는 UClass에 보관되고, GetClass 함수를 통해 런타임에 접근할 수 있다
- 컴파일 타임에는 StaticClass를 사용해 접근한다
- 이 함수는 UHT를 통해 자동으로 생성된다
- 컴파일 타임에는 StaticClass를 사용해 접근한다
- 코드 모듈이 로드되었을 때, 즉 엔진 초기화 시점에 생성된다
- 모듈을 리로드하는 핫리로드일 때도 다시 생성한다
하나의 UObject에 2개의 인스턴스가 생성된다
- 컴파일 타임에 UClass 생성 → 런타임에 CDO 생성
- 두 인스턴스 모두 읽기 전용으로 사용
UClass 인스턴스
- 언리얼 엔진이 이 클래스의 인스턴스들을 어떻게 다뤄야 하는지에 대한 전용 리플렉션 데이터 포함
- 클래스의 메타데이터, 프로퍼티 정보, 함수 정보 포함
CDO 인스턴스
- UClass에서 생성된 Object 인스턴스
- 클래스의 기본값들을 저장하는 템플릿 역할
- 새 인스턴스 생성 시 CDO에서 초기값을 복사해옴
Unreal Header Tool
- UObject 파생 유형에 제공되는 함수성을 활용하기 위해 해당 유형에 대해 헤더 파일에서 전처리 단계를 거쳐줘야 필요한 정보를 수집할 수 있다
- 유저가 정의하는 대부분의 클래스(UObject에서 파생할테니까…)에서 언리얼 엔진이 제공하는 기능을 활용하기 위해서는, 해더파일에 전처리 단계가 이뤄져야 한다
- 이 단계가 UHT에서 이뤄진다
#pragma once
#include 'Object.h'
#include 'MyObject.generated.h'
// UCLASS 매크로는 UMyObject 를 언리얼 엔진에 보이도록 만듭니다.
// 이 매크로는 다양한 클래스 지정자 지원을 통해 클래스에 어떤 기능을 켜고 끌 지 결정할 수 있습니다.
UCLASS()
// MyProject 가 UMyObject 클래스를 다른 모듈에 노출시키고자 한다면, MYPROJECT_API 지정이 필수입니다.
class MYPROJECT_API UMyObject : public UObject
{
// UObject로서 정상적으로 작동하도록 추가 함수를 생성해주는 매크로
// 등록 함수를 자동 생성해주고 실행
// StaticClass() 같은 함수도 포함시켜준단다 (?)
GENERATED_BODY()
};
출처
- https://docs.unrealengine.com/5.0/ko/creating-objects-in-unreal/#newobject
- https://forums.unrealengine.com/t/discussion-the-ab-use-of-isvalidlowlevel/21795
- https://unrealcommunity.wiki/revisions/6100e8119c9d1a89e0c31b32
- https://docs.unrealengine.com/4.26/en-US/ProgrammingAndScripting/ProgrammingWithCPP/Modules/API/