7 - AI 入门

本文将初步介绍 UE5 中有关人工智能系统的入门知识,例如 AI 控制器 AIController,黑板 Blackboards 和行为树的概念及简单使用。

AI Controller

PlayerControllerAIController 这两个 Actor 类都继承自 AController 基类,AController 可以用来控制 Pawn 或者 Character 的动作。玩家控制器依赖于实际玩家的输入,而 AI 控制器通过运用人工智能来操控其拥有的角色,并根据设定好的规则对环境作出反应。一个 AI 控制器可由相同 AI Pawn 的多个实例使用,并且相同的 AI 控制器可在不同的 AI Pawn 类中使用。

主动拥有 Pawn

在 C++ 中,可用 Possess() 来拥有一个 Pawn:

void AController::Possess(APawn* InPawn);

可用 UnPossess() 解除 Pawn 的拥有状态:

void AController::UnPossess();

在调用 Possess()UnPossess() 的同时,也会触发 OnPossess()OnUnPossess() 回调。

PawnSensing 组件

该组件赋予被控制的 Pawn 的感知功能,它可以从视觉和听觉两方面感知其他 Pawn 的存在,并作出相应反应。

当 AI 沿着其视线看到 Pawn 类的实例时,会触发 OnSeePawn() 事件;当 AI 检测到由某 Actor 中的 Pawn Noise Emitter 组件发出的特定噪音时,会触发 OnHearNoise() 事件。

导航网格体 NavMesh

UE5 通过导航网格体 NavMesh 告诉人工智能哪些环境是可导航的,而哪些环境是不可导航的。UE5 还支持动态的 NavMesh,它支持动态对象在环境中移动时实时更新 NavMesh,这使得 AI 能够识别环境变化,并适应导航路径。

在引擎中拖入一个 NavMesh,让它和地面相交,然后按下 P 键启用导航 Debug 视图,出现的绿色便是可导航的区域。效果如下:

RecastNavMesh

在创建 NavMesh 的同时,一个名字为 RecastNavMesh-Default 的 Actor 被自动创建。这个 RecastNavMesh 可以被视为 NavMesh 的 “大脑”,它包含了调整 NavMesh 所需的参数,这些参数将直接影响 AI 如何在给定区域中导航。

RecastNavMesh 包含的参数很多,常用到的参数如下:

  • 显示 Display:影响可视化调试 NavMeshBoundsVolume 生成的导航区域,方便 debug。
  • 生成 Generation:决定 NavMesh 如何生成,以及确认哪些区域是可导航的,哪些不是。常用到的参数如下:
    • Cell Size:NavMesh 在给定空间中生成可导航区域的精度;
    • Agent Radius:在此区域导航的 Actor 的半径。
    • Agent Height:在此区域导航的 Actor 的高度。
    • Agent Max Slope:游戏世界中可能存在的倾斜角度。
    • Agent Max Step Height:AI 可以导航到的台阶高度,以楼梯台阶为单位。

GetRandomPointInNavigableRadius

如果想要获取导航网格体内的随机一点,需要使用 GetRandomPointInNavigableRadius() 函数。

行为树和黑板

行为树和黑板协同工作,允许 AI 遵循不同的逻辑路径,并根据各种条件和变量做出决策。

黑板

黑板是定义变量(也被称为键 Key)的地方,方便行为树使用这些变量做决策。

行为树

行为树是一种可视化编程工具,可以根据某些因素和参数告诉 Pawn 该做什么。它由一组对象组成,即 组合器(Composites),任务(Tasks),服务(Services)和装饰器(Decorators),它们共同定义 AI 将如何根据设置的条件和逻辑流程进行行为响应。

组合器 Composites

组合器节点告诉行为树如何执行任务和其他操作,也能附加装饰器和服务,以便在执行行为树分支前应用可选条件。

常见的组合器节点如下:

  • Selector:Selector 节点按从左到右的顺序执行其子任务,并在其中一个子任务成功时停止执行之后的子任务。

    此外,能在节点右上角处发现数字,这是节点的执行顺序,一般是从上到下、从左到右。

  • Sequence:Sequence 节点从左到右顺序执行其子任务,当其中一个子任务失败将停止执行之后的子任务。

  • Simple Parallel:可同时执行任务和新的独立逻辑分支。例如下图,Wait 任务执行的同时 Sequence 也在执行:

    在 “细节” 面板中可以调节该节点的 Finish Mode 参数:

    • Immediate:该节点将在主任务完成后成功完成。例如上图,Wait 任务完成后后面的 Sequence 会自动停止。
    • Delayed:该节点将在主任务和后面的任务完成后才成功完成。

