# UE 基础篇

# Unreal 中常见类说明

# UObject

所有 UE 对象的基类

# UClass

用于描述 UObject 类型的类型

# FField

字段,组成复杂结构的基本单元

# UField

成员(成员字段 | 成员函数 | 成员结构),于 Unreal4.25 更新后移除了成员字段的作用,仅作为成员函数和成员结构使用。

https://docs.unrealengine.com/4.27/en-US/WhatsNew/Builds/ReleaseNotes/4_25/

# Unreal 中反射和类关系

# 反射机制

通俗来说就是通过一个实例获取到其类型和该类的全部定义。例如通过 Class_A 构造了 Instance_A,反射就是通过 Instance_A 获取到 Class_A 的成员。

反射可以实现的功能包括但不限于:根据类名动态创建实例,根据函数名调用函数,遍历实例的所有属性等等。

# 反射的实现

由于 C++ 本身是不支持反射的,那么 Unreal 是怎么做到反射效果的呢?

答案就是 UClass,前面说到 UClass 是描述 UObject 类型的类,言外之意,UObject 类的信息被记录在 UClass 对象里。

但是我们有很多的 UObject 对象,难道需要给每个 UObject 都自己创建一个 UClass 吗?

答案:是的,但是 Unreal 尽可能的简化了这部分,通过及自动生成的 gen.hgen.cpp 来帮助我们在无感知的情况下,自动创建和注册对应的 UClass 实例

// MyTestObject.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "MyTestObject.generated.h"
/**
 * 
 */
UCLASS()
class BLUEPRINT_API UMyTestObject : public UObject
{
	GENERATED_BODY()
	
};
// MyTestObject.cpp
#include "MyTestObject.h"

这里我们创建一个最简单的类,其继承自 UObject,Unreal 会生成 gen 文件:

// MyTestObject.generated.h
#include "UObject/ObjectMacros.h"
#include "UObject/ScriptMacros.h"
PRAGMA_DISABLE_DEPRECATION_WARNINGS
#ifdef BLUEPRINT_MyTestObject_generated_h
#error "MyTestObject.generated.h already included, missing '#pragma once' in MyTestObject.h"
#endif
#define BLUEPRINT_MyTestObject_generated_h
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_SPARSE_DATA
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_RPC_WRAPPERS
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_RPC_WRAPPERS_NO_PURE_DECLS
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_INCLASS_NO_PURE_DECLS \
private: \
	static void StaticRegisterNativesUMyTestObject(); \
	friend struct Z_Construct_UClass_UMyTestObject_Statics; \
public: \
	DECLARE_CLASS(UMyTestObject, UObject, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/BLUEPRINT"), NO_API) \
	DECLARE_SERIALIZER(UMyTestObject)
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_INCLASS \
private: \
	static void StaticRegisterNativesUMyTestObject(); \
	friend struct Z_Construct_UClass_UMyTestObject_Statics; \
public: \
	DECLARE_CLASS(UMyTestObject, UObject, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/BLUEPRINT"), NO_API) \
	DECLARE_SERIALIZER(UMyTestObject)
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_STANDARD_CONSTRUCTORS \
	/** Standard constructor, called after all reflected properties have been initialized */ \
	NO_API UMyTestObject(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); \
	DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyTestObject) \
	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyTestObject); \
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyTestObject); \
private: \
	/** Private move- and copy-constructors, should never be used */ \
	NO_API UMyTestObject(UMyTestObject&&); \
	NO_API UMyTestObject(const UMyTestObject&); \
public:
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_ENHANCED_CONSTRUCTORS \
	/** Standard constructor, called after all reflected properties have been initialized */ \
	NO_API UMyTestObject(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) : Super(ObjectInitializer) { }; \
private: \
	/** Private move- and copy-constructors, should never be used */ \
	NO_API UMyTestObject(UMyTestObject&&); \
	NO_API UMyTestObject(const UMyTestObject&); \
