(为免误导,特免责声明如下:本文所有内容,只是基于个人当前理解和实际做法,后面亦会有更正和修订,但任何版本都不免有个人能力有限、理解有误或者工作环境不同的状况,故文中内容仅供参考。任何人都可以借鉴或者直接使用代码片段,但对任何直接引用或者借鉴产生的技术问题等后果,作者不承担任何责任。)
1 什么是行为树BehaviorTree
BT Tree是有限状态机的升级,跟状态机一样基本也是根据条件,激活某个状态,执行相应的任务。
不同的是它采用树状结构来设置自动化流程,行为树的结构可以用xml文件配置,即在编译后方便的调整自动体的行为模式。
BT Tree目前广泛应用于一些自动控制的领域甚至是游戏的NPC,Nav2的导航也大量使用BT Tree。
参考链接:https://www.behaviortree.dev/
该维护团体还开发了Groot2,是一个GUI,可用于设计BT Tree的结构、实时监控系统运行状态和记录日志。
2 行为树结构
基本概念
-
tick
类似于信号(滴答、令牌、打勾),由树的根节点发出,通过树结构一直传到叶节点(leaf node)。传到哪个节点就执行哪个节点(TreeNode)。 -
节点状态
任何节点(TreeNode)获得tick后,都执行相应的操作(回调函数)完成相应的任务后返回的状态,只能是:- SUCCESS 成功
- FAILURE 失败
- RUNNING 还在运行
-
子节点
如果节点(TreeNode)有一个或多个子节点,则由此节点的类型,决定如何把tick传到子节点执行。比如
这个节点是顺序节点(sequence node 是最简单的控制节点)则它的子节点按先后顺序依次获得tick,依次执行,结束以后返回,如果所有页节点都返回成功,则它也将状态从running改为success
查看动画示意 -
叶节点
最底层没有子节点的就是叶节点,叶节点完成实际的任务,比如与系统其他部分沟通交互。比如action节点就是最普通的叶节点。
3节点类型
- 所有节点都继承自:TreeNode。
- ControlNode 控制节点,有1个或多个子节点。
- DecorateNode 修饰节点,只有一个子节点,它可以修改子节点的结果(比如取反)或者让子节点运行多次等。
- 条件节点(ConditionNode)和行为节点(ActionNode)都是页节点,不能再有子节点
- ConditionNode不能返回runnig,不能改变系统,(就是做条件判断,返回真假,真就是成功,假就是失败)
- ActionNode主要用来完成真正的任务,又可以进一步分为同步ActionNode和异步ActionNode。同步节点会阻塞进程,直到任务结束返回成功或者失败,而异步节点可以先返回running,需要多次tick执行直到有真正的结果返回。
目前系统可用的节点有:
(1)SequenceNode顺序控制节点
顺序控制节点:按顺序tick子节点,前一个子节点返回成功,则tick下一个子节点,所有的成功则返回成功。如果中间某个子节点返回失败,则不需要tick其它子节点,直接返回失败。
除了普通的Sequence,还有 SequenceWithMemory 和 ReactiveSequence
他们的共同特点有:
- 在tick第一个子节点前,先把状态改为running
- 如果一个子节点返回成功,tick下一个子节点
- 如果最后一个子节点也返回成功,则整个顺序节点返回成功
他们的区别在于有子节点返回失败(failure)或运行中(running)是:
类型 | 子节点返回失败 failure | 子节点返回运行中running |
---|---|---|
Sequence | 重新开始 | 再次tick |
ReactiveSequence | 重新开始 | 重新开始 |
SequenceWithMemory | 再次tick | 再次tick |
重新开始:意味着下次被tick的时候会从第一个子节点开始tick。
再次tick:当这个sequence再次被tick的时候,直接tick上次返回失败或者在running的子节点,而前面已经返回成功的子节点不再被tick。
举例
-
sequence
这是游戏里面一个狙击手的动作,前两个是条件节点 condition node,任何一个子节点,不管是前面的条件判断还是后面的动作节点Action node失败,下一次都是从第一个子节点开始,但如果是action node返回进行中running,下次就不从第一个子节点开始了(注意应该是异步action,同步的话只有等到成功或者失败才会返回),比如现在正在瞄准中,下次进来就不再做前面的条件判断了。 -
ReactiveSequence
ApproachEnemy是异步动作节点,会返回RUNNING。由于是ReactiveSequence,所以子节点isEnemyVisible 每次进来都会进行条件判断,如果某次判断为失败 (i,e, “FAILURE”), 则ApproachEnemy会被停止。 -
SequenceWithMemory
这种控制节点用于,当某个子节点运行过并返回成功后,以后不想再运行该子节点的情况。比如
这是一个巡逻机器人,必须每个地点巡逻一次,如果只是B位置失败了,下次就不用再tick巡逻A这个action了。
但是,电池是否正常(isBatteryOK )这种节点就必须每次都检查,所以它的父节点应该是个ReactiveSequence.
(2)Fallbacks回退控制节点
这类节点在其他的架构下可能被称为选择性控制或者优先控制等,目的是尝试不同的策略,直到找到一种可行的,(有某个子节点返回成功)。目前可用的有2种:
- Fallback
- ReactiveFallback
他们的相同点是:- 在tick第一个子节点前,先把状态改为running
- 如果一个子节点返回失败,则tick下一个子节点
- 如果所有的子节点都返回失败,则返回失败,并停止(halt)所有子节点。
- 如果某一个子节点返回成功,则整个节点停止下来返回成功,并停止(halt)所有子节点。
不同的是:
类型 | 子节点返回运行中running |
---|---|
Fallback | 再次tick |
ReactiveFallback | 重新开始 |
“重新开始” 是指从第一个子节点开始整个fallback重新启动直接点.
“再次tick” 是指下次这个控制节点被tick时,相同的子节点会被tick.而这个子节点前面的兄弟节点(那些返回失败的子节点)将不会被tick.
举例
- Fallback
- ReactiveFallback
这种控制节点用在我们希望中断任何可能在进行的异步子节点,以检测前面的条件判断子节点是否发生了状态变化,比如从上次的失败变为可以成功了.
上面这个例子,是一个角色(游戏里的NPC)需要休息最多8个小时,如果前面的条件判断子节点(是否休息好了)返回成功,则直接返回成功,终止正在进行中(running)的异步子节点(睡觉)。
(3)Decorators修饰器
修饰器节点至少有一个子节点,修饰器决定了它的子节点是否,何时被tick已经tick多少次。
-
Inverter 翻转器
tick子节点一次,如果子节点返回成功则翻转为失败,如果子节点返回失败则返回成功,如果子节点返回运行中,也返回运行中。 -
ForceSuccess 强制成功
子节点返回running,也返回running,除此之外不论子节点返回成功还是失败,一律返回成功 -
ForceFailure 强制失败
子节点返回running,也返回running,除此之外不论子节点返回成功还是失败,一律返回失败 -
Repeat 重复
在获得一次tick的时候,重复tick它的子节点最多到N次,N由输入口:num_cycles设定。
如果每次tick子节点返回的都是成功,在N次后,返回成功。
如果中间某次子节点返回是失败,则不继续tick下去,直接返回失败。
如果子节点返回运行中,节点也返回运行中,在下一次再被tick的时候继续tick子节点,并不会增加重复次数。 -
RetryUntilSuccessful 重复到成功
只要子节点返回是失败,就重复Tick子节点最多到N次,N由输入口:num_attempts传入。
N次后如果还是失败就返回失败。
如果某一次子节点返回成功,则打断不再继续tick了,直接返回成功。
如果子节点返回运行中,节点也返回运行中,在下一次再被tick的时候继续tick子节点,并不会增加重复次数。 -
KeepRunningUntilFailure保持运行直到失败
这种节点总数返回失败(在子节点返回失败时)或者运行中(子节点返回成功或者运行中)。 -
Delay延迟
在过了一个指定的时间段(由输入端口:delay_msec传入)后Tick 它的子节点.
如果子节点返回运行中,它也返回运行中,将会在下一次被tick的时候tick它的子节点. 否则把子节点的返回状态向上返回。 -
RunOnce 运行一次
这种节点用于你只想让某个子节点运行一次的情况.
如果子节点是个异步的,会tick到它返回成功或失败。(没懂,是当同步处理?阻塞在这里?目前的理解是:还是继续tick,因为还是第一次执行没完成)
在第一次执行完后,你可以把输入端口:then_skip 设置为:
- TRUE (default),这个节点在将来会被跳过.
- FALSE, 同步返回子级返回的相同状态,永远。(不太理解,目前的理解是:不执行动作,而是直接返回子节点第一次执行的结果。) -
PreCondition预置条件
Cf. Introduction to the Scripting language -
SubTree子树
Cf. Compose behaviors using Subtrees. -
其他需要再c++理注册的装饰器
-
ConsumeQueue 消耗型队列
-
SimpleDecoratorNode 简单装饰器节点(自定义装饰器)