第 12.4.2 节

行为树(BehaviorTree.CPP)

作者:董佳辉

行为树(BehaviorTree.CPP)

对应 BehaviorTree.CPP 3.8 官方文档:About。

行为树(Behavior Tree,简称 BT)是一种组织机器人任务及其切换的方式。 BehaviorTree.CPP 则是用于实现行为树的 C++ 库; 本教程的程序使用 3.8.7 验证,概念资料按官方 3.8 系列文档学习,官方教程入口在这里。

已经学过 FSM 后,可以从一个问题开始理解它:当导航、充电、任务取消等需求不断增加时,怎样把决策逻辑组织得清楚,并复用已有能力?

本篇先说明行为树解决什么问题,以及它与 FSM 的关系。节点状态、tick 传播和四类节点将在下一篇“基本概念”集中介绍。


从一个简单的 FSM 开始

假设移动机器人只有等待目标、执行导航、到达目标三个阶段:

IDLE → 收到目标 → NAVIGATING → 导航成功 → ARRIVED
                     └────── 导航失败 → IDLE

用 C++ 风格伪代码表示:

enum class RobotState
{
    IDLE,
    NAVIGATING,
    ARRIVED
};

RobotState current_state = RobotState::IDLE;

void Update()
{
    switch (current_state)
    {
        case RobotState::IDLE:
            if (HasNavigationGoal())
            {
                StartNavigation();
                current_state = RobotState::NAVIGATING;
            }
            break;

        case RobotState::NAVIGATING:
            if (NavigationSucceeded())
            {
                StopRobot();
                current_state = RobotState::ARRIVED;
            }
            else if (NavigationFailed())
            {
                current_state = RobotState::IDLE;
            }
            // 尚未结束时保留当前状态,下次 Update() 再检查
            break;

        case RobotState::ARRIVED:
            // 到达后的处理按具体任务补充
            break;
    }
}

外部周期性调用 Update()。启动导航的接口快速返回,机器人移动期间,每次更新只检查当前结果,不在函数里一直等待。

这种任务用 FSM 很容易描述:状态明确,转换条件也少。


复杂任务需要统一组织决策逻辑

任务增加后,同一条规则可能影响多个状态:

新需求可能影响的任务需要决定什么
电量不足等待、导航、执行任务是否暂停当前任务,前往充电
紧急停止请求导航、充电、执行任务是否中止当前操作并停止
定位丢失导航、返回充电点是否暂停移动、尝试恢复定位
收到新目标或取消请求导航是否取消旧请求、切换任务
导航失败普通任务、返回充电点是否重试、放弃或采用其他处理

例如,低电量发生在导航期间,可能需要先取消导航,再前往充电;发生在等待期间,则没有正在执行的导航需要取消。

如果每个状态都各自检查电量、取消请求和定位,规则就容易分散。修改一条策略时,需要检查多个状态及其进入、退出逻辑。

FSM 可以通过公共事件处理、分层组织等方式管理这些需求。学习行为树,是为了掌握另一种组织复杂任务的工具:用有层次的任务结构表达条件、优先级和执行关系。

区分底层能力与任务决策

机器人系统可以先按职责分成两层:

任务决策层
    ├── 当前应该执行哪个任务?
    ├── 应该去普通目标还是充电点?
    └── 失败或条件变化后怎样处理?
            ↓ 调用
能力模块
    ├── Navigation       导航到指定目标
    ├── Localization     提供定位及其有效性
    ├── Battery          提供电量和充电信息
    ├── Sensors          提供环境数据
    └── Actuators        执行运动等指令

导航模块主要解决“给定目标,如何到达”;任务决策层决定“现在是否导航、去哪里、失败怎么办”。行为树适合组织这部分任务决策,具体功能仍由相应模块实现。


用层次结构表达机器人任务

先用一个概念结构表达任务优先级:

RobotBehavior
├── EmergencyHandling     有紧急停止请求时,停止机器人
├── EnergyManagement      电量低时,处理充电
├── ExecuteMission        有任务时,执行导航等操作
└── WaitForTask           其余情况下,等待任务

这张图描述的是预期策略,不是可以直接运行的行为树。实际哪些条件会重新检查、怎样选择分支、如何中止旧任务,需要由具体节点和执行规则实现。

任务可以继续逐层展开:

RobotBehavior
├── EmergencyHandling
├── EnergyManagement
│   ├── CheckBattery
│   └── RechargeTask
│       ├── NavigateToChargingStation
│       └── StartCharging
└── ExecuteMission
    ├── CheckGoal
    └── NavigateToGoal

