行为树基本概念¶
引言:响应式AI的演进¶
在自主代理(autonomous agent)设计领域,行为树(Behavior Tree, BT)的出现标志着一次范式转移,它引领我们告别了早期人工智能技术中僵化、庞大的整体结构。要深刻理解行为树的革命性,我们必须首先审视其前身——有限状态机(Finite State Machine, FSM)。
有限状态机(FSM)的问题¶
有限状态机是一种基础且直观的AI模型,它由一组有限的状态和定义好的、显式的状态转移构成 \({}^{1}\)。对于逻辑简单的场景,例如控制一扇自动门(开启、关闭、正在开启、正在关闭),FSM表现得非常出色。其结构清晰,易于理解。
然而,当AI行为的复杂度增加时,FSM的根本性缺陷便暴露无遗:状态与转移的爆炸性增长。随着新行为和新决策逻辑的引入,状态的数量会线性增加,而更关键的是,状态之间的转移数量可能呈指数级增长。这最终会导致一个难以维护、调试和扩展的混乱网络,业界形象地称之为“意大利面条式状态机”(spaghetti state machine)\({}^{3}\)。
FSM的核心问题在于其固有的非模块化特性。例如,为一个已经包含“巡逻”、“攻击”、“待机”等状态的AI添加一个全局性的“逃跑”状态,将是一项极其繁琐的任务。开发者必须修改几乎所有现存的状态,为它们添加一个指向“逃跑”状态的新转移条件 \({}^{2}\)。这种紧耦合的设计使得系统的扩展性变得异常脆弱。
行为树作为解决方案¶
为了解决FSM的可扩展性危机,行为树应运而生。它最初在对AI要求极高的AAA级游戏开发(如里程碑式的《光环2》)和机器人技术领域得到发展和普及,被证明是一种功能强大、层次化、可组合且具备高度响应性的替代方案 \({}^{5}\)。
行为树的核心优势在于其模块化、可重用性以及对行为优先级的原生支持 \({}^{3}\)。与FSM不同,添加一个高优先级的“逃跑”行为,在行为树中通常只需要在树的顶层插入一个新的分支,而无需触及任何现有行为的内部逻辑。
从根本上看,FSM与BT代表了两种截然不同的控制流哲学。FSM的控制流类似于传统编程语言中的goto语句,控制权通过显式声明的单向指令进行转移 \({}^{8}\)。而行为树则更像现代的结构化编程,其控制流是通过类似 if-else 和函数嵌套的层次化结构隐式决定的 \({}^{9}\)。正是这种内在的结构优雅性,赋予了行为树无与伦比的强大能力与灵活性。
1. 执行模型:行为树的“心跳”¶
行为树的动态特性源于其独特的执行模型。这个模型围绕着一个核心概念——“Tick”(心跳)信号,以及所有节点必须遵守的一套简单的通信协议。
Tick信号:执行的引擎¶
“Tick”是一个离散的、周期性的信号,由外部系统(如游戏引擎的主循环)以固定的频率(例如,每帧一次或每0.1秒一次)发送给行为树的根节点 \({}^{10}\)。这个Tick信号是驱动整个行为树进行评估和执行的唯一催化剂。
一旦根节点接收到Tick,该信号便会根据每个节点的内部逻辑,自上而下地在树中传播。这个过程可以被看作是对树进行的一次深度优先、从左到右的遍历 \({}^{10}\)。父节点“Tick”其子节点,子节点执行完毕后向父节点返回一个状态,父节点再根据这个返回的状态决定下一步的动作——是继续Tick下一个子节点,还是停止执行并向其自身的父节点返回状态。这个循环不断重复,直到整个树完成一次评估。
三种返回状态:控制流的语言¶
行为树的简洁与强大,很大程度上归功于其极简的节点通信协议。每个被Tick的节点在执行完毕后,都必须向其父节点返回以下三种状态之一。这三种状态构成了行为树内部控制流的全部语言 \({}^{10}\)。
- SUCCESS (成功): 表示该节点所代表的任务已经圆满完成,且结果是成功的。例如,“开门”动作完成,“玩家可见”条件成立。
- FAILURE (失败): 表示该节点所代表的任务无法完成,或其检查的条件不成立。例如,“开门”动作因门被锁住而失败,“玩家可见”条件因玩家躲在掩体后而不成立。
- RUNNING (运行中): 表示该节点所代表的任务是持续性的,需要超过一个Tick的时间才能完成。例如,“移动到指定位置”这个动作,在AI到达目的地之前,会持续返回RUNNING状态。这是实现长时间行为的关键。
父节点正是根据子节点返回的这三种状态,来决定如何继续执行树的其余部分。这种机制使得复杂的决策逻辑得以通过简单的、标准化的信号进行传递和处理。
响应性与记忆性的平衡:一项关键的设计洞察¶
将行为树简单地描述为纯粹的“响应式”系统是一种常见的过度简化。其真正的威力在于,它允许设计者在即时响应性(Reactivity) 和 状态持久性(Memory) 之间进行精妙的权衡。
一个纯粹响应式的行为树(即所有节点都是“无记忆”的)在每一次接收到Tick信号时,都会从根节点开始重新评估整棵树 \({}^{7}\)。这种机制赋予了AI极高的响应速度。例如,一个正在追击目标的AI,其行为树的根节点可能是一个选择器(Selector),左侧是高优先级的“规避投掷物”分支,右侧是“追击”分支。即使“追击”分支当前正处于RUNNING状态,每一帧的Tick都会首先检查“规避投掷物”的条件。一旦有投掷物飞来,该分支会立即被激活并返回SUCCESS或RUNNING,从而中断低优先级的追击行为,实现瞬时反应 \({}^{16}\)。
然而,对于需要多个步骤才能完成的任务,纯粹的响应式评估是低效甚至错误的。考虑一个序列(Sequence)任务:[找到钥匙] → [打开门锁] → [穿过门]。假设AI已经完成了前两步,正在执行“穿过门”这个持续性动作(返回RUNNING)。如果这个序列节点是无记忆的,那么在下一个Tick,它会重新从“找到钥匙”开始评估,这显然是荒谬且冗余的。
这个问题的解决方案,正是组合节点(Composite Node)中的“记忆”功能 \({}^{17}\)。一个带有记忆功能的序列节点,当其子节点返回RUNNING时,它会“记住”当前正在运行的子节点。在下一次Tick到达时,它会直接跳过已经成功的前续节点,从那个返回RUNNING的子节点继续执行。
因此,优秀的行为树设计,本质上是在进行一种架构上的选择。树的高层分支,尤其是根节点下的选择器,通常被设计为无记忆的,以确保对外界关键事件(如受到攻击、发现高价值目标)的最高优先级响应。而树的底层分支,特别是执行复杂、多步动作的序列节点,则通常被配置为有记忆的,以保证任务流程的正确性和执行效率。这并非矛盾,而是行为树提供的一种精密而强大的设计特性。
中止(Halting)机制¶
当一个高优先级的分支中断了一个正在返回RUNNING的低优先级分支时,行为树框架会向被中断的分支发送一个halt(中止)信号 \({}^{12}\)。接收到该信号的节点有责任执行清理工作,例如,一个正在移动的Action节点需要停止角色的移动动画并重置其导航状态。这个机制确保了行为切换时的状态一致性和平滑过渡。
2. 行为树节点分类¶
行为树是由不同类型的节点通过层级结构组合而成的。理解这些节点的分类和功能,是掌握行为树设计的基石。所有节点大致可分为三类:叶子节点(Leaf Nodes) 负责执行具体工作,组合节点(Composite Nodes) 负责组织逻辑流程,而 装饰节点(Decorator Nodes) 则负责修改其子节点的行为。
为了提供一个清晰的概览,下表总结了行为树中所有标准节点的分类、功能和逻辑。
| 节点类型 (Node Type) | 类别 (Category) | 逻辑类比 (Logical Analogy) | 执行逻辑摘要 | 返回 SUCCESS 当... | 返回 FAILURE 当... | 返回 RUNNING 当... |
|---|---|---|---|---|---|---|
| 选择器 (Selector) | 组合 (Composite) | OR / if-elif-else | 从左到右Tick子节点,直到一个成功。 | 任何一个子节点返回SUCCESS。 | 所有子节点都返回FAILURE。 | 任何一个子节点返回RUNNING。 |
| 序列 (Sequence) | 组合 (Composite) | AND / 串行步骤 | 从左到右Tick子节点,直到一个失败。 | 所有子节点都返回SUCCESS。 | 任何一个子节点返回FAILURE。 | 任何一个子节点返回RUNNING。 |
| 并行 (Parallel) | 组合 (Composite) | 并发 / fork-join | 同时Tick所有子节点。 | 满足其成功策略(如:N个成功)。 | 满足其失败策略(如:1个失败)。 | 不满足成功/失败策略且有子节点在运行。 |
| 动作 (Action) | 叶子 (Leaf) | 函数调用 / 命令 | 执行改变世界状态的具体操作。 | 动作成功完成。 | 动作执行失败。 | 动作需要更多时间来完成。 |
| 条件 (Condition) | 叶子 (Leaf) | 布尔判断 / if | 检查世界状态,不改变它。 | 条件为真。 | 条件为假。 | (不适用) |
| 反转器 (Inverter) | 装饰 (Decorator) | NOT | 反转子节点的SUCCESS和FAILURE。 | 子节点返回FAILURE。 | 子节点返回SUCCESS。 | 子节点返回RUNNING。 |
| 成功/失败器 | 装饰 (Decorator) | true / false | 总是返回SUCCESS或FAILURE。 | 总是。 | (不适用) | (不适用) |
| 重复器 (Repeater) | 装饰 (Decorator) | for / while 循环 | 重复Tick其子节点N次或直到失败。 | (根据配置) | (根据配置) | 正在重复执行中。 |
| 重试器 (Retry) | 装饰 (Decorator) | try-catch | 当子节点失败时,重试N次。 | 子节点在重试次数内成功。 | 重试N次后子节点仍失败。 | 子节点返回RUNNING。 |
2.1 叶子节点(Leaf Nodes):"工作"的执行者¶
叶子节点是行为树的末端,是AI与游戏世界或机器人系统进行实际交互的地方。它们是树中唯一不包含子节点的节点 \({}^{12}\)。
- 动作节点 (Action Nodes): 动作节点是AI的“动词”。它们执行具体的代码,用以改变世界或AI自身的状态,例如 MoveTo(position)、PlayAnimation("attack") 或 FireWeapon() \({}^{15}\)。一个动作节点可以设计为同步的(在一个Tick内完成并返回 SUCCESS或FAILURE)或异步的(需要多个Tick,期间返回RUNNING)。
- 条件节点 (Condition Nodes): 条件节点是AI的“问句”。它们用于检查世界或AI自身的状态,但绝不应修改任何状态。例如 IsPlayerVisible?、IsHealthLow? 或 HasKey? \({}^{15}\)。条件节点是瞬时检查,因此它们只返回 SUCCESS(条件为真)或FAILURE(条件为假),绝不应返回RUNNING。值得注意的是,一些现代行为树框架(如Unreal Engine)推荐将条件检查主要作为装饰器来实现,而不是独立的叶子节点。这种做法可以使行为树的主干逻辑更清晰,因为它将“做什么”(动作)与“在什么条件下做”(条件)在视觉上分离开来 \({}^{18}\)。
2.2 组合节点(Composite Nodes):"逻辑"的组织者¶
组合节点是行为树的内部骨架,它们至少拥有一个子节点,负责控制Tick信号的流向,从而构建出复杂的逻辑结构 \({}^{14}\)。
- 选择器 (Selector / Fallback):
- 逻辑: 选择器按从左到右的顺序依次Tick其子节点。一旦某个子节点返回SUCCESS,选择器便会立即停止执行,并向其父节点返回SUCCESS。如果子节点返回FAILURE,它会继续Tick下一个子节点。只有当所有子节点都返回FAILURE时,选择器自身才会返回FAILURE \({}^{10}\)。
- 类比: 逻辑上的OR门,或编程中的if-elif-else链。
- 用途: 这是实现优先级逻辑的基石。最高优先级的行为被放置在最左侧,重要性依次递减,最低优先级的默认行为则放在最右侧。例如,一个典型的战斗AI根节点会是这样的选择器:[规避手雷] OR [攻击敌人] OR [寻找掩体] OR [巡逻] \({}^{21}\)。
- 序列 (Sequence):
- 逻辑: 序列同样按从左到右的顺序依次Tick其子节点。但与选择器相反,一旦某个子节点返回FAILURE,序列会立即停止执行,并向其父节点返回FAILURE。只有当所有子节点都依次成功返回SUCCESS时,序列自身才会返回SUCCESS \({}^{10}\)。
- 类比: 逻辑上的AND门,或一个必须按顺序完成所有步骤的流程清单。
- 用途: 用于定义一系列必须连续完成的动作。例如,一个“装填弹药”的行为可以被构建为一个序列:[移动到掩体后] AND [蹲下] AND [播放装弹动画] \({}^{21}\)。
- 并行 (Parallel):
- 逻辑: 并行节点在每次被Tick时,会同时Tick其所有的子节点 \({}^{3}\)。这是一种概念上的并发,实际执行仍然在单个线程中按顺序进行,但效果是所有子分支都在同步推进。
- 策略: 并行节点自身的SUCCESS或FAILURE状态由一个可配置的策略决定。常见的策略有:“当N个子节点成功时,自身成功”、“当所有子节点成功时,自身成功”、“当任意一个子节点失败时,自身失败”等 \({}^{11}\)。
- 用途: 用于执行一个主要任务的同时,持续监控某个条件或执行一个辅助任务。例如,一个AI可以在执行[跑向玩家]这个主要动作的同时,并行执行[瞄准玩家]的辅助动作。
2.3 装饰节点(Decorator Nodes):"行为"的修饰者¶
装饰节点是一种特殊的节点,它有且仅有一个子节点。其作用不是执行具体任务,而是增强、修改或控制其子节点的行为或返回结果 \({}^{10}\)。
- 逻辑修饰类:
- 反转器 (Inverter): 将子节点的返回结果反转。SUCCESS变为FAILURE,FAILURE变为SUCCESS。RUNNING状态则保持不变 \({}^{14}\)。一个常见的用法是,通过
Inverter包装一个IsDoorOpen?条件,来实现IsDoorClosed?的逻辑。 - 成功器/失败器 (Succeeder/Failer): 无论其子节点返回什么状态,该节点总是返回SUCCESS或FAILURE \({}^{20}\)。这在序列节点中非常有用,可以用来标记某个步骤是“可选的”。
- 流程控制类:
- 重复器 (Repeater): 不断地Tick其子节点,可以配置为重复指定的次数,或者无限重复,直到子节点返回FAILURE \({}^{11}\)。
- 重试器 (Retry): 当其子节点返回FAILURE时,会重新Tick它,直到成功或达到预设的重试次数上限 \({}^{10}\)。
- 条件守卫类 (Conditional Decorators / Guards): 这是最常用的一类装饰器。它们像一个“阀门”,在Tick其子节点之前,会先检查一个条件。只有当条件满足时(返回SUCCESS),Tick信号才会被传递给子节点;否则,装饰器自身会返回FAILURE,从而阻止子节点的执行。这种将条件直接附加在行为分支上的方式,使得行为树的结构更加紧凑和易读 \({}^{18}\)。
3. 数据流与解耦:黑板模式¶
行为树的模块化和可重用性并不仅仅来自于其节点结构,更深层次上,它依赖于一种优雅的数据管理架构——黑板模式(Blackboard Pattern)。
问题:状态与可重用性¶
软件工程的一个核心原则是:要使一个组件(在我们的场景中,是一个行为树节点)可重用,就必须将其逻辑与它所操作的具体数据解耦 \({}^{24}\)。如果一个名为
MoveTo的动作节点内部硬编码了一个目标坐标(10, 20, 30),那么这个节点除了移动到这个特定坐标外,将毫无用处。
此外,行为树节点应当是无状态的。将状态信息(例如,“目标是否已到达”)存储在节点实例内部是一种糟糕的设计。因为无法保证一个节点在每一帧都会被Tick,这可能导致其内部数据变得陈旧或过时。同时,有状态的节点也不可重入,无法在树的不同位置安全地复用同一个节点实例 \({}^{24}\)。
解决方案:黑板架构¶
黑板模式为这些问题提供了完美的解决方案。黑板是一个集中的、键值对(key-value)形式的数据存储中心,它被整个行为树的所有节点共享,扮演着AI“工作记忆”的角色 \({}^{24}\)。
- 数据中心: 任何节点都可以从黑板读取数据,也可以向黑板写入数据。
- 解耦: 节点之间不再直接通信,而是通过黑板这个中介进行交互。它们只需要遵守一个共同的“契约”,即预先定义好的黑板键(key)的名称和数据类型 \({}^{27}\)。这彻底地将行为逻辑(节点做什么)与上下文数据(对谁做,用什么做)分离开来。
数据访问:端口与重映射¶
一个设计精良的行为树节点,并不会在其代码中硬编码黑板键的字符串。相反,它会向外暴露“端口”(Ports),这些端口定义了该节点需要哪些输入数据,以及会产生哪些输出数据 \({}^{27}\)。
例如,MoveTo节点可以暴露一个名为TargetLocation的输入端口,其类型为向量。在行为树的可视化编辑器中,设计者可以将这个TargetLocation端口映射到黑板中的任意一个向量类型的键上,比如PlayerLastKnownPosition或CurrentPatrolPoint \({}^{27}\)。
这种端口映射机制极大地提升了节点的可重用性。同一个MoveTo节点实例,可以通过在树的不同位置改变其端口映射,被用于追击玩家、巡逻、或移动到撤退点,而无需修改任何代码。
黑板作为“神经系统”与通信总线¶
黑板的价值远不止于单个AI的内存管理。当我们将视野放大,会发现它构成了AI群体智能的基石,扮演着连接感知、决策和行动的“神经系统”角色。
在最基础的层面上,黑板服务于单个代理的记忆系统 \({}^{25}\)。然而,一个更高级的应用是实现
代理间的通信。想象一个由多名AI组成的战斗小队,他们可以共享同一个“小队级”的黑板实例 \({}^{25}\)。
- 当小队成员A发现了玩家,它不仅更新自己的内部状态,更重要的是,它会将玩家的位置信息写入到共享的小队黑板中,键为Squad_PlayerSpottedLocation。
- 此时,小队成员B和C,即使他们自己并没有看到玩家,其行为树中也存在着监听这个共享键的分支。
- 当它们检测到Squad_PlayerSpottedLocation被设置后,便可以自动触发“火力压制”或“侧翼包抄”等协同行为。它们是在对通过黑板间接获得的信息做出反应。
这种模式自然地引出了 分层或作用域黑板(Hierarchical or Scoped Blackboards) 的概念 \({}^{29}\)。一个AI代理可能拥有自己的本地黑板,这个本地黑板又可以有一个指向小队黑板的父链接,而小队黑板则可能链接到一个全局黑板。当一个节点请求某个键的值时,系统会首先在本地黑板查找,如果找不到,则会沿着作用域链向上,到父级黑板中继续查找,直至找到或到达根部。
这种分层结构提供了一种极其复杂的、结构化的数据管理方案。它不仅可以有效防止不同层级间的命名冲突,更重要的是,它为实现从个体行为到小队战术,再到全局战场策略的多层次、协同AI提供了坚实的架构基础。
综上所述,黑板不仅仅是一个数据字典。它是将独立的AI个体提升为协调一致、具备智能的团队的关键架构组件。它是连接感知、决策和行动的API,无论是在单个代理内部,还是在整个AI群体之间。
4. 应用设计:构建一个多层行为AI代理¶
理论知识的最终目的是指导实践。本节将通过一个具体的案例研究,一步步地构建一个经典的、包含“巡逻->追击->攻击->撤退”等多种行为的AI代理,从而将前面讨论的所有概念融会贯通。
4.1 行为定义与黑板设计¶
在着手绘制行为树之前,首要任务是明确定义AI需要具备的行为,并据此设计其“记忆系统”——黑板。这个“设计先行”的步骤至关重要,它能确保逻辑结构的清晰和完整。
行为需求定义:
- 巡逻 (Patrol): 在没有其他任务时,沿着预设的路径点移动。
- 调查 (Investigate): 听到可疑声音时,移动到声源处进行观察。
- 追击 (Chase): 当看到玩家时,向玩家移动。
- 攻击 (Attack): 当玩家进入攻击范围且在视线内时,对其进行攻击。
- 撤退 (Retreat): 当自身生命值过低时,远离玩家,跑到安全地点。
基于以上行为需求,我们可以设计出如下的黑板结构。这相当于为我们即将构建的所有行为树节点建立了一个清晰的“数据契约” \({}^{25}\)。
AI代理黑板设计表
| 键名 (Key Name) | 数据类型 (Data Type) | 作用域 (Scope) | 描述 (Description) |
|---|---|---|---|
| SelfActor | Object (Pawn) | 本地 (Local) | 对AI自身控制的Pawn对象的引用。 |
| PlayerTarget | Object (Actor) | 本地 (Local) | 对当前可见的玩家角色的引用,在看到玩家时设置 \({}^{30}\)。 |
| CanSeePlayer | Boolean | 本地 (Local) | 标志位,表示当前是否能直接看到玩家 \({}^{30}\)。 |
| LastKnownPlayerLocation | Vector | 本地 (Local) | 玩家最后一次被看到的位置,用于丢失目标后的调查行为 \({}^{25}\)。 |
| CurrentPatrolPoint | Vector | 本地 (Local) | 当前正在前往的巡逻路径点的坐标 \({}^{21}\)。 |
| IsUnderAttack | Boolean | 本地 (Local) | 标志位,表示AI最近是否受到了伤害,可用于触发防御或规避行为。 |
| HealthPercentage | Float | 本地 (Local) | AI当前的生命值百分比(0.0到1.0),用于触发撤退行为 \({}^{31}\)。 |
| RetreatLocation | Vector | 本地 (Local) | 计算出的一个用于撤退的安全目标点 \({}^{31}\)。 |
| LastKnownSoundLocation | Vector | 本地 (Local) | 听到的最后一个可疑声音的来源位置。 |
| SquadAlertLevel | Enum | 小队 (Squad) | AI所在小队的共享警戒等级(如:正常、警觉、战斗)\({}^{25}\)。 |
4.2 使用根选择器构建优先级¶
整个AI的行为逻辑将从一个根选择器(Selector)节点开始。这种结构天生就建立了一个基于优先级的决策框架。所有连接到该选择器的子分支将严格按照从左到右的顺序进行评估 \({}^{21}\)。
高层行为树结构:
Selector (AI行为根节点)
|
|--- [优先级 1] 生存分支 (Sequence: 撤退)
|
|--- [优先级 2] 战斗分支 (Sequence: 攻击/追击)
|
|--- [优先级 3] 调查分支 (Sequence: 调查声音)
|
`--- [优先级 4] 默认分支 (Sequence: 巡逻)
这种布局确保了“生存”是AI的最高行为准则。只有在不需要撤退时,AI才会考虑战斗。只有在既不需撤退也不需战斗时,它才会去调查声音。而巡逻,则是它在无事可做时的最低优先级默认行为。
4.3 实现行为子树(分支细节)¶
现在,我们来详细构建每一个优先级分支的内部逻辑。
生存分支 (撤退)¶
这是最高优先级的行为,确保AI在危急时刻首先选择自保。
- Sequence: Retreat
- Decorator: IsHealthLow? (条件守卫): 检查黑板中的HealthPercentage是否低于某个阈值(例如0.2)\({}^{31}\)。
- Action: FindRetreatLocation: 这是一个自定义动作。它读取PlayerTarget的位置,计算出一个远离该位置的安全点,并将结果写入黑板的RetreatLocation键 \({}^{31}\)。
- Action: MoveTo: 将其TargetLocation端口映射到黑板的RetreatLocation键,驱动AI向安全点移动。
- Action: RequestHeal: 到达安全点后,可以执行一个等待计时器或播放一个请求治疗的动画。
战斗分支 (攻击/追击)¶
当AI状态良好且发现敌人时,此分支被激活。
- Sequence: Combat
- Decorator: CanSeePlayer?: 检查黑板中的CanSeePlayer是否为true \({}^{30}\)。
- Selector: Attack or Chase: 使用一个内部选择器来决定是攻击还是追击。
- Sequence: Attack (高优先级)
- Decorator: IsPlayerInAttackRange?: 检查玩家与自身的距离是否在有效攻击范围内。
- Action: AimAtTarget: 持续将朝向对准PlayerTarget。
- Action: FireWeapon: 执行开火动作。
- Sequence: Chase (低优先级回退)
- Action: MoveTo: 将其TargetLocation端口映射到黑板的PlayerTarget键,驱动AI追向玩家。
调查分支¶
当AI丢失目标或听到声音时,此分支用于表现出更智能的搜索行为。
- Sequence: Investigate
- Selector: Investigate Cue
- Sequence: Investigate Sound (高优先级)
- Decorator: HasHeardSound?: 检查黑板中是否有LastKnownSoundLocation。
- Action: MoveTo: 移动到LastKnownSoundLocation。
- Action: LookAround: 到达后执行一个四处观察的动画。
- Action: ClearSoundLocation: 清除黑板中的LastKnownSoundLocation,防止重复调查。
- Sequence: Investigate Last Sighting (低优先级)
- Decorator: HasLostPlayer?: 检查CanSeePlayer为false但LastKnownPlayerLocation有值。
- Action: MoveTo: 移动到LastKnownPlayerLocation。
- Action: SearchArea: 在最后发现地点附近进行搜索。
- Action: ClearLastSighting: 清除LastKnownPlayerLocation。
默认分支 (巡逻)¶
这是AI的“待机”行为,优先级最低。
- Sequence: Patrol
- Action: FindNextPatrolPoint: 从预设路径中获取下一个巡逻点,并写入黑板的CurrentPatrolPoint键 \({}^{21}\)。
- Action: MoveTo: 将其TargetLocation端口映射到CurrentPatrolPoint键。
- Action: Wait: 到达巡逻点后,执行一个短暂的等待,使其行为更自然。
4.4 完整的行为树图景¶
通过上述设计,我们构建了一个结构清晰、逻辑分层、行为富有表现力的AI。一个完整的可视化图表将能更直观地展示这一切。在不同的情境下,Tick信号的执行路径会截然不同:
- 情景一:玩家进入视野。Tick从根节点开始,首先检查“生存分支”,因生命值正常而失败。接着检查“战斗分支”,CanSeePlayer?装饰器成功,Tick进入该分支。AI根据与玩家的距离,选择执行“攻击”或“追击”子序列。整个“巡逻”分支由于优先级低,完全不会被执行。
- 情景二:AI生命值过低。Tick从根节点开始,检查“生存分支”时,IsHealthLow?装饰器成功。Tick信号将完全流入该分支,AI开始执行撤退逻辑。所有低优先级的战斗、调查和巡逻行为都被立即中断(如果正在运行,会收到halt信号)。
这种通过结构来定义优先级的模式,正是行为树相较于FSM的巨大优势所在。它使得AI的行为逻辑既能响应瞬息万变的战场环境,又能保持内在的结构化和可维护性。
5. 结论¶
总结¶
本文档深入剖析了行为树(Behavior Tree)的核心原理与实践应用。我们从其作为有限状态机(FSM)演进方案的背景出发,揭示了其在解决复杂AI设计中可扩展性与模块化问题的卓越能力。
核心的执行模型,即由周期性的“Tick”信号驱动,并以SUCCESS、FAILURE和RUNNING三种状态作为通用语言的机制,构成了行为树响应式与异步行为的基础。我们特别强调了在“响应性”与“记忆性”之间进行权衡的设计思想,这对于构建既能快速反应又能正确执行多步流程的AI至关重要。
通过对节点分类法的详细梳理,我们明确了叶子节点(动作与条件)、组合节点(选择器、序列、并行)和装饰节点(反转器、守卫等)各自在行为树中扮演的角色,它们是构建一切复杂逻辑的原子构件。
而黑板模式的引入,则从根本上解决了数据流和组件解耦的问题。通过将状态集中存储于黑板,并利用端口映射机制,行为树节点得以实现真正的无状态和高度可重用。更进一步,分层或作用域黑板的概念,为实现复杂的群体智能和协同战术提供了坚实的架构支持。
最后,通过构建一个多层行为AI代理的应用设计案例,我们将所有理论知识付诸实践,展示了如何利用根选择器构建优先级,如何将复杂行为分解为独立的子树,以及如何通过黑板来驱动决策,最终形成一个富有表现力且易于扩展的AI系统。
高级主题与混合模型¶
行为树并非解决所有AI问题的银弹,但在其适用领域内表现卓越。值得注意的是,它可以与其他AI技术结合,形成更强大的混合模型。
- 行为树与FSM的结合: 一种常见的有效模式是,使用一个高阶的FSM来管理AI的宏观状态(例如,和平、警戒、战斗),而在每一个宏观状态内部,使用一棵完整的行为树来驱动具体的、细粒度的行为 \({}^{3}\)。例如,当FSM进入
战斗状态时,它会开始Tick一棵专门用于战斗的行为树。这种模式兼具了FSM状态切换的明确性和BT行为组织的灵活性。 - 其他AI范式: 对于某些特定类型的问题,其他AI范式可能更为适合。例如,目标导向动作规划(Goal-Oriented Action Planning, GOAP),在需要AI动态生成一系列动作以达成某个复杂目标(如《F.E.A.R.》中的AI)时表现出色。而效用系统(Utility Systems),则通过为每个可能的行为计算一个“效用分数”来做决策,非常适合模拟类游戏或需要进行复杂权衡的AI \({}^{6}\)。
思考¶
行为树不仅仅是一种技术工具,更是一种设计哲学。它鼓励开发者通过组合简单的、可重用的、目标明确的构建块,来逐步搭建出复杂、可信且易于维护的智能系统。它将复杂的决策过程转化为一个层次分明、优先级清晰的树状结构,使得设计师和程序员能够用一种更接近自然语言的方式来思考和构建AI行为。掌握行为树,意味着掌握了一种能够驾驭复杂性、创造富有生命力角色的强大方法论。
参考文献¶
- behavior tree or FSM? : r/godot - Reddit, https://www.reddit.com/r/godot/comments/10pohz5/behavior_tree_or_fsm/
- Should I use behavior trees or Finite state machines? : r/unrealengine - Reddit, https://www.reddit.com/r/unrealengine/comments/1eskk42/should_i_use_behavior_trees_or_finite_state/
- Behavior Trees or Finite State Machines - Opsive, https://opsive.com/support/documentation/behavior-designer/behavior-trees-or-finite-state-machines/
- What is the difference between FSM and Behavior Trees? - Construct 3, https://www.construct.net/en/forum/construct-2/general-discussion-17/difference-fsm-behavior-trees-92614
- Finite State Machine & Behavior Tree in Robotics | Mingu Kwon, https://www.mingukwon.com/posts/finite-state-machine-&-behaviour-tree/
- Is using behavior trees a good option for enemy AI in my first FPS shooter game? - Reddit, https://www.reddit.com/r/gamedev/comments/1dls8tk/is_using_behavior_trees_a_good_option_for_enemy/
- State Machines vs Behavior Trees ... - Polymath Robotics Blog, https://www.polymathrobotics.com/blog/state-machines-vs-behavior-trees
- CN112549029B - 一种基于行为树的机器人行为控制方法及装置 - Google Patents, https://patents.google.com/patent/CN112549029B/zh
- Behavior Tree with interrupted sequence - Game Development Stack Exchange, https://gamedev.stackexchange.com/questions/114125/behavior-tree-with-interrupted-sequence
- Introduction to BTs | BehaviorTree.CPP, https://www.behaviortree.dev/docs/learn-the-basics/bt_basics/
- Behavior Trees — DeepStream documentation - NVIDIA Docs, https://docs.nvidia.com/metropolis/deepstream/dev-guide/graphtools-docs/docs/text/ExtensionsManual/Behavior_Tree.html
- Execution Semantics of Behavior Trees in Robotics Applications - arXiv, https://arxiv.org/pdf/2408.00090
- Behavior Trees - NVIDIA Docs, https://docs.nvidia.com/holoscan/sdk-user-guide/gxf/doc/behavior_tree/behavior_trees.html
- Nodes - Game Creator!, https://docs.gamecreator.one/behavior/behavior/behavior-graph/nodes
- 经验分享|行为树在动作游戏AI中的应用 - 华南理工大学, https://www2.scut.edu.cn/huanghan/2021/1021/c9791a448050/page.htm
- Behavior Trees :: Actions That Take Longer Than One Tick, https://gamedev.stackexchange.com/questions/51738/behavior-trees-actions-that-take-longer-than-one-tick
- Composites — py_trees 2.3.0 documentation - Py Trees, https://py-trees.readthedocs.io/en/devel/composites.html
- Behavior Tree Tutorial - Community & Industry Discussion - Unreal Engine Forums, https://forums.unrealengine.com/t/behavior-tree-tutorial/105
- Your first Behavior Tree | BehaviorTree.CPP, https://www.behaviortree.dev/docs/3.8/tutorial-basics/tutorial_01_first_tree/
- Nodes - Documentation, https://docs.gamecreator.io/behavior/behavior-trees/nodes/
- Behavior Tree in Unreal Engine - Quick Start Guide | Unreal Engine ..., https://dev.epicgames.com/documentation/en-us/unreal-engine/behavior-tree-in-unreal-engine---quick-start-guide
- Simple AI with Behavior Trees - Agro? - AI - Unreal Engine Forums, https://forums.unrealengine.com/t/simple-ai-with-behavior-trees-agro/303678
- Smart Enemy AI | (Part 2: Patrolling & States) | Tutorial in Unreal Engine 5 (UE5) - YouTube, https://www.youtube.com/watch?v=WFV5IewGks8\&pp=0gcJCfwAo7VqN5tD
- Behavior Trees and how to implement them in Godot | This is Vini!, https://thisisvini.com/behavior-trees
- npc - Data in blackboards of behavior trees - Game Development ..., https://gamedev.stackexchange.com/questions/204162/data-in-blackboards-of-behavior-trees
- Using the Blackboard in Unity Behavior | Unity Tutorial - YouTube, https://www.youtube.com/watch?v=YWGYv95gSfY
- Blackboard and ports - BehaviorTree.CPP, https://www.behaviortree.dev/docs/tutorial-basics/tutorial_02_basic_ports/
- Behaviour Tree Quick Start Guide - AI not patrolling [SOLVED] - Unreal Engine Forums, https://forums.unrealengine.com/t/behaviour-tree-quick-start-guide-ai-not-patrolling-solved/145084
- Sharing data using Blackboard — LimboAI 1.0 documentation, https://limboai.readthedocs.io/en/stable/behavior-trees/using-blackboard.html
- Enemy AI With Behavior Trees In Unreal Engine - Awesome Tuts, https://awesometuts.com/blog/ai-behavior-tree-unreal-engine/
- How to make AI Run Away from Player with BluePrint? - AI - Epic ..., https://forums.unrealengine.com/t/how-to-make-ai-run-away-from-player-with-blueprint/682764