行为树的工作原理

最简单的理解行为树在我们AI代理中的角色的方法是将其想象成一个大脑。它做出决策,并因此采取行动。它是代理中人工智能的处理器。在我们开始之前,如果你在其他上下文中有过行为树的经验,重要的是要理解它们在虚幻引擎的上下文中是不同的。

如果你想了解更多关于它们的不同之处,可以通过访问以下链接来完成:https://docs.unrealengine.com/en-US/Engine/AI/BehaviorTrees/HowUE4BehaviorTreesDiffer.

然而,这里有一个关键差异需要强调:虚幻引擎中的行为树是从上到下读取的,节点将从左到右执行。在其他上下文中,你可能会发现顺序是相反的,即树从左到右读取,节点从顶部到底部执行。

如果你是第一次遇到行为树,那么当你阅读下一部分时,这会更有意义。

数学树的结构

好了,现在是理解行为树工作原理的时候了。首先,顾名思义,它是一棵树,从数学的角度来说。

如果你对图论中的树感兴趣,可以查阅以下维基百科页面:https://en.wikipedia.org/wiki/Tree_(graph_theory)。或者,如果你想了解更多技术性的内容,可以查阅以下页面:http://mathworld.wolfram.com/Tree.html。然而,这两个链接中的定义都非常数学化,你不需要它们来理解行为树。

重要的是要明确指出,(数学上的)树表达了节点之间的关系。在这个意义上,描述家庭关系(例如父母、子女、兄弟姐妹)的相同关系已经被技术术语所采用。为了简化对树的理解,你可以想象你的家谱树:每个节点是一个人,而连接这些人的分支就是人们之间的关系。然而,结构仍然略有不同。

那么,什么是树呢?它是一种描述不同节点之间关系的图。特别是,有一个“根”节点,它是唯一一个没有父母的节点。从这里开始,每个节点可以有一个或多个孩子,但只有一个父母。终端节点,即没有孩子的节点,被称为叶子。这里有一个简单的图,帮助你理解数学树的基本结构:

听起来可能很复杂,但实际上并非如此。当我们开始讨论行为树时,事情会变得越来越有趣。

行为树组件

如果你查阅官方文档,你会发现有五种类型的节点(任务、装饰器、服务、复合)可供使用,具体取决于你想创建的行为类型(以及AI在世界上应该如何行动)。然而,我想重新阐述这些内容,以便于理解并更具实用性。

除了根节点外,唯一的非叶子节点是复合节点。叶子节点被称为任务装饰器服务是复合节点或任务叶子的附加组件。尽管Unreal允许你将复合节点作为叶子节点,但你不应该这样做,因为这意味着你可以删除该节点,而行为树仍然会以相同的方式工作。以下是一个展示所有不同类型节点的树的示例(实际上,我们会在这本书的后部分构建这个行为树):

当一棵树在执行时,你需要从根节点开始,沿着树向下,从左到右阅读节点。你需要以特定的方式遍历所有不同的分支(复合节点),直到我们到达一个叶节点,即一个任务。在这种情况下,AI会执行那个任务。需要注意的是,任务可能会失败,例如,如果AI无法完成它。任务可能会失败的事实对于理解复合节点的工作方式非常重要。毕竟,一个决策过程就是选择执行哪个任务以更好地实现目标(例如,杀死玩家)。因此,根据哪个任务未能执行(或者,正如我们将看到的,装饰器可以使任务或整个分支失败),复合节点将确定树中的下一个任务。

此外,当你创建你的行为树时,每个节点都可以被选择,并且在详情面板中可以找到一些调整节点/叶节点行为的设置。此外,由于顺序很重要,行为树中的节点有数字(在右上角)以帮助你理解节点的顺序(尽管它总是从上到下,从左到右)。以下截图显示了你可以找到这些数字的位置:

值 "-1" 表示该节点将永远不会以任何顺序执行,且节点周围的颜色会比一点暗。这可能是由于该节点未以某种方式连接到根节点,因此它是孤立的:

让我们详细看看这些组件,特别关注复合节点。

根节点

关于根节点没有太多要说的。树需要从某处开始,所以根节点就是树开始执行的地方。它的外观如下:

请注意,根节点只能有一个子节点,且这个子节点必须是复合节点。你不能将装饰器或服务附加到根节点。如果你选择根节点,它没有任何属性,但你将能够分配一个黑板(我们将在本章后面介绍),如下面的屏幕截图所示:

任务

当我们想到一棵树时,我们通常会想象一个大树干和树枝,而这些树枝上长有叶子。在 UE4 的上下文中,这些“叶子”就是我们所说的“任务”。这些是执行各种操作(如移动 AI)的节点,并且可以附加装饰器或服务节点。然而,它们没有输出,这意味着它们不参与决策过程本身,这完全留给复合节点。相反,它们定义了如果需要执行该任务,AI 应该做什么。

