因为刚刚接触AI模块,采用英语反而理解的不好,故先使用汉语引脚等【】命名还是用的英语。

文档参考:虚幻引擎中的行为树 – 快速入门指南 |虚幻引擎 5.2 文档 |Epic 开发者社区 — Behavior Tree in Unreal Engine – Quick Start Guide | Unreal Engine 5.2 Documentation | Epic Developer Community

不得不说,初次接触任意模块时,官方文档是最好的入口。这里专门整理相关介绍以及实现方法。

基础

AI Controller

PlayerController 依赖于人类玩家来决定要做什么,而 AIController 更侧重于响应来自环境和游戏世界的输入。AIController 的工作是观察周围的世界并做出决策并做出相应的反应,而无需人类玩家的明确输入。


黑板BlackBoard

虚幻引擎中的行为树资产可用于为项目中的非玩家角色创建人工智能(AI)。行为树资产用于执行包含逻辑的分支,为了确定应执行哪些分支,行为树依赖于另一个称为黑板的资产,该资产充当行为树的“大脑”。Blackboard 资产,它本质上是 AI 的大脑。我们希望 AI 知道的任何内容都会有一个我们可以参考的黑板键 。我们将创建用于跟踪玩家的键,无论 AI 是否与玩家有视线,以及 AI 在不追逐玩家时可以移动的位置。

黑板由两个面板组成: 黑板(Blackboard)和黑板详细信息(Blackboard Details),用于添加和跟踪黑板键 (要监控的变量),黑板详细信息(Blackboard Details)用于命名和指定键的类型。

黑板包含多个用户定义的键 ,这些键保存行为树用于做出决策的信息。例如,你可以有一个名为 Is Light 的布尔键,行为树可以引用该键来查看值是否已更改。如果该值为 true,则可以执行导致蟑螂逃跑的分支。如果该值为 false,则可以执行不同的分支,其中蟑螂可能会在环境中随机移动。行为树可以像模拟多人游戏中的另一个人类玩家一样复杂,该玩家会寻找掩体、向玩家射击并寻找拾取物品。


行为树Behavior Trees

行为树由三个面板组成:  行为树图表(Behavior Tree)图表,你可以直观地布局定义行为的分支和 节点,细节 (Details) 面板,可以定义节点的属性,以及黑板(Blackboard),在游戏运行时显示黑板键  及其当前值,对调试很有用。

虚幻引擎中的行为树从左到右、从上到下执行其逻辑。可以在图表中放置的节点的右上角查看作的数字顺序。

虚幻引擎行为树的差异

行为树是事件驱动的

虚幻引擎行为树是事件驱动的,以避免每帧执行不必要的工作。行为树不是不断检查是否发生了任何相关更改,而是被动侦听可用于触发树中更改的“事件”。在下面的例子中,事件用于更新 Blackboard Key HasLineOfSight? 。 这会导致任何优先级较低的任务被中止,转而执行优先级较高的最左侧分支。

拥有事件驱动的架构可以提高性能和调试。但是,要充分利用这些改进,你需要了解虚幻引擎行为树的其他区别,并适当地构建你的行为树。由于代码不必每次更新都迭代整个树,因此性能要好得多。从概念上讲,我们不必不断地问“我们到了吗?”,我们可以休息,直到我们被刺激并被告知,“我们到了!

在行为树的执行历史记录中向前和向后移动以直观地调试行为时,最好让历史记录显示相关更改,而不是显示不相关的更改。在虚幻引擎中的事件驱动实现中,没有必要过滤掉在树上迭代的不相关步骤,并选择与以前相同的行为,因为从一开始就不必发生额外的迭代。相反,只有对树或黑板值中的执行位置的更改才重要,并且显示的是这些差异。

条件不是叶节点

在行为树的标准模型中,条件是任务叶节点,除了成功或失败之外,它不执行任何作。尽管没有什么可以阻止您执行传统的条件任务,但强烈建议您改用装饰器来处理条件。


创建AIController蓝图类

选择 角色移动(Character Movement) 组件,然后 将 细节(Details) 面板中的 最大行走速度(Max Walk Speed) 设置为 120.0。这会降低我们的 AI 角色在巡逻而不是追逐玩家时在环境中移动的速度。

复合Composites 