任务 Tasks

这里提供 AI 可以完成的任务,UE5 默认内置了一些任务,包括移动到特定位置,旋转以面对目标,发射武器等。也能通过在蓝图或 C++ 中创建自己的任务。

例如下图中定义了一个任务,从 ReceiveExecuteAI 事件开始,基于 AI 自己 Pawn 的位置随机生成一个新位置,然后在 FinishExecute 结束,并返回是否成功。

装饰器 Decorators

装饰器是可以添加到任务或组合器的条件,通常用于实现分支逻辑。除了使用内置的装饰器外,也能通过蓝图自定义装饰器。

服务 Services

和装饰器比较相似,也能添加到任务或组合器间,但服务允许我们基于它定义的间隔执行节点分支。

要想运行行为树,可在 AIController 中的 BeginPlay 中调用 RunBehaviorTree:

实践

AI 寻路

接下来尝试实践一下,实现 AI 按巡逻点寻路的功能。

首先,新建一个基于 Actor 的蓝图类 BP_AIPoints,用于存储巡逻点。在这个类上新建一个 vector 数组 Points,勾选 “实例可编辑” 和 “显示 3D 控件”。然后新建一个函数 GetNextPoint,用于在 Points 中获取一个巡逻点,这个巡逻点是本地坐标的,因此要转换为世界坐标:

然后将这个蓝图类放到地图上,在细节面板中添加数个巡逻点,然后在地图上摆好:

接下来要让敌人的蓝图类引用这个巡逻点实例,新建一个 PatrolPoints 变量,引用 BP_AIPoints,别忘了勾选实例可编辑。然后在地图上点击敌人,绑定引用变量。接下来实现敌人的行为树:

其中找到巡逻点的函数如下:

最后编译好蓝图就行了。

AI 感知

除了巡逻外,AI 还能够通过视线和听力来感知玩家,这里将实现如下两个功能:

  • AI 对玩家的视觉感知;
  • AI 对玩家发出噪音的听觉感知;

首先要在对应 AI Controller 中添加 Pawn Sensing 组件,用于处理视觉感知和听觉感知。

然后实现相关事件:

  • 敌人感知到玩家的 OnSeePawn() 事件:

    敌人感知到 Pawn 时,如果这个 Pawn 是玩家,就会更新它的玩家角色对象引用黑板变量,方便行为树的后续敌人追捕玩家操作。

  • 敌人听到玩家发出噪声的 OnHearNoise() 事件:

    敌人听到玩家发出的噪声时,如果噪声距离敌人的位置在敌人的听觉范围内,就会更新噪声位置黑板变量,方便行为树的后续敌人前往噪声地点操作。

接下来处理敌人看到玩家的行为树。敌人看到玩家后应该要追捕玩家,追到后对玩家造成伤害,之后原地等待并重置看到的玩家:

其中,Damage Player 节点负责对玩家造成伤害,它的任务 BTTask_DoAttack 的实现如下:

Reset Player Seen 节点负责清除敌人看到的玩家这一对象引用黑板变量:

最后处理敌人听到玩家噪声的行为树。敌人听到玩家发出的噪声后,如果敌人处在噪声范围内,就会前往噪声位置调查,等待后重置听到玩家的状态:

当然,别忘了给玩家 Character 添加发出噪声的 Pawn Noise Emitter 组件:

然后在该发出噪声的逻辑中调用该组件的 Make Noise 函数,例如下图在玩家按下鼠标左键开火时发出噪声:

运行时生成 AI Actor

可以使用 SpawnAIFromClass() 函数在运行时生成 AI Actor,需要指定生成的 Pawn,使用的行为树 Behavior Tree,变换等:

参考资料

  • 中文翻译:《UE5 C++ 游戏开发完全学习教程》

  • 英文原版:《Elevating Game Experiences with UE5》