请注意,任务可以根据您的需求变得非常复杂。它们可以从等待一段时间这样简单的事情,到在射击玩家的同时解决拼图这样复杂的事情。大型任务很难调试和维护,而小型任务可能会使行为树变得过于拥挤和庞大。作为一名优秀的 AI 设计师,您应该尝试在任务大小之间找到平衡,并以可以在树的不同部分(甚至在其他树中)重复使用的方式编写它们。

任务可以失败(报告失败)或成功(报告成功),并且在报告这两种结果之一之前,它不会停止执行。复合节点负责处理这个结果并决定下一步要做什么。因此,一个任务可能需要几个帧来执行,但只有在报告失败或成功时才会结束。在您转向第 6 章“扩展行为树”时,请记住这一点,您将创建自己的任务。

任务可以有参数(一旦选择一个任务,您就可以在详细信息面板中设置这些参数),通常,它们是硬编码值或黑板键引用(本章后面将详细介绍黑板)。

在行为树编辑器中,任务呈现为紫色框。在以下截图中,您可以看到一些任务示例以及它们在编辑器中的外观:

Unreal 包含一些内置的任务,这些任务已经准备好可以使用。它们是通用的,涵盖了你可能会需要的基本用例。显然,它们不能特定于你的游戏,因此你需要创建自己的任务(我们将在第6章,扩展行为树中介绍这一点)。 以下是 Unreal 中的内置任务列表:

  • 以结果完成:强制任务立即返回一个完成结果(失败或成功)。

  • 制造噪音:产生一个噪声刺激,由感知系统使用(这将在第5章“代理意识”中探讨)。

  • 直接朝...移动:类似于下一个节点,但忽略导航系统。

  • 移动到:将棋子(通过使用导航系统移动,我们将在第3章“导航”中探讨)移动到黑板指定的位置(我们将在本章后面探讨黑板)。

  • 播放动画:如其名称所示,此节点播放动画。然而,排除例外情况(这也是此节点存在的原因),将动画逻辑和行为逻辑分开是良好的实践。因此,尽量不要使用此节点,而是改进您的动画蓝图。

  • 播放声音:如其名称所示,此节点播放声音。

  • 执行 Pawn Action:执行一个 Pawn Action(不幸的是,本书不会介绍它们)。

  • 旋转以面对 BBEntry:将 AI pawn 旋转到面向黑板内记住的特定键(我们将在本章后面查看什么是黑板)。

  • 运行行为:整体运行另一个行为树作为子树。因此,可以嵌套行为树来创建和组合非常复杂的行为。

  • 运行动态行为:像前一个节点一样,但可以在运行时更改要执行的哪个(子)行为树。

  • 运行 EQS 查询:执行一个 EQS 查询(我们将在第 4 章《环境查询系统》中看到它们),并将结果存储在黑板上。

  • 设置标签冷却时间:通过使用标签为特定的冷却节点(这将是本章后面要查看的装饰器)设置计时器。

  • 等待:停止行为一段时间。可以指定随机偏差,使等待的时间每次不同。

  • 等待黑板时间:像前一个节点一样,但时间是从黑板获取的(更多关于黑板的信息将在本章后面)。

现在我们已经了解了 Task 节点的运作方式,让我们探索 Composite 节点,它们根据 Task 返回失败还是成功来做出决策。

Composite

Composite 节点是 Unreal 中行为树决策能力的核心,理解它们的工作原理是关键。

有三种类型的 Composite 节点:Selector(选择器)、Sequence(序列)和 Simple Parallel(简单并行)。最后一种最近才添加,您会发现通过组合使用选择器和序列,您将能够覆盖大部分情况。以下是它们的工作原理:

选择器:这种类型的节点会尝试找到一个其子节点来执行,这意味着它会试图找到一个分支(即作为子节点的另一个复合节点)或一个任务(即另一个子节点,但它是叶子节点)来执行。因此,选择器从最左边的子节点开始尝试执行。如果失败(要么任务未能执行,要么整个分支失败),则尝试第二个最左边的,依此类推。如果其中一个子节点返回成功,这意味着任务已经完成或整个分支已经完成,那么选择器会向其父节点报告成功,并停止执行其他子节点。另一方面,如果选择器的所有子节点都报告失败,那么选择器也会向其父节点报告失败。在下面的屏幕截图中,你可以看到选择器节点的外观:

序列:这种类型的节点的工作方式有点像选择器的相反。要向父节点报告成功,序列的所有子节点都必须报告成功。这意味着序列将从最左边的子节点开始执行。如果成功,它将继续执行下一个最左边的,依此类推。如果所有子节点直到最右边的都成功,那么序列会向其父节点报告成功。否则,如果有一个子节点失败,那么序列将停止执行其子节点,并向父节点报告失败。在下面的屏幕截图中,你可以看到序列节点的外观:

简单并行:这是一种特殊的复合节点,用于特定情况。实际上,它只能有两个子节点。最左边的子节点必须是一个任务,而最右边的子节点可以是任务或复合节点(从而生成一个子树)。简单并行开始并行执行其两个子节点,尽管最左边的被认为是主要的。如果主要的失败,它会报告失败,但如果主要的成功,那么它会报告成功。根据其设置,简单并行在完成执行主要任务后,可以选择等待子树的执行结束,或者直接报告成功或失败给其父节点并停止执行子树。在下面的屏幕截图中,你可以看到简单并行节点的外观。请注意,只能拖动两个子节点,其中最左边的必须是任务(紫色块是可拖动区域):

通过这种方式,Composite 节点可以根据其子节点的报告(失败或成功)来决定执行哪些任务,并将结果(失败或成功)报告给其父节点。即使是根节点的唯一子节点(也是一个 Composite 节点)向根节点报告成功,那么这个树就已经成功执行了。一个好的行为树设计应该总是允许成功。

装饰器

装饰器节点(也称为条件节点)可以附加到 Composite 或 Task 节点上。装饰器节点对行为树中的分支,甚至是单个节点,是否能被执行做出决策。本质上,它们是一个条件;它们检查是否应该发生某事。换句话说,装饰器可以检查是否值得继续在那个分支上,并可以根据某个条件,确定任务(或子树)肯定会失败,从而报告一个预防性失败。这将避免装饰器尝试执行一个不可能完成的任务(或子树)(无论原因:信息不足,目标不再相关等)。

总的来说,装饰器节点可以充当父节点和子树其他部分之间的门。因此,装饰器有权在满足特定条件之前循环子树,或者在特定计时器过期之前不在该子树中执行,甚至可以改变子树的返回结果。

举一个简单的例子,想象有一个专门用于杀死玩家的子树(它会让智能体尝试杀死玩家)。检查玩家是否在范围内(且不在地图的另一边),或者玩家是否还活着,可能会在我们甚至没有执行那个子树的情况下给我们一个预防性失败。因此,树可以继续进行其他事件或树的其他部分,例如,在另一个子树中,该子树将负责漫游行为。

装饰器可以有参数(一旦选择了装饰器,您就可以在详细信息面板中设置这些参数),通常它们是硬编码值或黑板键引用(本章后面会详细介绍黑板)。

几乎每个装饰器都有一个复选框,用于反转条件(因此,您将有更多的自由度,并可以在树的不同部分使用相同的装饰器来执行不同的条件)。

以下截图显示了一个装饰器如何附加到一个复合节点上。请注意,一个节点可以有多个装饰器:

对于那些熟悉其他行为树系统中的条件节点的人来说,重要的是不要将它们与 Unreal Engine 中的任务叶子节点混淆。更多信息可以在以下网址找到:https://docs.unrealengine.com/en-us/Engine/AI/BehaviorTrees/HowUE4BehaviorTreesDiffer。

与任务一样,Unreal 提供了一些内置的装饰器,可以直接使用。它们是通用的,覆盖了您可能需要的基本用例,但显然,它们不能针对您的游戏或应用程序进行特定化,因此您需要创建自己的装饰器(我们将在第 6 章“扩展行为树”中详细讨论这个问题)。

以下是Unreal内置任务的列表:

  • 黑板:检查黑板上的特定键是否已设置(或未设置)。

  • 检查Actor上的游戏标签:顾名思义,它检查Actor上是否存在特定的游戏标签,该标签由黑板值指定。

  • 比较BBEntry:比较两个黑板值,检查它们是否相等(或不相等)。

  • 复合:允许您一次性组合不同的装饰器,并使用布尔逻辑。一旦放置了这个装饰器,您就可以通过双击打开其编辑器。从那里,您将能够使用布尔运算符和其他装饰器构建一个图形。

  • 条件循环:只要条件满足(无论是黑板键已设置还是未设置),它将继续循环遍历子树。

  • 锥形检查:这个装饰器检查一个点(通常是另一个演员)是否在一个从另一个点(通常是AI代理)开始的锥形范围内,锥形的角度和方向可以改变。例如,如果你想检查玩家是否在敌人前面,你可以使用这段代码来确定这个条件。

  • 冷却时间:一旦执行从这个包含此装饰器的分支退出,冷却计时器将开始,直到这个计时器过期,这个装饰器不允许执行再次进入(它会立即报告失败)。这个节点用于防止你过于频繁地重复相同的子树。

  • 路径是否存在:这个装饰器使用导航系统(更多关于导航的内容在第3章)来确定(并检查)是否存在一条特定点的路径。

  • 强制成功:顾名思义,它强制子树成功,无论下面的子树是否报告了失败(或成功)。这对于在Sequence中创建可选分支非常有用。