public: \
	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, UMyTestObject); \
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(UMyTestObject); \
	DEFINE_DEFAULT_OBJECT_INITIALIZER_CONSTRUCTOR_CALL(UMyTestObject)
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_PRIVATE_PROPERTY_OFFSET
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_12_PROLOG
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_GENERATED_BODY_LEGACY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
	Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_PRIVATE_PROPERTY_OFFSET \
	Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_SPARSE_DATA \
	Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_RPC_WRAPPERS \
	Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_INCLASS \
	Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_STANDARD_CONSTRUCTORS \
public: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS
#define Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_GENERATED_BODY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
	Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_PRIVATE_PROPERTY_OFFSET \
	Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_SPARSE_DATA \
	Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_RPC_WRAPPERS_NO_PURE_DECLS \
	Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_INCLASS_NO_PURE_DECLS \
	Demo_Source_BLUEPRINT_Public_MyTestObject_h_15_ENHANCED_CONSTRUCTORS \
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS
template<> BLUEPRINT_API UClass* StaticClass<class UMyTestObject>();
#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID Demo_Source_BLUEPRINT_Public_MyTestObject_h
PRAGMA_ENABLE_DEPRECATION_WARNINGS
    
// MyTestObject.gen.cpp
#include "UObject/GeneratedCppIncludes.h"
#include "BLUEPRINT/Public/MyTestObject.h"
#ifdef _MSC_VER
#pragma warning (push)
#pragma warning (disable : 4883)
#endif
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void EmptyLinkFunctionForGeneratedCodeMyTestObject() {}
// Cross Module References
	BLUEPRINT_API UClass* Z_Construct_UClass_UMyTestObject_NoRegister();
	BLUEPRINT_API UClass* Z_Construct_UClass_UMyTestObject();
	COREUOBJECT_API UClass* Z_Construct_UClass_UObject();
	UPackage* Z_Construct_UPackage__Script_BLUEPRINT();
// End Cross Module References
	void UMyTestObject::StaticRegisterNativesUMyTestObject()
	{
	}
	UClass* Z_Construct_UClass_UMyTestObject_NoRegister()
	{
		return UMyTestObject::StaticClass();
	}
	struct Z_Construct_UClass_UMyTestObject_Statics
	{
		static UObject* (*const DependentSingletons[])();
#if WITH_METADATA
		static const UE4CodeGen_Private::FMetaDataPairParam Class_MetaDataParams[];
#endif
		static const FCppClassTypeInfoStatic StaticCppClassTypeInfo;
		static const UE4CodeGen_Private::FClassParams ClassParams;
	};
	UObject* (*const Z_Construct_UClass_UMyTestObject_Statics::DependentSingletons[])() = {
		(UObject* (*)())Z_Construct_UClass_UObject,
		(UObject* (*)())Z_Construct_UPackage__Script_BLUEPRINT,
	};
#if WITH_METADATA
	const UE4CodeGen_Private::FMetaDataPairParam Z_Construct_UClass_UMyTestObject_Statics::Class_MetaDataParams[] = {
		{ "Comment", "/**\n * \n */" },
		{ "IncludePath", "MyTestObject.h" },
		{ "ModuleRelativePath", "Public/MyTestObject.h" },
	};