流控制的一种形式,可确定连接到它们的子分支的执行方式。

Selector  选择器

从左到右执行分支,通常用于在子树之间进行选择。当选择器找到成功执行的子树时,它们会停止在子树之间移动。例如,如果 AI 成功追逐玩家,它将停留在该分支中,直到其执行完成,然后转到选择器的父复合以继续决策流。

Sequence  序列

从左到右执行分支,更常用于按顺序执行一系列子项。与选择器不同,序列会继续执行其子项,直到到达失败的节点。例如,如果我们有一个序列要移动到玩家,请检查它们是否在范围内,然后旋转并攻击。如果检查它们是否在范围内部分失败,则不会执行旋转和攻击作

Simple Parallel  简单并行

简单并行有两个“连接”。第一个是主任务,只能为其分配一个任务节点(意味着没有复合节点)。第二个连接(后台分支)是应该在主任务仍在运行时执行的活动。根据属性,简单并行可能会在主任务完成后立即完成,或者等待后台分支完成。

这表示作顺序。 行为树从左到右和自上而下执行,因此节点的排列很重要。AI 最重要的作通常应放置在左侧,而不太重要的作(或回退行为)应放置在右侧。 子分支以相同的方式执行,如果任何子分支失败,整个分支将停止执行并失败返回树。 例如, 如果 Chase Player 失败,它将返回 AI Root,然后再进入 Patrol

除了使用内置任务外,您还可以创建和分配自定义任务

这些任务具有可以自定义和定义的其他逻辑。此任务将用于更改 AI 的移动速度,使其跟在玩家之后运行。创建新任务时,将自动创建并打开新的蓝图 

最好在创建任何新创建的任务  装饰器服务TasksDecorators or Services )时立即重命名它们。正确的命名约定是在资产名称前加上你创建的资产类型,such as BTT for Behavior Tree Tasks, BTD for Behavior Tree Decorators, or BTS for Behavior Tree Services. 。 

我们的行为树的框架已经完成。在下一步中,我们将添加更改 AI 移动速度的逻辑,在 AI 巡逻时找到一个随机位置进行导航,以及确定 AI 何时应该追逐玩家或巡逻的逻辑。

任务设置 – 追逐玩家

在 BTT_ChasePlayer 中,添加  事件接收执行 AI 节点 【Event Receive Execute AI 】

如果代理是 AI 控制器,则应始终选择 事件接收执行(Event Receive Execute)、 事件接收中止(Event Receive Abort) 和 事件 接收更新(Event Receive Tick ) 的 AI 版本。如果同时实现通用和 AI 事件版本,则只会调用更合适的版本,这意味着 AI 版本用于 AI,否则调用通用版本。

在这里,我们使用 Cast 节点访问名为 BP_Enemy01 的 AI 的角色蓝图 

在 内容侧滑菜单(Content Drawer) 中,打开 BP_Enemy01 蓝图,并添加一个名为 更新行走速度(Update Walk Speed) 的  函数 。 此函数将从我们的行为树中调用,并将用于更改 AI 的移动速度。

从技术上讲,我们可以在追逐玩家 任务 中的 Cast 节点上访问角色移动  组件,并从任务中 调整移动速度,但让行为树直接 更改子对象的属性并不是推荐的最佳实践。相反,我们将让行为树在角色中调用一个函数,然后该函数将进行我们需要的修改。

在 更新行走速度(Update Walk Speed ) 函数的 细节(Details)  面板中,添加一个 名为 NewWalkSpeed 的  浮点(Float)  输入。 

点击并拖出 角色移动(Character Movement) 的引脚,然后从动作菜单中输入 设置最大行走速度(Set Max Walk Speed),然后从菜单中点击 设置最大行走速度(Set Max Walk Speed)。 

当我们从行为树中调用此函数时,我们可以传递一个值以用作新速度。

回到 BTT_ChasePlayer  任务中,从 As Enemy Character 节点 调用 Update Walk Speed  设置为 500.0 。在 Update Walk Speed 之后,添加两个 Finish Execute 节点并连接,如下所示。右键点击 新建行走速度(New Walk Speed) 引脚,然后将其提升为变量并将其命名为 ChaseSpeed。对于 ChaseSpeed,请确保启用 实例可编辑(Instance Editable)。

