AI行为树的全面指南,从核心概念和组件到游戏、机器人等领域的实际应用。
人工智能:深入探索行为树
在广阔且不断发展的人工智能领域中,开发人员一直在寻找强大、可扩展且直观的工具。从我们最喜欢的视频游戏中出现的非玩家角色 (NPC) 到仓库中分拣包裹的自主机器人,创建可信且有效的 AI 行为是一项艰巨的任务。虽然存在许多技术,但有一种技术以其优雅性和灵活性而成为主导力量:行为树 (BT)。
如果您曾经对游戏中智能地寻找掩护、与盟友协调并根据情况改变战术的敌人感到惊叹,那么您可能已经见证了行为树的实际应用。本文全面探讨了行为树,从基本概念到高级应用,专为全球开发人员、设计师和 AI 爱好者设计。
简单系统的局限性:为什么我们需要行为树
要理解行为树的创新之处,了解之前的技术会有所帮助。多年来,简单 AI 的首选解决方案是有限状态机 (FSM)。
FSM 由一组状态(例如,巡逻、追逐、攻击)和它们之间的转换组成(例如,如果“发现敌人”,则从巡逻转换到追逐)。对于具有一些不同行为的简单 AI,FSM 效果很好。但是,随着复杂性的增加,它们很快变得难以管理。
- 可扩展性问题:添加新状态(例如“寻找掩护”)可能需要从每个其他现有状态创建转换。这导致开发人员所说的“意大利面条式代码”——一个难以调试和扩展的复杂连接网络。
- 缺乏模块化:行为与状态紧密耦合。在不同场景中重用“寻找弹药”逻辑很困难,除非复制代码和逻辑。
- 刚性:FSM 一次总是处于一个且仅一个状态。这使得难以模拟细微或分层的行为。
开发行为树是为了解决这些问题,提供一种更结构化、模块化和可扩展的方法来设计复杂的 AI 代理。
什么是行为树?AI 的分层方法
从本质上讲,行为树是一个节点的分层树,用于控制 AI 代理的决策流程。可以将其视为公司的组织结构图。顶部的 CEO(根节点)不执行每项任务;相反,他们委派给经理(组合节点),然后经理委派给执行特定工作的员工(叶节点)。
树从上到下进行评估,从根开始,通常在每个帧或更新周期中进行。此过程称为“tick”。tick 信号沿树向下传播,根据一组规则激活特定路径上的节点。每个节点在完成后,都会向其父节点返回一个状态:
- SUCCESS:节点代表的任务已成功完成。
- FAILURE:无法完成任务。
- RUNNING:任务正在进行中,需要更多时间才能完成(例如,走到目的地)。
父节点使用这些状态来决定接下来 tick 哪个子节点。这种连续的自上而下的重新评估使 BT 能够对世界中不断变化的条件做出惊人的反应。
行为树的核心组件
每个行为树都由几种基本类型的节点构成。理解这些构建块是掌握系统的关键。
1. 叶节点:动作和条件
叶节点是树的端点——它们是执行任务或检查条件的实际工作者。它们没有子节点。
- 动作节点:这些节点在游戏世界中执行一个动作。如果动作是瞬时的(例如,发射武器),它可能会立即返回 `SUCCESS`。如果需要时间(例如,移动到某个点),它将在每次 tick 时返回 `RUNNING`,直到完成为止,此时它返回 `SUCCESS`。示例包括 `MoveToEnemy()`、`PlayAnimation("Attack")`、`ReloadWeapon()`。
- 条件节点:这些是叶节点的一种特殊类型,用于检查世界的状态而不改变它。它们充当树中的网关,如果条件为真,则返回 `SUCCESS`,如果条件为假,则返回 `FAILURE`。示例包括 `IsHealthLow?`、`IsEnemyInLineOfSight?`、`HasAmmunition?`。
2. 组合节点:控制流
组合节点是树的管理者。它们有一个或多个子节点,并使用一组特定的规则来决定执行哪个子节点。它们定义了 AI 的逻辑和优先级。
-
序列节点:通常表示为箭头 (→) 或标记为“AND”。序列按顺序从左到右执行其子节点。只要其中一个子节点失败,它就会停止并返回 `FAILURE`。如果所有子节点都成功,则序列本身返回 `SUCCESS`。这用于创建必须按顺序执行的任务序列。
示例: `Reload` 序列可能是:Sequence( `HasAmmoInInventory?`, `PlayReloadAnimation()`, `UpdateAmmoCount()` )。如果代理库存中没有弹药,则第一个子节点失败,并且整个序列立即中止。
-
选择器节点(或回退节点):通常表示为问号 (?) 或标记为“OR”。选择器也按顺序从左到右执行其子节点。但是,只要其中一个子节点成功,它就会停止并返回 `SUCCESS`。如果所有子节点都失败,则选择器本身返回 `FAILURE`。这用于创建回退行为或从一系列可能性中选择一个动作。
示例: `Combat` 选择器可能是:Selector( `PerformMeleeAttack()`, `PerformRangedAttack()`, `Flee()` )。AI 将首先尝试近战攻击。如果那不可能(例如,目标太远),它会失败,并且选择器移动到下一个子节点:远程攻击。如果这也失败(例如,没有弹药),它会移动到最后一个选项:逃跑。
-
并行节点:此节点同时执行其所有子节点。它自身的成功或失败取决于指定的策略。例如,它可以只要一个子节点成功就返回 `SUCCESS`,或者它可以等待所有子节点都成功。这对于运行主要任务,同时运行辅助监视任务非常有用。
示例: `Patrol` 并行可以是:Parallel( `MoveAlongPatrolPath()`, `LookForEnemies()` )。AI 沿着它的路径行走,同时不断扫描环境。
3. 装饰器节点:修饰符
装饰器节点只有一个子节点,用于修改该子节点的行为或结果。它们添加了一个强大的控制和逻辑层,而不会使树变得混乱。
- 反相器:反转其子节点的结果。`SUCCESS` 变为 `FAILURE`,`FAILURE` 变为 `SUCCESS`。`RUNNING` 通常保持不变地传递。这非常适合创建“如果不是”逻辑。
示例: Inverter( `IsEnemyVisible?` ) 将创建一个仅当敌人不可见时才成功的条件。
- 重复器:执行其子节点指定的次数或无限期地执行,直到子节点失败。
- 成功器/失败器:始终分别返回 `SUCCESS` 或 `FAILURE`,而不管其子节点返回什么。这对于使树的分支成为可选的是很有用的。
- 限制器/冷却:限制其子节点可以执行的频率。例如,`GrenadeThrow` 动作可以用限制器装饰,以确保它只能每 10 秒执行一次。
将所有内容放在一起:一个实际示例
让我们为一个简单的第一人称射击游戏中的敌方士兵 AI 设计一个行为树。所需的行为是:如果士兵可见,则士兵的首要任务是攻击玩家。如果玩家不可见,士兵应在指定区域巡逻。如果在战斗中士兵的生命值降低,他们应该寻找掩护。
以下是我们如何在行为树中构建此逻辑(从上到下读取,缩进显示层次结构):
Root (Selector) |-- Low Health Escape (Sequence) | |-- IsHealthLow? (Condition) | |-- FindCoverPoint (Action) -> returns RUNNING while moving, then SUCCESS | `-- TakeCover (Action) | |-- Engage Player (Sequence) | |-- IsPlayerVisible? (Condition) | |-- IsWeaponReady? (Condition) | |-- Combat Logic (Selector) | | |-- Shoot At Player (Sequence) | | | |-- IsPlayerInLineOfSight? (Condition) | | | `-- Shoot (Action) | | `-- Move To Attack Position (Sequence) | | |-- Inverter(IsPlayerInLineOfSight?) (Decorator + Condition) | | `-- MoveTowardsPlayer (Action) | `-- Patrol (Sequence) |-- GetNextPatrolPoint (Action) `-- MoveToPoint (Action)
它在每次“tick”上的工作方式:
- Root Selector 开始。它尝试它的第一个子节点,`Low Health Escape` 序列。
- `Low Health Escape` 序列首先检查 `IsHealthLow?`。如果生命值不高,则此条件返回 `FAILURE`。整个序列失败,控制返回到根。
- Root Selector,看到它的第一个子节点失败,移动到它的第二个子节点:`Engage Player`。
- `Engage Player` 序列检查 `IsPlayerVisible?`。如果不是,它会失败,根移动到 `Patrol` 序列,导致士兵和平巡逻。
- 但是,如果 `IsPlayerVisible?` 成功,则序列继续。它检查 `IsWeaponReady?`。如果成功,它会继续到 `Combat Logic` 选择器。此选择器将首先尝试 `Shoot At Player`。如果玩家在视线中,则执行 `Shoot` 动作。
- 如果在战斗中,士兵的生命值下降,在下一个 tick 上,第一个条件 (`IsHealthLow?`) 将成功。这将导致 `Low Health Escape` 序列运行,使士兵找到并采取掩护。因为根是一个选择器,并且它的第一个子节点现在成功(或正在运行),它甚至永远不会评估 `Engage Player` 或 `Patrol` 分支。这就是如何自然地处理优先级。
这种结构清晰、易于阅读,最重要的是,易于扩展。想要添加一个投掷手榴弹的行为?您可以将另一个序列插入到 `Combat Logic` 选择器中,其优先级高于射击,并具有自己的条件(例如,`IsPlayerInCover?`、`HasGrenade?`)。
行为树与有限状态机:复杂性的明显赢家
让我们正式比较一下:
特征 | 行为树 (BTs) | 有限状态机 (FSMs) |
---|---|---|
模块化 | 非常高。子树(例如,“寻找健康包”序列)可以创建一次,并在许多不同的 AI 中或在同一树的不同部分中重用。 | 低。逻辑嵌入在状态和转换中。重用行为通常意味着复制状态及其连接。 |
可扩展性 | 优秀。添加新行为就像在树中插入一个新分支一样简单。对逻辑其余部分的影响是局部的。 | 差。随着状态的添加,潜在转换的数量可能会呈指数增长,从而导致“状态爆炸”。 |
反应性 | 固有地反应。树从根开始在每次 tick 时重新评估,从而可以根据定义的优先级立即对世界变化做出反应。 | 反应性较差。代理“卡在”其当前状态,直到触发特定的预定义转换。它不会不断地重新评估其总体目标。 |
可读性 | 高,尤其是在使用可视化编辑器的情况下。分层结构清楚地显示了优先级和逻辑流程,即使对于非程序员(如游戏设计师)来说,也很容易理解。 | 随着复杂性的增加而降低。复杂 FSM 的可视化图形看起来像一盘意大利面条。 |
游戏以外的应用:机器人技术和模拟
虽然行为树在游戏行业中声名鹊起,但它们的效用远不止于此。任何需要自主、面向任务的决策的系统都是 BT 的主要候选者。
- 机器人技术:可以使用 BT 对仓库机器人的整个工作日进行建模。根可能是 `FulfillOrder` 或 `RechargeBattery` 的选择器。`FulfillOrder` 序列将包括诸如 `NavigateToShelf`、`IdentifyItem`、`PickUpItem` 和 `DeliverToShipping` 之类的子节点。诸如 `IsBatteryLow?` 之类的条件将控制高级转换。
- 自主系统:无人飞行器 (UAV) 或探索任务中的漫游车可以使用 BT 来管理复杂的任务计划。一个序列可能涉及 `TakeOff`、`FlyToWaypoint`、`ScanArea` 和 `ReturnToBase`。选择器可以处理紧急回退,例如 `ObstacleDetected` 或 `LostGPS`。
- 模拟和培训:在军事或工业模拟器中,BT 可以驱动模拟实体(人员、车辆)的行为,以创建逼真且具有挑战性的培训环境。
挑战和最佳实践
尽管行为树功能强大,但也并非没有挑战。
- 调试:在一个大型树中,跟踪 AI 做出特定决策的原因可能很困难。可视化调试工具显示树执行时每个节点的实时状态(`SUCCESS`、`FAILURE`、`RUNNING`)对于复杂项目几乎是必不可少的。
- 数据通信:节点如何共享信息?一个常见的解决方案是称为 黑板的共享数据上下文。`IsEnemyVisible?` 条件可能会从黑板读取玩家的位置,而 `DetectEnemy` 动作会将位置写入其中。
- 性能:每帧 tick 一个非常大、深的树可能会在计算上很昂贵。诸如事件驱动的 BT(其中树仅在发生相关事件时运行)之类的优化可以缓解此问题,但这会增加复杂性。
最佳实践:
- 保持浅层:优先选择较宽的树而不是较深的树。深度嵌套的逻辑可能难以理解。
- 拥抱模块化:为常见的任务(如导航或库存管理)构建小的、可重用的子树。
- 使用黑板:通过使用黑板来获取所有状态信息,将树的逻辑与代理的数据分离。
- 利用可视化编辑器:诸如内置于 Unreal Engine 中的工具或 Unity 的 Behavior Designer 之类的资产非常宝贵。它们允许快速原型设计、轻松可视化以及程序员和设计师之间更好的协作。
未来:行为树和机器学习
行为树不会与现代机器学习 (ML) 技术竞争;它们是互补的。混合方法通常是最强大的解决方案。
- 用于叶节点的 ML:BT 可以处理高级策略(例如,`DecideToAttack` 或 `DecideToDefend`),而经过训练的神经网络可以执行低级操作(例如,使用 ML 进行精确的、类似人类的瞄准的 `AimAndShoot` 动作节点)。
- 用于参数调整的 ML:可以使用强化学习来优化 BT 中的参数,例如特殊能力的冷却时间或撤退的生命值阈值。
这种混合模型结合了行为树的可预测、可控和设计师友好的结构与机器学习的细微、自适应能力。
结论:现代 AI 的重要工具
行为树代表了从有限状态机的严格限制向前迈出的重要一步。通过为决策提供模块化、可扩展且高度可读的框架,它们使开发人员和设计师能够创建在现代技术中看到的一些最复杂和最可信的 AI 行为。从轰动一时游戏中的狡猾敌人到未来工厂中的高效机器人,行为树提供了将简单代码转化为智能行动的逻辑骨干。
无论您是经验丰富的 AI 程序员、游戏设计师还是机器人工程师,掌握行为树都是对基础技能的投资。它是一种弥合简单逻辑和复杂智能之间差距的工具,它在自主系统世界中的重要性只会继续增长。