
上篇讲了行为树的四种基本节点——序列、选择、装饰、并行。单个节点很简单但组合起来能表达非常复杂的逻辑。问题是怎么组合就像搭积木积木块就那几种但搭出来的东西千差万别。行为树的设计模式就是前人总结出来的搭法——在机器人任务管理场景下哪些组合方式好用、哪些是坑。这些模式不是教科书上的理论而是游戏行业和机器人行业十几年实践沉淀下来的经验。面试时能说出几个具体的设计模式比泛泛地讲行为树比状态机好有说服力得多。一、反应式模式Reactive Sequence这是Nav2里用得最多的模式。核心思路每个tick都从根节点重新评估所有条件。如果某个条件变了比如突然检测到障碍物行为立刻跟着变。ReactiveSequence ├── [Condition] 路径是否有效 ├── [Condition] 前方是否畅通 └── [Action] 跟随路径每个tick都先检查路径是否有效、前方是否畅通。如果任一条件不满足跟随路径立刻停止。这比状态机里写如果检测到障碍物则转移到避障状态要直观得多。反应式模式的关键是条件节点放在序列节点的前面动作节点放在最后。这样每个tick都先检查条件再决定是否执行动作。二、带记忆的序列Sequence with Memory普通的序列节点每次tick都从第一个子节点开始。如果第一个子节点返回Running还在执行中下次tick还是从它开始——但如果第一个子节点已经返回Success了下次tick还是会重新执行它。有时候我们不希望这样。比如打开门→走过去→关上门这个序列打开门成功后下一个tick应该直接从走过去开始不需要再检查门是否打开。带记忆的序列节点SequenceStar会记住上次执行到哪个子节点。如果子节点A返回Success了下次tick直接从子节点B开始不回头。SequenceStar带记忆 ├── [Action] 打开门 → Success记住 ├── [Action] 走过去 → Running下次从这里继续 └── [Action] 关上门这个模式适合步骤之间有严格顺序、且前面步骤完成后不需要重复验证的场景。三、优先级选择Priority Selection选择节点的子节点按优先级从高到低排列。高优先级的行为先尝试成功了就不执行后面的。Fallback优先级从高到低 ├── [Sequence] 紧急避障 │ ├── [Condition] 前方距离 0.3m │ └── [Action] 急停 ├── [Sequence] 减速绕行 │ ├── [Condition] 前方距离 0.8m │ └── [Action] 减速转向 ├── [Sequence] 正常导航 │ ├── [Condition] 目标可达 │ └── [Action] 跟随路径 └── [Action] 原地等待紧急避障优先级最高——只要前方距离小于0.3m不管在做什么都会立刻停下来。距离在0.3-0.8m之间减速绕行。正常情况就跟随路径。所有条件都不满足就原地等待。这种模式在机器人安全控制中特别重要。安全相关的行为永远放在最高优先级。四、并行装饰模式Parallel Decorator有些任务需要同时做多件事。比如机器人在导航的同时要持续检测障碍物、上报位置。Parallel (success_on_all) ├── [Sequence] 主任务导航 │ ├── ComputePath │ └── FollowPath ├── [Decorator: Repeat] 持续检测障碍物 │ └── [Action] 扫描环境 └── [Decorator: Repeat] 持续上报位置 │ └── [Action] 发布状态主任务完成后并行节点结束。但检测障碍物和上报位置会一直跑通过Repeat装饰器循环执行。这种模式要注意并行节点的子节点共享tick周期。如果某个子节点执行时间太长会拖慢整个tick。工程上有个小技巧并行节点里的持续任务比如检测障碍物可以用异步方式执行——动作节点内部启动一个后台线程tick时只检查结果不阻塞执行。这样不会拖慢主任务的节奏。五、强制成功模式Force Success有时候某个动作失败了但你不想让整个序列停下来。比如上报位置这个动作失败了也不应该影响主流程。用Inverter装饰器可以反转结果但我们要的是不管成功失败都继续。正确的做法是用ForceSuccess装饰器Sequence ├── [Action] 执行主任务 ├── [ForceSuccess] 上报位置 │ └── [Action] 发布状态 └── [Action] 下一步不管发布状态返回什么ForceSuccess都返回Success序列继续往下走。六、恢复模式Recovery Pattern导航失败时怎么办恢复模式用选择节点来组织一系列恢复策略从简单到复杂依次尝试。Fallback ├── [Sequence] 正常导航 │ ├── ComputePath │ └── FollowPath └── [Sequence] 恢复策略 ├── [Action] ClearCostmap清除代价地图 ├── [Action] Spin原地旋转扫描 ├── [Action] BackUp后退 └── [Action] Wait等待几秒后重试正常导航失败了先清除代价地图上的鬼影。还不行就原地旋转重新扫描环境。再不行就后退脱离困境。最后就是等一等再试。Nav2的默认行为树里就有这个恢复模式。恢复策略的顺序和种类可以根据实际场景调整。七、子树复用模式Subtree Reuse大型项目中行为树会变得很庞大。很多行为在不同场景下会重复出现——比如导航到某个位置这个行为在送餐、巡检、回充等多个任务里都会用到。BehaviorTree.CPP支持把一段行为定义成子树SubTree然后在不同地方引用BehaviorTree IDNavigateToPose Sequence ComputePath goal{goal} path{path}/ FollowPath path{path}/ /Sequence /BehaviorTree BehaviorTree IDDeliverFood Sequence SubTree IDNavigateToPose goal{table_pos}/ Action IDReleaseFood/ SubTree IDNavigateToPose goal{kitchen_pos}/ /Sequence /BehaviorTree子树复用让行为树像函数一样可以模块化组织。每个子树有自己的输入端口goal和输出端口通过黑板传递数据。八、面试高频追问Q反应式序列和普通序列的区别是什么A反应式序列每个tick都从头评估所有条件。普通序列如果某个子节点返回Success下次tick还会重新执行它。反应式序列更适合条件可能随时变化的场景比如导航中障碍物随时出现。Q行为树怎么防止震荡——两个行为来回切换A加迟滞hysteresis。比如避障条件的触发距离设0.3m恢复距离设0.5m。距离小于0.3m触发避障大于0.5m才恢复正常。中间区域保持当前状态。Q行为树的调试方法有哪些ABehaviorTree.CPP库提供了Groot可视化工具可以实时查看行为树的执行状态每个节点当前是Running/Success/Failure。也可以用日志记录每个tick的节点执行路径。调试时最关键的是看哪个节点在什么时候返回了什么状态。Q行为树怎么处理长时间运行的任务A长时间运行的任务放在动作节点里返回Running状态。行为树每个tick都会检查这个节点的状态。动作节点内部可以用异步线程或者ROS2的Action服务端来实现不阻塞行为树的执行。设计模式是行为树从理论到实战的桥梁。掌握这几个常用模式面试时聊到任务管理就不会空谈了。下一篇我们来对比行为树和状态机——为什么行业在从状态机迁移到行为树。行为树设计模式是实战经验的总结。反应式序列、优先级选择、恢复模式这几个pattern在机器人项目中反复出现。上一篇第271篇 行为树基础下一篇对比行为树和状态机聊聊行业迁移的原因。如果这篇文章对你有帮助欢迎点赞支持一下你的鼓励是我持续更新的动力