注意,没有强制失败的装饰器,因为这样没有意义。如果这个装饰器放在Selection上,它就会变成一个Sequence,如果放在Sequence上,它只会让一个子执行。

  • 是否在位置:顾名思义,它检查Pawn是否在特定的位置(或者靠近特定的位置),可以选择使用导航系统。

  • 是否为特定类别的BBEntry:顾名思义,它检查特定的黑板条目是否为特定类别。当黑板条目是Object类型时,如果你需要检查黑板内的引用是否为特定类(或继承自某个类),这会非常有用。

  • 保持锥形内:与锥形检查类似,这个装饰器(持续)检查观察者是否在一个锥形内。

  • 循环:顾名思义,它在子树中循环特定的次数(甚至可以无限次;在这种情况下,需要其他东西来停止子树的行为,例如另一个装饰器)。

  • 设置标签冷却时间:与其同名的任务类似,当这个装饰器变得相关(或者你可以想象它作为一个门,当它被穿越时),它将改变特定标签的冷却时间(参见下面的节点)。

  • 标签冷却时间:这和冷却时间节点一样,但是它与一个标签关联一个计时器。因此,这个计时器可以被"设置标签冷却时间"任务和"设置标签冷却时间"装饰器改变。

  • 时间限制: 正如其名称所示,它为子树的执行提供一个时间限制。否则,此装饰器将停止执行并返回失败。

现在我们已经了解了装饰器节点的工作原理,让我们探讨行为树的最后一类节点——服务节点,它们将实时更新并提供信息。

服务

服务节点附加到复合节点或任务节点,并将在其分支正在执行时执行。这意味着,只要节点下方有节点附加,无论有多少层级的父子节点正在执行,服务都会运行。以下截图将帮助您可视化这一点:

服务节点是行为树执行过程中的“眼睛”。实际上,它们会持续运行(如果子树处于活动状态),并可以实时执行检查和/或更新黑板(稍后会提到)的值。

服务节点对于您的行为树应用非常具体,因此只有两个默认的。例如,它们可能用于提供/更新信息给子树。想象一个场景,一个子树(敌人)试图杀死玩家。然而,即使玩家没有向敌人射击,继续追求这个目标会很愚蠢(嗯,这取决于敌人的类型,巨魔可能不那么聪明)。因此,当子树试图杀死玩家时,子树需要找到掩护以减少敌人受到的伤害。但是,敌人可能在地图上移动,或者玩家可能摧毁了我们的AI藏身的掩护。因此,子树需要关于最近和最安全掩护位置的信息,这些掩护仍在玩家的射程范围内(EQS查询可以计算出)。服务可以实时更新这些信息,以便当子树需要使用关于掩护的数据时,它们已经准备好。在这个特定的例子中,通过在服务上运行环境查询来寻找掩护是一种动态处理任务的方法(我们将在第4章,环境查询系统中查看这个话题)。否则,服务可能会检查设计师放置在某些指定地图点,并评估哪个最适合其给定的动作。

如你所见,服务节点可能非常强大,但它们也特定于你正在使用它们的应用。因此,它们真的取决于你为你的游戏编程的AI。

以下截图展示了一些服务的示例。请注意,服务可以与装饰器一起使用,一个复合节点可以有多个服务:

服务节点替代了其他行为树系统中的传统并行节点。

唯一可用的两个默认服务(由于您需要为自己的游戏编程自己的服务,我们将在第6章“扩展行为树”中进行)如下面的截图所示:

  • 设置默认焦点:当此节点变为活动状态时,它会自动设置AI控制器的默认焦点。

  • 运行EQS(定期查询):顾名思义,它定期运行环境查询(更多信息请参阅第4章“环境查询系统”),以检查特定位置或演员。这就是我们在为敌人寻找掩护的示例中所需的服务类型。

你将在第4章《环境查询系统》中了解更多关于环境查询的信息。然而,目前,你只需要知道这是一个用于空间推理的系统,运行这些查询可以找到具有特定属性的空间内的位置(或参与者)(例如,为敌人寻找掩护,找到最能满足这些属性:最近、最安全,并且仍然愤怒到向玩家射击的掩护)。

现在,我们已经了解了构成行为树的不同类型的节点。现在,是时候探索黑板了!

最后更新于