#endif
	const FCppClassTypeInfoStatic Z_Construct_UClass_UMyTestObject_Statics::StaticCppClassTypeInfo = {
		TCppClassTypeTraits<UMyTestObject>::IsAbstract,
	};
	const UE4CodeGen_Private::FClassParams Z_Construct_UClass_UMyTestObject_Statics::ClassParams = {
		&UMyTestObject::StaticClass,
		nullptr,
		&StaticCppClassTypeInfo,
		DependentSingletons,
		nullptr,
		nullptr,
		nullptr,
		UE_ARRAY_COUNT(DependentSingletons),
		0,
		0,
		0,
		0x001000A0u,
		METADATA_PARAMS(Z_Construct_UClass_UMyTestObject_Statics::Class_MetaDataParams, UE_ARRAY_COUNT(Z_Construct_UClass_UMyTestObject_Statics::Class_MetaDataParams))
	};
	UClass* Z_Construct_UClass_UMyTestObject()
	{
		static UClass* OuterClass = nullptr;
		if (!OuterClass)
		{
			UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_UMyTestObject_Statics::ClassParams);
		}
		return OuterClass;
	}
	IMPLEMENT_CLASS(UMyTestObject, 1743818385);
	template<> BLUEPRINT_API UClass* StaticClass<UMyTestObject>()
	{
		return UMyTestObject::StaticClass();
	}
	static FCompiledInDefer Z_CompiledInDefer_UClass_UMyTestObject(Z_Construct_UClass_UMyTestObject, &UMyTestObject::StaticClass, TEXT("/Script/BLUEPRINT"), TEXT("UMyTestObject"), false, nullptr, nullptr, nullptr);
	DEFINE_VTABLE_PTR_HELPER_CTOR(UMyTestObject);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
#ifdef _MSC_VER
#pragma warning (pop)
#endif

gen 里定义了非常多的宏,展开后大致就是描述如何创建出该 UObject 的 UClass 对象,生成工具通过读取 UCLASS()GENERATED_BODY() 等标注,来解析类的各种信息,如:类名、继承关系、元素名称和类型、函数等,然后再根据 gen 文件在创建 UClass 对象的时候把这些信息也添加上。

UCLASS()
class BLUEPRINT_API UMyTestObject : public UObject
{
	GENERATED_BODY()
	
    // 声明一下该属性
    UPROPERTY(EditAnywhere)
	FString ClassName;
    
    // 声明一下该函数
    UFUNCTION()
	TWeakObjectPtr<UPrimaryDataAsset> GetDataAsset(const FString& ClassName);
};

至于 UClass 是怎么创建的呢?这个后面再讲。接下来让我们看看 FField 和 UField。

# FField 和 UField

前面说到 UObject 的类信息是通过 UClass 来进行描述的,那么 UClass 用于描述类型信息的基本单位就是 Field。

Function 是个 Field,Enum 是个 Field,Property 是个 Field,Struct 和 Class 也是个 Field。

因此,一个类实际上就是一个个的 Field 互相组合嵌套而成的~,下面是 UField 的关系类图:

image-20220507111651007

值得一提的是 UField 被设计成了一个单链表结构,因此 MyTestObject 类的组织形式大概长这样(省略了很多细节):

image-20220507114050180

可以发现 MyTestObject 里面分成了 ChildPropertiesChildren 两部分,一边存储 Property,另一边存储 Function、Struct 等。

而划分规则是在 UStruct 定义的也就是从 UStruct 往下的派生中,开始支持嵌套了。

image-20220507114425921

那么 FField 在哪呢?它取代了 UProperty 的地位,自立门户了~

image-20220507114934510

由图中可以看出其实 UField 是继承自 UObject 的,好处是它的 UClass 也可以通过宏定义给创建出来,而代价是承载了 UObject 的冗余数据。这些数据对于 UClass 和 UFuntion 来说其实还可以接受,但是 UProperty 本身使用频繁且小巧,这么一来就显得很累赘。因此 Unreal 在 4.25 版本单独定义了一个 FField,来替代 UProperty 的职责,并且定义了一个 FFieldClass 作为 FField 的 UClass 类来实现类型系统。

# 类型系统的构建流程

前面也提到了,类的信息实际上是定义在 UClass 对象里,那么 UClass 对象的构建流程,顺序,以及如何通过名字查询等就有必要在此说明一下了:

注:以下内容仅仅简单说明,其中的实现细节较为复杂,不做展开,有兴趣可以阅读源码。

# 初始化 UObject 的 UClass 对象

由于 UObject 是所有对象的基类,因此它的 UClass 对象应该最先被创建的,事实也正是如此:

  • 创建 UClass 对象
  • 设置属性
    • SuperStruct:定义了类型的继承关系
    • ClassWithin:定义了 UClass 包含在哪个 UClass 内,用于描述嵌套关系

