当前位置:首页 > 动态 > UE4开发入门   > UE4入门学习4:C++编程介绍
内容详情
UE4入门学习4:C++编程介绍
来源:      发布时间:2018-05-08 01:24      浏览:2385     字体:    

       UE4直接使用C++作为逻辑层语言,这样引擎层与逻辑层语言统一,不需要胶水代码去转发,消除了逻辑层和引擎层的交互成本。为了便于开发,UE4对C++做了一些包装,比如反射和垃圾回收,大大减轻C++开发的难度。本文结合UE4官方C++编程指南文档,对C++相关特性做一些描述和总结。


反射

       C++本来是不支持反射的,只有一个基本的RTTI(运行时类型信息)特性,仅能在运行时获取对象的类型信息,无法得到成员变量和函数列表信息。如果我们要对成员变量进行序列化(存档/读档),需要自己写很多辅助的读取和写入方法,非常麻烦。

       UE4在C++编译开始前,使用工具UnrealHeaderTool,对C++代码进行预处理,收集出类型和成员等信息,并自动生成相关序列化代码。然后再调用真正的C++编译器,将自动生成的代码与原始代码一并进行编译,生成最终的可执行文件。这个过程类似于Qt的qmake预处理机制。

       拿出我们之前工程的代码来分析:



// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class HELLOUE4_API AMyActor : public AActor
{
    GENERATED_BODY()
public: 
    // 默认构造函数,初始化一些成员属性。
    AMyActor();
protected:
    // 游戏开始或者被创建出来后调用。
    virtual void BeginPlay() override;
public: 
    // 每帧都会被调用
    virtual void Tick(float DeltaTime) override;

    // 这个变量会出现在编辑器编辑界面。
    UPROPERTY(EditAnywhere)
    int32   MyID;

private:
    // 这是个内部变量。不会出现在编辑器界面。
    float RunningTime;
};



名称 描述
MyActor.generated.h 这个文件是UE4自动生成的,里面存贮了UE4收集的类型信息。
UCLASS 告诉UE4这个类需要收集类型信息
UPROPERTY 告诉UE4这个成员变量的信息需要被收集
GENERATED_BODY 告诉UE4自动生成的代码注入在这里

以下是MyActor.generated.h中的部分代码:


#define HelloUE4_Source_HelloUE4_MyActor_h_9_INCLASS_NO_PURE_DECLS \
private: \
static void StaticRegisterNativesAMyActor(); \
friend HELLOUE4_API class UClass* Z_Construct_UClass_AMyActor(); \
public: \
DECLARE_CLASS(AMyActor, AActor, COMPILED_IN_FLAGS(0), 0, TEXT("/Script/HelloUE4"), NO_API) \
DECLARE_SERIALIZER(AMyActor) \
/** Indicates whether the class is compiled into the engine */ \
enum {IsIntrinsic=COMPILED_IN_INTRINSIC};


       实际上,GENERATED_BODY这个宏最终展开后就对应了宏HelloUE4_Source_HelloUE4_MyActor_h_9_INCLASS_NO_PURE_DECLS,也就是说自动生成的代码会在C++编译的时候注入到了类AMyActor中。

注意,如果声明变量或类型不加上前缀,是不会生成类型信息的。下面是一些基本的类型标记:

1、UCLASS() - 告诉UE4生成类的反射数据。类必须派生自 UObject。

2、USTRUCT() - 告诉UE4生成结构体的反射数据。

3、UENUM() - 告诉UE4生成枚举的反射数据。

4、GENERATED_BODY() - UE4 使用它替代为类型生成的所有必需样板文件代码。

5、UPROPERTY() - 使 UCLASS 或 USTRUCT 的成员变量可用作 UPROPERTY。UPROPERTY 用途广泛。它允许变量被复制、被序列化,并可从蓝图中进行访问。垃圾回收器还使用它们来追踪对 UObject 的引用数。

6、UFUNCTION() - 使 UCLASS 或 USTRUCT 的类方法可用作 UFUNCTION。UFUNCTION 允许类方法从蓝图中被调用,并在其他资源中用作 RPC。


序列化

       有了反射功能之后,成员变量的序列化也就更方便了。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,在下个垃圾回收阶段中,系统会自动回收。

使用垃圾回收的时候,需要遵从一定的规范:

1、所有需要被托管的成员变量需要用宏UPROPERTY()标记

2、所有从UObject派生的类,才能被系统托管。非UObject派生的类可以考虑从类FGCObject派生,并实现AddReferencedObjects方法,或者使用智能指针

3、数组TArrayUObject类型指针元素,可以被自动托管

4、Actor类型对象在不用的时候,需要手动调用Destroy。调用Destroy后不会立即消耗,也会等待下个垃圾回收阶段


编码规范 类名前缀

UE4的类名必须遵从命名规范,需要在类名前面加上正确的前缀,与之对应的C++文件名则不加前缀。否则会编译报错。

1、派生自 Actor 的类前缀为 A,如 AController

2、派生自 UObject 的类前缀为 U,如 UComponent

