UE4直接使用C++作为逻辑层语言,这样引擎层与逻辑层语言统一,不需要胶水代码去转发,消除了逻辑层和引擎层的交互成本。为了便于开发,UE4对C++做了一些包装,比如反射和垃圾回收,大大减轻C++开发的难度。本文结合UE4官方C++编程指南文档,对C++相关特性做一些描述和总结。
C++本来是不支持反射的,只有一个基本的RTTI(运行时类型信息)特性,仅能在运行时获取对象的类型信息,无法得到成员变量和函数列表信息。如果我们要对成员变量进行序列化(存档/读档),需要自己写很多辅助的读取和写入方法,非常麻烦。
UE4在C++编译开始前,使用工具UnrealHeaderTool,对C++代码进行预处理,收集出类型和成员等信息,并自动生成相关序列化代码。然后再调用真正的C++编译器,将自动生成的代码与原始代码一并进行编译,生成最终的可执行文件。这个过程类似于Qt的qmake预处理机制。
拿出我们之前工程的代码来分析:
1 // Fill out your copyright notice in the Description page of Project Settings. 2 #pragma once 3 #include "GameFramework/Actor.h" 4 #include "MyActor.generated.h" 5 6 UCLASS() 7 class HELLOUE4_API AMyActor : public AActor 8 { 9 GENERATED_BODY() 10 public: 11 // 默认构造函数,初始化一些成员属性。 12 AMyActor(); 13 protected: 14 // 游戏开始或者被创建出来后调用。 15 virtual void BeginPlay() override; 16 public: 17 // 每帧都会被调用 18 virtual void Tick(float DeltaTime) override; 19 20 // 这个变量会出现在编辑器编辑界面。 21 UPROPERTY(EditAnywhere) 22 int32 MyID; 23 24 private: 25 // 这是个内部变量。不会出现在编辑器界面。 26 float RunningTime; 27 };
这个代码文件包含了一些特殊的头文件和宏,下面逐一介绍:
名称 | 描述 |
MyActor.generated.h | 这个文件是UE4自动生成的,里面存贮了UE4收集的类型信息。 |
UCLASS | 告诉UE4这个类需要收集类型信息 |
UPROPERTY | 告诉UE4这个成员变量的信息需要被收集 |
GENERATED_BODY | 告诉UE4自动生成的代码注入在这里 |
以下是MyActor.generated.h
中的部分代码:
1 #define HelloUE4_Source_HelloUE4_MyActor_h_9_INCLASS_NO_PURE_DECLS 2 private: 3 static void StaticRegisterNativesAMyActor(); 4 friend HELLOUE4_API class UClass* Z_Construct_UClass_AMyActor(); 5 public: 6 DECLARE_CLASS(AMyActor, AActor, COMPILED_IN_FLAGS(0), 0, TEXT("/Script/HelloUE4"), NO_API) 7 DECLARE_SERIALIZER(AMyActor) 8 /** Indicates whether the class is compiled into the engine */ 9 enum {IsIntrinsic=COMPILED_IN_INTRINSIC};
实际上,GENERATED_BODY这个宏最终展开后就对应了宏HelloUE4_Source_HelloUE4_MyActor_h_9_INCLASS_NO_PURE_DECLS,也就是说自动生成的代码会在C++编译的时候注入到了类AMyActor中。
注意,如果声明变量或类型不加上前缀,是不会生成类型信息的。下面是一些基本的类型标记:
有了反射功能之后,成员变量的序列化也就更方便了。UE4收集了每个类成员的类型信息,这样存档和读档时,根据名称和类型就可以自动完成了,整个过程不需要人工干预。
需要序列化的成员变量,需要在变量声明的时候在前面加上UPROPERTY()宏,宏参数有很多,分别表示变量的详细属性,下面列举一些常用的:
UPROPERTY参数 | 说明 |
EditAnywhere | 表示该属性可从编辑器内的属性窗口编辑。 |
Category | 定义属性的分类。使用方法: Category=CategoryName. (分类=分类名称) |
Const | 编辑器中不能修改该值 |
BlueprintReadOnly | 在蓝图中只读,不可修该。 |
BlueprintReadWrite | 在蓝图中可读写。 |
BlueprintCallable | 仅能用于Multicast代理。该代理可被蓝图调用。 |
与UPROPERTY对应的还有一个用于修饰函数的宏UFUNCTION,该宏常用于描述如何从蓝图中访问C++的函数。
UFUNCTION参数 | 说明 |
BlueprintCallable | 这种类型的函数,只能在C++中实现和重写。可以理解为蓝图“只读”函数。 |
BlueprintImplementableEvent | 只能在蓝图中实现的函数。类似于C++的纯虚函数 |
BuleprintNativeEvent | C++可以提供默认实现,蓝图可以重写。 |
在编辑器模式下,UE4将工程代码编译成动态链接库,这样编辑器可以动态的加载和卸载某个动态链接库。UE4为工程自动生成一个cpp文件(本工程为HelloUE4.generated.cpp),cpp文件包含了当前工程中所有需要反射的类信息,以及类成员列表和每个成员的类型信息。在动态链接库被编辑器加载的时候,自动将类信息注册到编辑器中。反之,卸载的时候,这样类信息也会被反注册。
在开发的过程中,当我们编译完成工程的时候,UE4编辑器会自动检测动态链接库的变化,然后自动热重载这些动态链接库中的类信息。
有了反射机制,UE4也能够知道哪些类型是指针类型,以及哪些变量需要被垃圾收集系统管理。被垃圾收集系统管理的对象,不需要手动调用delete,只需要正确的维持变量的引用即可。我没有看过UE4垃圾收集系统实现源码,姑且将UE4的垃圾收集系统看成是一个使用了“标记-清扫”算法的一个沙盒,当不需要使用某个指针变量的时候,我们只需要把他置为NULL,在下个垃圾回收阶段中,系统会自动回收。
使用垃圾回收的时候,需要遵从一定的规范:
————————————————
版权声明:本文为CSDN博主「游蓝海」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/you_lan_hai/java/article/details/72597958
原文:https://www.cnblogs.com/jzyl/p/13028975.html