本文讲述如何构建一个简单树来体验以下行书树的使用。
一 tick的理解
假设要按顺序做2件事,发现目标和抓住目标(先发现了才能抓住),其规划如下,
PS:上面2层可以看作是战略 (大体的规划),最底层则可以看作是战术(具体如何做)
tick可以看做是一个触发信号,当一个tick信号发出,从Sequence开始往下走,先走到DeteckObject,然后走到ObjectRecognition Component,执行完毕后回到Sequence,Sequence会根据DeteckObject的执行结果再继续执行。
如果DeteckObject执行成功,就会继续走到GraspObject,最后走到Manipulation Component,执行完毕后回到Sequence。如果DeteckObject执行失败,那么就认为这个Sequence执行结束。
需要注意的是以上操作只需要一个tick信号,node之间会根据情况自动tick。
tick更形象的例子应该是牛顿摆,
二 树的规划
树使用xml格式进行战略规划,开门->进屋->关门,
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Sequence name="root_sequence">
<OpenDoor name="open_door_of_house"/>
<EnterHouse name="enter_house"/>
<CloseDoor name="close_door_of_house"/>
</Sequence>
</BehaviorTree>
</root>
xml格式有以下几个要点,
- root这个tag是必须的,而且它需要有main_tree_to_execute的属性,表示执行哪颗树
- root的子元素必须有BehaviorTree,BehaviorTree必须有ID属性
- 如果root拥有多个BehaviorTree,那么BehaviorTree的ID的属性值必须是不同的
- 如果root只有一个BehaviorTree,那么main_tree_to_execute属性可有可无
- BehaviorTree的子元素就是树节点(tree nodes)
这里的树节点类型是Sequence,其包含3个children。
Sequence树节点的特点是它的children必须全部返回SUCCESS才认为执行成功,有一个child返回FAILURE就导致这个树节点执行失败。
- Before ticking the first child, the node status becomes RUNNING.
- If a child returns SUCCESS, it ticks the next child.
- If the last child returns SUCCESS too, all the children are halted and the sequence returns SUCCESS.
Sequence节点又分为三种,
- Sequence (同名)
- SequenceStar
- ReactiveSequence
它们的区别如下,
- Restart的意思是从第一个child开始重新运行整个树
- Tick again的意思是下一次收到tick信号,只执行当前运行失败的child,之前运行成功的child不会再运行
三 代码
强调一点,xml负责规划执行逻辑(战略),具体的执行过程则由用户提供(战术)。
第一种方式
首先需要创建BehaviorTreeFactory的对象,用于注册node,
// We use the BehaviorTreeFactory to register our custom nodes
BT::BehaviorTreeFactory factory;
然后注册用户自定义的战术,即对开门,进屋和关门提供自定义的操作,
factory.registerSimpleAction("OpenDoor", std::bind(OpenDoorFunc));
factory.registerSimpleAction("EnterHouse", std::bind(EnterHouseFunc));
factory.registerSimpleAction("CloseDoor", std::bind(CloseDoorFunc));
执行时会根据名称(第一个参数)来找到这些战术,所以名称必须和xml里child的tag名字一致。
接着是加载战略,
auto tree = factory.createTreeFromText(xml_text);
这里是把xml以字符串的形式写在代码里,如果有xml文件,则使用另外一个接口,
auto tree = factory.createTreeFromFile(xml_file);
最后是由root来tick一下,
tree.tickRoot();
整体代码如下,
#include "behaviortree_cpp_v3/bt_factory.h"
static const char* xml_text = R"(
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Sequence name="root_sequence">
<OpenDoor name="open_door_of_house"/>
<EnterHouse name="enter_house"/>
<CloseDoor name="close_door_of_house"/>
</Sequence>
</BehaviorTree>
</root>
)";
BT::NodeStatus OpenDoorFunc()
{
std::cout << "Door is opened" << std::endl;
return BT::NodeStatus::SUCCESS;
}
BT::NodeStatus EnterHouseFunc()
{
std::cout << "Enter house" << std::endl;
return BT::NodeStatus::SUCCESS;
}
BT::NodeStatus CloseDoorFunc()
{
std::cout << "Close door" << std::endl;
return BT::NodeStatus::SUCCESS;
}
int main()
{
// We use the BehaviorTreeFactory to register our custom nodes
BT::BehaviorTreeFactory factory;
// Registering a SimpleActionNode using a function pointer.
// you may also use C++11 lambdas instead of std::bind
factory.registerSimpleAction("OpenDoor", std::bind(OpenDoorFunc));
factory.registerSimpleAction("EnterHouse", std::bind(EnterHouseFunc));
factory.registerSimpleAction("CloseDoor", std::bind(CloseDoorFunc));
auto tree = factory.createTreeFromText(xml_text);
// To "execute" a Tree you need to "tick" it.
// The tick is propagated to the children based on the logic of the tree.
// In this case, the entire sequence is executed, because all the children
// of the Sequence return SUCCESS.
tree.tickRoot();
return 0;
}
最后执行结果如下,
第二种方式(推荐)
首先需要创建BehaviorTreeFactory的对象,用于注册node,
// We use the BehaviorTreeFactory to register our custom nodes
BT::BehaviorTreeFactory factory;
对于Sequenc下的三个child (即开门,进屋和关门),我们使用类的方式来实现其操作,先是开门,
class OpenDoorImpl : public BT::SyncActionNode
{
public:
OpenDoorImpl(const std::string& name) :
BT::SyncActionNode(name, {})
{
}
// You must override the virtual function tick()
BT::NodeStatus tick() override
{
std::cout << "Door is opened" << std::endl;
return BT::NodeStatus::SUCCESS;
}
};
这里以BT::SyncActionNode作为基类,基类里提供了tick()函数,这个必须由用户自己去实现(战术),override关键字表示这个函数是来自于基类的虚函数,必须要实现。
同理,实现进屋和关门的操作,
class EnterHouseImpl : public BT::SyncActionNode
{
public:
EnterHouseImpl(const std::string& name) :
BT::SyncActionNode(name, {})
{
}
// You must override the virtual function tick()
BT::NodeStatus tick() override
{
std::cout << "Enter house" << std::endl;
return BT::NodeStatus::SUCCESS;
}
};
class CloseDoorImpl : public BT::SyncActionNode
{
public:
CloseDoorImpl(const std::string& name) :
BT::SyncActionNode(name, {})
{
}
// You must override the virtual function tick()
BT::NodeStatus tick() override
{
std::cout << "Close door" << std::endl;
return BT::NodeStatus::SUCCESS;
}
};
有了节点后,在代码里需要注册一下,注意参数要和xml里的tag名称一样,否则找不到,
factory.registerNodeType<OpenDoorImpl>("OpenDoor");
factory.registerNodeType<EnterHouseImpl>("EnterHouse");
factory.registerNodeType<CloseDoorImpl>("CloseDoor");
接着是加载战略,
auto tree = factory.createTreeFromText(xml_text);
这里是把xml以字符串的形式写在代码里,如果有xml文件,则使用另外一个接口,
auto tree = factory.createTreeFromFile(xml_file);
最后是由root来tick一下,
tree.tickRoot();
整体代码如下,
#include "behaviortree_cpp_v3/bt_factory.h"
static const char* xml_text = R"(
<root main_tree_to_execute = "MainTree" >
<BehaviorTree ID="MainTree">
<Sequence name="root_sequence">
<OpenDoor name="open_door_of_house"/>
<EnterHouse name="enter_house"/>
<CloseDoor name="close_door_of_house"/>
</Sequence>
</BehaviorTree>
</root>
)";
class OpenDoorImpl : public BT::SyncActionNode
{
public:
OpenDoorImpl(const std::string& name) :
BT::SyncActionNode(name, {})
{
}
// You must override the virtual function tick()
BT::NodeStatus tick() override
{
std::cout << "Door is opened" << std::endl;
return BT::NodeStatus::SUCCESS;
}
};
class EnterHouseImpl : public BT::SyncActionNode
{
public:
EnterHouseImpl(const std::string& name) :
BT::SyncActionNode(name, {})
{
}
// You must override the virtual function tick()
BT::NodeStatus tick() override
{
std::cout << "Enter house" << std::endl;
return BT::NodeStatus::SUCCESS;
}
};
class CloseDoorImpl : public BT::SyncActionNode
{
public:
CloseDoorImpl(const std::string& name) :
BT::SyncActionNode(name, {})
{
}
// You must override the virtual function tick()
BT::NodeStatus tick() override
{
std::cout << "Close door" << std::endl;
return BT::NodeStatus::SUCCESS;
}
};
int main()
{
// We use the BehaviorTreeFactory to register our custom nodes
BT::BehaviorTreeFactory factory;
factory.registerNodeType<OpenDoorImpl>("OpenDoor");
factory.registerNodeType<EnterHouseImpl>("EnterHouse");
factory.registerNodeType<CloseDoorImpl>("CloseDoor");
auto tree = factory.createTreeFromText(xml_text);
// To "execute" a Tree you need to "tick" it.
// The tick is propagated to the children based on the logic of the tree.
// In this case, the entire sequence is executed, because all the children
// of the Sequence return SUCCESS.
tree.tickRoot();
return 0;
}
运行结果,
小结
为什么推荐第二种,当树变的复杂时,就会体现第二种的优势了。
四 总结
本文通过构建一个简单的行为树来体验下如何使用BehaviorTree.CPP,为后续的学习打好基础。
暂无评论内容