阅读整体任务时,只看第一层就能知道主要职责; 研究充电流程时,再展开 RechargeTask。 小任务可以组成大任务,一整棵子树也可以成为更大任务的一部分。

这里仍只展示任务分解,具体节点类型和成功、失败时的执行规则后面再补充。


模块化、复用与组合怎样落到代码上

官方把节点比作积木:每个节点封装一种检查或操作,再把节点组合成任务。各个特点可以对应到具体的设计选择:

特点在移动机器人中的做法
模块化 Modularity电量检查、导航请求、停止操作分别封装
可复用性 Reusability普通目标导航和返回充电点复用同一种导航节点实现,配置不同目标
可组合性 Composability将检查、导航、充电组合成完整充电任务
关注点分离 Separation of Concerns导航模块负责到达目标,行为树负责决定何时导航和如何处理结果

例如 IsBatteryLow 只检查电量,NavigateToGoal 负责请求导航,StartCharging 负责启动充电。这样可以分别测试和替换各项能力,也能在不同任务中重新组合。

可复用的是节点实现或子树设计,不意味着多处任务必须共享同一个正在运行的节点对象。具体怎样创建和配置实例,在后面的编程示例中学习。

行为树的树形图也有助于阅读工作流程。 不过,完整理解仍要结合每种节点的执行语义;仅凭连线无法判断所有运行细节。


反应性来自持续驱动与重新判断

机器人环境会在任务执行期间变化。例如:

开始导航:电量和定位正常,存在目标
    ↓
导航过程中:收到取消请求
    ↓
重新判断:取消当前导航,转入后续处理

反应性(Reactive)指系统能够在任务尚未完成时,根据变化调整行为。

前面学习的非阻塞 FSM,通过周期执行 Update() 检查事件和任务进度。

行为树通过外部持续发起 tick,按照树的规则推进任务:

执行方式一次更新主要做什么
FSM 的 Update()根据当前状态处理事件,决定是否转移
行为树的一次 tick从根节点进入,按各节点的规则选择并执行当前需要的节点

对于持续任务,每次调用应及时交还控制权,方便后续继续决策。 导航系统可以在两次更新之间继续工作,决策层不必堵在一次调用中等待机器人到达。

行为树能否及时响应条件变化,取决于驱动频率、哪些条件被重新 tick,以及动作是否支持中止。 把条件节点放进树里,不代表它会自动持续监控;具体的响应式规则将在后续示例中学习。


状态机FSM与行为树BT可以配合使用

选择组织方式时,可以先看问题的结构:

场景常见做法
设备具有明确阶段,如关闭、启动、运行、故障使用 FSM 表达阶段及其转换
任务包含多种条件、优先级和可复用子流程使用行为树组织任务
上层需要组合任务,某个底层模块内部又有明确阶段上层使用行为树,模块内部保留 FSM

例如:

RobotBehavior              行为树:组织整体任务
└── NavigateToGoal         动作节点:调用导航模块
    └── NavigationModule   模块内部:用 FSM 管理运行阶段

这张图表达软件调用与组织关系。NavigationModule 是外部模块,不是动作节点下面的行为树子节点。

两种工具都能组织任务决策,适用范围也有重叠。 现有 FSM 结构清晰时,可以继续使用;任务需要更多层次和组合时,再考虑引入行为树。


本教程的学习顺序

本篇作为导读,后面按六个完整主题组织主线。阶段 1 集中建立概念,阶段 2 从最小程序逐步增加功能,统一使用 BehaviorTree.CPP 3.8.7、C++20 和 VSCode CMake Tools。

文档主要内容所在阶段
基本概念节点、状态、tick 传播和四类节点阶段 1
控制与装饰规则顺序、备选方案、重试、重复和超时阶段 1
第一个完整程序先衔接概念,再从最小 demo 逐步增加条件和端口阶段 1 收尾,进入阶段 2
节点之间传数据输入输出、自定义类型和外部 Blackboard阶段 2
持续任务与中止持续导航、多次 tick、响应式检查和取消阶段 2
组合实际任务子树映射、多 XML 文件和完整任务的成功、失败路径阶段 2 收尾

实践按自己的完成情况推进:先运行当前版本,看到输出并理解执行过程,再增加下一步功能。各篇预留了 VSCode 截图位置,完整程序使用模拟移动机器人数据。