3、枚举 的前缀为 E,如 EFortificationType

4、接口 类的前缀通常为 I,如 IAbilitySystemInterface

5、模板 类的前缀为 T,如 TArray

6、派生自 SWidget(Slate UI)的类前缀为 S,如 SButton

7、其余类的前缀均为 字母 F ,如 FVector


其他类型命名

bool 类型变量需要加上b前缀,如 bCallable




Object
    Object类是UE中所有其它类的基类。它不能被添加到游戏中(不能生成或放到关卡中),但它可以包含数据和函数。如果你的关卡中一无所有那就用Object吧,因为它不会产生绘制调用(Draw Call)。目前还无法在Blueprint中使用Object。
小提示:Blueprint相当于Unity中的预制件(Prefab)。


Actor
    Actor可以在关卡中生成。它也可以包含一些组件如用于角色图形表现的Static Mesh Component。要先在关卡中放置Actor才可以访问该类。Actor会产生绘制调用,所以如果你的类里只包含数据最好不要继承Actor。


Pawn
    简单来说Pawn就是Actor的一个子类,但它可以被PlayerController或AIController操作。举个例子,Pawn可以是一只小狗,它可以被玩家操作,也可以被RTS(即时战略)游戏的AI角色操作。


Character
    Character是Pawn的子类,只是加入了MovementComponent组件以便进行导航和移动,同时还新增了SkeletalMeshComponent用于指定角色的图形显示。
    现在新建一个Character并改名为GamePlayCharacter。
    小提示:MovementComponent组件在移动平台开销较大,如果你的游戏包含大量角色,更好的做法是自己扩展Pawn来实现移动。


PlayerController
    PlayerController最重要的作用就是获取玩家输入(鼠标、键盘、触摸板等等),它可以控制Pawn和Character。可以针对不同的移动功能指定不同的PlayerController。例如,控制汽车移动可以使用CarPlayerController,用另一个不同的PlayerController来控制飞机。类似的还有分别控制主菜单和游戏内容的PlayerController。
    新建一个PlayerController并改名为GamePlayPlayerController。


AIController
    与PlayerController类似,AIController可用于控制Pawn和Character,但它不能获取玩家输入。它可以访问所有AI工具,如行为树,SensingComponent等等。
    小提示:Pawn与Controller的区别在于,Pawn基本上是指角色的图形显示(网格、动画等),而Controller相当于Pawn的大脑,告诉Pawn何去何从等。


GameInstance
    GameInstance是用于存储全局数据的最佳选择。该类会在游戏开始时生成并于游戏结束时销毁。而其它类通常在关卡加载时生成,关卡切换或销毁时被删除。所有Blueprint均可访问GameInstance。
    新建一个GameInstance并改名为ShooterGameInstance。
    依次点击菜单项Edit > Project Seetings > Maps&Modes,添加GameInstance并改名为ShooterGameInstance。
    现在运行游戏就会生成ShooterGameInstance,并且可以从其它类中访问到它。


GameMode
    GameMode的重要之处在于只有服务器可以访问它。在单人游戏中玩家就是服务器,所以不需使用服务器-客户端的架构,但还是有必要了解一下。其中包含一些事件,会在其它玩家与你的关卡连接或失联时触发。通常GameMode保存了默认关卡的信息。
    UE中的每个关卡都要关联一个或多个GameMode。
    GameMode保存了默认的Pawn、PlayerController、HUDClass以及GameState的数据。
    这些类将在关卡加载时生成,大多情况下GameMode用于决定关卡赢家,或是否成功通过关卡。简单来说GameMode决定并管理关卡规则。
    新建GameMode类并改名为GamePlayGameMode。创建完成后将其赋给关卡。


GameState
    GameState一般用于跟踪关卡(游戏)进度,所有人都可以访问到它。举个例子,在回合制游戏中玩家想知道当前轮到谁,或者在射击游戏中当前目标是什么。
    新建GameState改名为GamePlayGameState。将新建的GamePlayGameState赋给之前创建的GamePlayGameMode。


HUD
    HUD主要用于创建2D UI,但UE4中使用UMG更好。但还是可以用HUD来保存一些UMG Widgets的引用,因为所有的类都可以访问到HUD。
    新建HUD类改名为GamePlayHUD。将新建的HUD赋给之前创建的GameMode,同样还有之前创建的PlayerController和Character。
    UE4真的是一个非常庞大的引擎,学习过程应该循序渐进。了解这些类的基本概念之后别急着实现,先思考这些功能应该用在哪里。最后解释一下UE4游戏运行过程,也算是对以上内容的一个总结:
    1. 游戏开始运行时会生成GameInstance类;
    2. 加载默认关卡;
    3. 关卡开启后检查GameMode,并生成GameMode包含的所有默认类;
    4. 自动检测DefaultPlayerController并对DefaultPawn进行设置,这样就不用手动设置角色姿势;
    5. 生成关卡中包含的所有Actor。
    打开其它关卡或重新打开当前关卡后除了GameInstance之外所有的类都会被销毁。

途联微信号