注:这里的初始化其实并不完全,UClass 内的 UFunction 和 UStruct 等都还没办法创建,相当于做了个占位

# 初始化 CoreUObject 的 UClass 对象

由于 UObject 是所有 CoreUObject 的基类嘛~所以 UObject 初始化好了,自然就轮到了 CoreUObject

初始化的内容和 UObject 的 UClass 一致,都是半成品,只保留了 SuperStruct 和 ClassWithin 的关系网

注:这一步其实没有初始化 UScriptStruct 和 UEnum,因为他们结构信息里面依赖了 UClass 所以得先把其他类的 UClass 初始化完毕才能轮到它们

# 注册之前初始化的 UClass 对象

到此我们就得到了所有 CoreUObject 的 UClass 对象的半成品,剩下的就是对它们进行组装,变成完全体

但是在组装之前,还得把原来遗漏的两个类型的 UClass 补上 ——UEnum 和 UScriptStruct,并把这些 UClass 实例装填到一个全局字典和全局链表里方便后续查询和遍历。

因此这一步需要做三件事:

  • 把 UClass 对象注册到全局字典和全局链表里,并设置一些属性:
    • NamePrivate:定义了对象的名字
    • ClassPrivate:定义了对象的类型关系,UClass 实例是从哪个 UClass 类创建的(这里都是 UClass 类,但是如果有其他继承的话,就不一样了
    • OuterPrivate:定义了对象的从属关系,UObject 所在的 UPackage
void UObjectBase::DeferredRegister(UClass *UClassStaticClass,const TCHAR* PackageName,const TCHAR* InName)
{
	check(UObjectInitialized());
	// Set object properties.
	UPackage* Package = CreatePackage(PackageName);
	check(Package);
	Package->SetPackageFlags(PKG_CompiledIn);
	OuterPrivate = Package;
	check(UClassStaticClass);
	check(!ClassPrivate);
	ClassPrivate = UClassStaticClass;
	// Add to the global object table.
	AddObject(FName(InName), EInternalObjectFlags::None);
	// Make sure that objects disregarded for GC are part of root set.
	check(!GUObjectArray.IsDisregardForGC(this) || GUObjectArray.IndexToObject(InternalIndex)->IsRootSet());
	UE_LOG(LogUObjectBootstrap, Verbose, TEXT("UObjectBase::DeferredRegister %s %s"), PackageName, InName);
}
  • 凭借现有的 UClass 对象初始化 UEnum 和 UScriptStruct 的 UClass 对象
/**
 * Call StaticStruct for each struct...this sets up the internal singleton, and important works correctly with hot reload
 */
static void UObjectLoadAllCompiledInStructs()
{
	// 创建 UPackage
    // ...
    
	// Load Structs
	for (const FPendingEnumRegistrant& EnumRegistrant : PendingEnumRegistrants)
	{
		EnumRegistrant.RegisterFn();
	}
	for (const FPendingStructRegistrant& StructRegistrant : PendingStructRegistrants)
	{
		StructRegistrant.RegisterFn();
	}
}
  • 补全 UClass 对象里的 Function、Property、Struct 等信息,并创建 UClass 实例的 CDO
// UObjectBase.cpp
static void UObjectLoadAllCompiledInDefaultProperties()
{
    TArray<UClass*> NewClasses;
    TArray<UClass*> NewClassesInCoreUObject;
    TArray<UClass*> NewClassesInEngine;
    TArray<UClass* (*)()> PendingRegistrants = MoveTemp(DeferredCompiledInRegistration);
    for (UClass* (*Registrant)() : PendingRegistrants)
    {
        // 核心代码在这里,这里会补全 UClass 信息 Registrant 是之前初始化的时候传进来的
        // MyTestObject 类对应的就是 Z_CompiledInDefer_UClass_UMyTestObject
        UClass* Class = Registrant(); 
        UE_LOG(LogUObjectBootstrap, Verbose, TEXT("UObjectLoadAllCompiledInDefaultProperties After Registrant %s %s"), *Class->GetOutermost()->GetName(), *Class->GetName());
        
        // 这里把 UClass 对象分三类 CoreUObject Engine Other
        if (Class->GetOutermost()->GetFName() == GLongCoreUObjectPackageName)
        {
            NewClassesInCoreUObject.Add(Class);
        }
        else if (Class->GetOutermost()->GetFName() == LongEnginePackageName)
        {
            NewClassesInEngine.Add(Class);
        }
        else
        {
            NewClasses.Add(Class);
        }
    }
    {
        // 这里就是按照类别依次创建每个 UClass 实例 的 CDO
        // 顺序依次是 CoreUObject > Engine > Other,因为 UClass 依赖关系也是这个关系
        for (UClass* Class : NewClassesInCoreUObject)
        {
            Class->GetDefaultObject();
        }
        // ...
    }
}
// MyTestObject.gen.cpp
static FCompiledInDefer Z_CompiledInDefer_UClass_UMyTestObject(Z_Construct_UClass_UMyTestObject, &UMyTestObject::StaticClass, TEXT("/Script/Blueprint"), TEXT("UMyTestObject"), false, nullptr, nullptr, nullptr);
UClass* Z_Construct_UClass_UMyTestObject()
{
    static UClass* OuterClass = nullptr;
    if (!OuterClass)
    {
        UE4CodeGen_Private::ConstructUClass(OuterClass, Z_Construct_UClass_UMyTestObject_Statics::ClassParams);
    }
    return OuterClass;
}
// 这里就定义了 function property 等信息
struct Z_Construct_UClass_UMyTestObject_Statics
{
    static UObject* (*const DependentSingletons[])();
    #if WITH_METADATA
    static const UE4CodeGen_Private::FMetaDataPairParam Class_MetaDataParams[];
    #endif
    static const FCppClassTypeInfoStatic StaticCppClassTypeInfo;
    static const UE4CodeGen_Private::FClassParams ClassParams;
};

到此,所有 UObject 的 UClass 对象就都创建好了,并且 UClass 对象里面也记录了各个类的信息用于实现反射效果。

# 其他介绍

# CDO

ClassDefaultObject,说白了就是 UClass 实例通过默认参数构建出的一个 UObject 实例,这个 UObject 对象有着所有字段的默认值

# UPackage

UPackage 是 Unreal 对于资源的包装,一个 uasset 文件对应了一个 Package,而把这个 Package 加载到内存后,创建的实例就是 UPackage

而一个 uasset 可以包含多个类、函数、变量

# UObject 和 UClass 常用字段说明

UObject:

  • ClassPrivate:这个对象对应的 UClass 实例
  • NamePrivate:对象名称,一般是类名
  • OuterPrivate:这个 UObject 是在哪个 UPackage 里

UClass:

  • ClassWithin:这个类被包含在了其他类的时候,通过该字段指向上一层
  • SuperStruct:描述 UObject 的父类
  • Children:描述 UObject 里面有哪些 Function、Struct
  • ChildProperties:描述 UObject 里面有哪些 Property

image-20220507154703061

# 总结

最后做个总结,所有 Unreal 内的对象都派生自 UObject,并且通过对应的标注 —— UCLASS()、GENERATED_BODY()... 来生成宏定义,用于创建对应的 UClass 对象。

而 UClass 对象则保存了 UObject 类里的各种定义信息,并存储在全局字典内,通过名字查找的方式实现了反射。并且所有 UObject 类的继承关系也一并保存在了 UClass 对象里。

此外 UClass 还会存储一个 UObject 的 CDO 对象,并且负责 UObject 对象的构造,可谓是责任重大。

而 UClass 又是通过 UField 定义的 Function、Struct、Enum 以及 FField 定义的 Property 所组成。

更新于 阅读次数

请我[恰饭]~( ̄▽ ̄)~*

鑫酱(●'◡'●) 微信支付

微信支付

鑫酱(●'◡'●) 支付宝

支付宝