通过将其提升为 实例可编辑(Instance Editable) 变量,可以从此蓝图外部设置 最大行走速度(Max Walk Speed) 的值,并将作为行为树中的属性提供。 

在这里,当我们成功转换为 Enemy_Character 时,我们将任务标记为成功完成。如果受控的 Pawn 未 Enemy_Character,我们需要处理这种情况,因此我们将任务标记为不成功,这将中止任务。


任务设置 – 查找随机巡逻

在 BTT_FindRandomPatrol 中,使用 事件接收(Event Receive) 执行 AI 和 投射(Cast) 来 Enemy_Character 并连接它们。

调用 更新行走速度(Update Walk Speed) 并将 新行走速度(New Walk Speed) 提升为名为 巡逻速度(Patrol Speed) 的变量

Variable Name to PatrolSpeed
– PatrolSpeed 的变量名称
– Instance Editable to Enabled
– 实例可编辑已启用
– Patrol Speed (Default Value) to 125.0
– 巡逻速度(默认值) 为 125.0

将 GetRandomReachablePointInRadius 上的 半径(Radius) 提升为具有以下设置的变量:

– Variable Name to PatrolRadius
– PatrolRadius 的变量名称
– Instance Editable to Enabled
– 实例可编辑已启用
– Patrol Radius (Default Value) to 1000.0
– 巡逻半径(默认值) 为 1000.0

在 受控 Pawn(Controlled Pawn) , 获取 Actor 位置(Get Actor Location), 然后 GetRandomReachablePointInRadius,并将返回值连接到分支。

使用分支节点来处理找不到要移动到的随机点的边缘情况:

在 随机位置(Random Location) 引脚上,使用 将黑板值设置为向量(Set Blackboard Value as Vector), 并将 键(Key) 提升为名为 PatrolLocation 的变量。使用另一个 将黑板值设置为矢量(Set Blackboard Value as Vector) 节点,其 值(Value) 来自 获取 Actor 位置(Get Actor Location) 。使用两个节点进行连接,导致 完成执行(Finish Execute) 标记为 成功(Success)。在 Cast 节点的 Cast Failed 引脚上,使用 Finish Execute with Success 禁用。如果受控 Pawn 未 Enemy_Character,则此任务将被标记为不成功并中止。

如果敌人找到一个随机移动的位置,它将作为移动到的位置存储在黑板中。如果找不到某个位置,它将使用其当前位置并保持原状,然后再尝试新位置。我们仍然需要处理 Controlled Pawn 不 Enemy_Character 的边缘情况。

AI 控制器设置

添加 附身事件(Event On Possess ) 节点,添加 运行行为树(Run Behavior Tree)  节点, 并将 BTAsset 设置为 BT_Enemy。在 组件(Components) 窗口中,点击 + 添加(Add) 并搜索并添加 AIPerception 组件 

AI 感知组件AI Perception 用于在 AI 感知系统中创建一个刺激监听器,并收集注册的刺激(在我们的例子中,我们可以使用 Sight),你可以做出响应。这将使我们能够确定 AI 何时真正看到玩家并做出相应的反应。

在 AIPerception 组件的 细节(Details) 面板中,添加 AI视力配置 (AI Sight config)  并 启用 检测中立方(Detect Neutrals) 。

通过“ 按隶属关系检测” 属性,你可以设置基于团队的 AI,与同一隶属关系的队友并肩作战,并攻击对方隶属关系的成员。  默认情况下,Actor 不会被分配隶属关系,并被视为中立。目前,你无法通过蓝图分配隶属关系,因此我们启用了 检测中立(Detect Neutral) 标志来检测玩家。作为替代方案,我们将使用 Actor 标记来确定哪个角色是玩家,并强制 AI 角色仅追逐标记为 玩家的 Actor。

在 AIPerception 的 “事件” 部分中,单击“ 目标感知已更新” 旁边的 + 号。在 目标感知更新(On Target Perception Updated) ,添加一个 Actor Has Tag  节点,并将  标签(Tag)  设置为  玩家(Player)。  

在 刺激(Stimulus ) 引脚上,添加 Break AIStimulus 节点。添加一个 分支(Branch ) 节点

在这里,我们将检查 Actor 是否被成功感应,以及该 Actor 是否具有玩家的标签。

分支的 False  处,使用 Set Timer by Event 并将  Time  设置为 4.0。 

