BehaviorTree之概念讲解(一)

3 篇文章 0 订阅

什么是BehaviorTree

行为树,顾名思义,就是一个以树为底层结构的,决定行为走向的一个工具。行为树一开始多用于游戏行业,但是随着机器人行业的发展,越来越多的机器人框架(如navigation2,pr2_robot)开始使用行为树来决定机器人的动作走向。没有行为树之前,这种动作/状态之间的切换大多使用的是FSM(有限状态机)。FSM在状态多起来的时候,很容易发生混乱,在做动作的时候还要兼顾状态的切换,增加程序员的心智负担。行为树在这种场景下应运而生!

行为树与状态机的差异

状态机

状态机的本质,就是维护了多个不同的状态,每个状态之间可以互相切换,状态本身可以做动作,可以做条件,也可以在做动作的过程中判断条件,改变状态。状态机最大的好处就是自由,状态的切换可以在动作中,也可以在外部切换,内部只做动作。
在这里插入图片描述

行为树

节点1就是树的root节点,外部每tick一次,就可以从root节点开始,依次遍历。行为树解决了状态机,状态一多层次就会不清晰,不好维护的痛点。行为树把动作和执行动作的逻辑本身隔离开来,让动作模块可以更加关注动作本身,不用分心考虑状态切换的问题。
在这里插入图片描述

行为树节点简介

行为树是一种有向树,由节点和连线构成。相互连接的一对节点分别为父节点和子节点,没有子节点的节点称为叶节点。常用的非叶节点有选择节点和序列节点;常用的叶节点有条件节点和动作节点。

选择节点(非叶)

选择节点按照自左向右的顺序计算每个子节点,一旦某个子节点返回了“成功”或“运行中”的状态,那么选择节点就会立刻将自身的状态相应地更改为“成功”或“运行中”,并不再执行后面的节点。

序列节点(非叶)

序列节点按照自左向右的顺序计算每个子节点,一旦某个子节点返回了“失败”或“运行中”的状态,那么序列节点就会立刻将自身的状态相应地更改为“失败”或“运行”中,并不再执行后面的节点。

装饰器节点(非叶)

InverterNode
  • 勾选孩子一次,如果孩子失败则返回 SUCCESS 或如果孩子成功则返回 FAILURE。
    如果子节点返回 RUNNING,则此节点也返回 RUNNING。
ForceSuccessNode
  • 如果子节点返回 RUNNING,则此节点也返回 RUNNING。
    否则,它总是返回 SUCCESS。
ForceFailureNode
  • 如果子节点返回 RUNNING,则此节点也返回 RUNNING。
    否则,它总是返回 FAILURE。
RepeatNode
  • 最多勾选孩子 N 次,其中 N 作为输入端口传递,只要孩子返回 SUCCESS。
    如果子进程返回 FAILURE,则中断循环,在这种情况下,也返回 FAILURE。
    如果子节点返回 RUNNING,则此节点也返回 RUNNING。
RetryNode
  • 最多勾选孩子 N 次,其中 N 作为输入端口传递,只要孩子返回 FAILURE。
    如果子进程返回 SUCCESS,则中断循环,在这种情况下,也返回 SUCCESS。
    如果子节点返回 RUNNING,则此节点也返回 RUNNING。

条件节点(叶节点)

条件节点,就是根据条件,返回SUCCESS或者FAILURE,结合非叶节点可以有效的打断树的运行,适合在逻辑复杂的场景下使用。

动作节点(叶节点)

动作节点,就是最终做动作的节点,具体的业务动作处理就在这里面做, 动作节点也分为同步节点和异步节点,异步节点又分为开线程做异步和通过协程实现异步。

大概节点类型及作用如下:
在这里插入图片描述

通过C语言实现简易行为树

先要确定数据结构,去以一个适当的方式排列节点。在选取一个适当的方式遍历整棵树。我选择的是孩子兄弟树,原因是它既有行为树本身的层次结构,又兼顾了二叉树的遍历方式,非常符合目前的需求。

孩子兄弟树

即每个节点都只能有一个孩子,但可以有多个兄弟。
在这里插入图片描述数据结构如下:

typedef bool (*NodeCallBack)(void);

typedef struct BTNode
{
	    const char* id;
	    NodeCallBack node_callback;
	    struct BTNode *firstchild,*rightsib;

} BTNode;

typedef struct BTTree
{
	    int leaf_len;            // 叶子结点个数(执行动作的节点)
	    int node_len;            // 树的节点树(不算头节点)
	    BTNode *root;            // 指向头节点的
    
} BTTree;

遍历方式

孩子兄弟树的一个优点,就是可以通过二叉树的前序遍历,实现树从左到右的完全遍历。代码形式也非常简单。

/* 
 * describe  : 前序便利
 * parameter : bt_node 指向根节点的指针
 *             id 查找到的节点ID
 *             pre_node 指向当前找到的节点
 * return    : 指向id符合的节点的指针
 */
static void ErgodicTree(BTNode* bt_node)
{
	  do {
	
		       if(bt_node == NULL) break;
		       if(GetEndLoopTarget()) break;
		       if(bt_node->node_callback != NULL) bt_node->node_callback();
		
		       ErgodicTree(bt_node->firstchild); 
		       ErgodicTree(bt_node->rightsib);
	
	   } while(0);
}

// describe  : 前序遍历,执行所有叶子结点的函数指针指向
// parameter : tree 树的地址
// return    : None
void DisBehaviorTree(const BTTree* tree)
{
	   if(tree == NULL)
	   {
		       DEBUG("DisBehaviorTree error ");
		       return;
	   }
	   
	   SetEndLoopTarget(false);
	   
	   ErgodicTree(tree->root);
}

用法展示

在这里插入图片描述在这里插入图片描述

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值