右键点击 时间(Time ) 并将其提升为变量 ,并将其 命名为  视线计时器(Line Of Sight Timer)。 

此变量和分配的值将决定 AI 在放弃追逐玩家之前多长时间,在何时,附加的事件将执行。

这通过 Handle 存储对 Timer 的引用。可以通过脚本调用此句柄,以使自身失效并清除任何关联的事件(阻止执行关联的事件)。我们可以稍后在 AI 在视线计时器用完之前再次 看到玩家时使用它,这可以阻止 AI 失去对玩家的视线并放弃追逐。 

创建自定义事件  并将其命名为 StartEnemyTimer ,并将其连接到 按事件设置计时器(Set Timer by Event) 的  事件(Event ) 引脚。 

右键点击,然后在 变量 > AI 下, 添加 获取黑板(Get Blackboard ) 节点。

在 Blackboard 之外,使用 将值设置为布尔值(Set Value as Bool ) 和  将值设置为对象(Set Value as Object ) 并 如下所示进行连接。 右键点击,将两个关键点名称  分别提升为 HasLineOfSight  和 EnemyActor  的  变量 。 编译  蓝图 ,并将 两个  关键点名称  的 默认值(Default Values ) 分别设置为 HasLineOfSight  和 EnemyActor 。 

分支的 true  之外,使用 Get EnemyTimer ,然后 使用 Clear and Invalidate Timer by Handle。 当 AI 看到玩家时,它会清除视线计时器,直到它再次失去玩家的视线(新的视线计时器将启动)。复制并粘贴 黑板(Blackboard ) 节点、  值设置为(Set Value as) 和  键名称(Key Name ) 节点,如图所示。 

在 将值设置为布尔(Set Value as Bool ) 节点上,启用  布尔值(Bool Value ) 并将 Actor(Actor ) 引脚拖到  对象值 ( Object Value) 上,如图所示。 

这会将 Has Line Of Sight 的 Blackboard 关键值设置为 True ,将 EnemyActor 设置为我们感知到的 Actor(我们已将其设置为仅在玩家时触发)。 

最终图表应类似于上述。

装饰器Decorator和最终设置

调整玩家角色和敌方角色蓝图上的一些设置。我们还在行为树中 设置了装饰器 ,它将根据指定条件确定我们可以输入哪个分支。 

打开  第三人称角色(ThirdPersonCharacter)  蓝图,在 细节(Details)  面板中,搜索 标签(Tag)  并将其设置为  玩家(Player) 添加 。通过设置添加此玩家标签,AI 现在可以感知玩家并做出反应。

添加这个标签后,AI将自动在看到玩家后进行追逐。

由于AIController里面写的是追逐Actor has tag[player]。所以AI将追随任何带有Player标签的Actor:

例如 我们在另一个AI身上打上Player标签,去掉玩家的标签,AI会追那个带有标签的AI,而无视玩家。

Enemy_Character 蓝图,在 细节(Details)  面板中,搜索 旋转 (Rotation)  并启用  使用控制器旋转偏航(Use Controller Rotation Yaw) 。 AI 将在从行为中调用 旋转到面 BB 条目(Rotate to Face BB Entry ) 时正确旋转。

打开 BT_Enemy  并右键点击  追逐玩家(Chase Player),然后在 添加装饰器(Add Decorator..) 下 ,选择  黑板(Blackboard)。 

右键点击行为树中的节点时,可以添加提供附加功能的子节点: 

Decorator  
装饰
也称为条件句。这些附加到另一个节点,并决定是否可以执行树中的分支,甚至单个节点。
Service  
服务
这些节点附加到任务  节点和  复合节点,只要它们的分支正在执行,它们就会以定义的频率执行。这些通常用于进行检查和更新黑板 。这些节点取代了其他行为树系统中的传统并行节点。

我们将使用 黑板装饰器(Blackboard Decorator ) 来确定黑板键的 值,该键在有效时将允许此分支执行。

选择添加的 基于黑板的条件(Blackboard Based Condition), 并在 细节(Details) 面板中设置以下设置。 

[UE4/UE5]GamePlayTag 标签 (一) – 知乎

‘键+小键盘4显示AI视力射线

‘键+小键盘3显示AI视力范围

分类: 未分类