(为免误导,特免责声明如下:本文所有内容,只是基于个人当前理解和实际做法,后面亦会有更正和修订,但任何版本都不免有个人能力有限、理解有误或者工作环境不同的状况,故文中内容仅供参考。任何人都可以借鉴或者直接使用代码片段,但对任何直接引用或者借鉴产生的技术问题等后果,作者不承担任何责任。)
1 BT tree结构
一个顺序控制节点下面4个子节点:第一个是条件判断节点,后三个动作节点
就是会判断电池状态,如果正常,依次执行打开夹具,靠近物体,闭合夹具的动作,如果某一子节点返回失败,则直接返回失败,不再继续。
2 创建自己的动作节点
- 类继承
// Example of custom SyncActionNode (synchronous action)
// without ports.
class ApproachObject : public BT::SyncActionNode
{
public:
ApproachObject(const std::string& name) :
BT::SyncActionNode(name, {})
{}
// You must override the virtual function tick()
BT::NodeStatus tick() override
{
std::cout << "ApproachObject: " << this->name() << std::endl;
return BT::NodeStatus::SUCCESS;
}
};
上面代码创建了一个自己的动作节点类,继承自同步动作节点。必须要重载的虚函数是tick(),本例子只是输出对象的名称。其返回值必须是RUNNING, SUCCESS或FAILURE三者之一。
- 行为绑定简单动作类
除了这种从动作节点继承来生成自己的动作节点类的方法,还有简单的生成动作节点的方法,就是dependency injection(依赖注入?依赖绑定,就是用具有一下结构的函数去绑定到BT自己的类上(它的tick函数被替代了)
BT::NodeStatus myFunction(BT::TreeNode& self)
例如:
using namespace BT;
// Simple function that return a NodeStatus
BT::NodeStatus CheckBattery()
{
std::cout << "[ Battery: OK ]" << std::endl;
return BT::NodeStatus::SUCCESS;
}
// We want to wrap into an ActionNode the methods open() and close()
class GripperInterface
{
public:
GripperInterface(): _open(true) {}
NodeStatus open()
{
_open = true;
std::cout << "GripperInterface::open" << std::endl;
return NodeStatus::SUCCESS;
}
NodeStatus close()
{
std::cout << "GripperInterface::close" << std::endl;
_open = false;
return NodeStatus::SUCCESS;
}
private:
bool _open; // shared information
};
可以用
CheckBattery()
GripperInterface::open()
GripperInterface::close()
任意一个函数创建简单动作节点SimpleActionNode。
3实例
如果行为树的XML文件my_tree.xml为如下(与页面头部的图片一致):
<root BTCPP_format="4" >
<BehaviorTree ID="MainTree">
<Sequence name="root_sequence">
<CheckBattery name="check_battery"/>
<OpenGripper name="open_gripper"/>
<ApproachObject name="approach_object"/>
<CloseGripper name="close_gripper"/>
</Sequence>
</BehaviorTree>
</root>
在代码中如果创建与xml文件定义的BT树结构一样的对象,并tick运行呢:
2.1) 注册类
首先必须把自定义的节点类注册到行为树工厂(BehaviorTreeFactory ),这样它读取xml文件后,才知道如何根据xml文件的名称去创建对应的类的对象。
从xml文件可以看出需要注册自己的类有CheckBattery ,OpenGripper ,ApproachObject 和CloseGripper 。
根据第2部分,注册方法有两种:
-
第一种是类继承的ApproachObject 类,其注册方法是:factory.registerNodeType(“ApproachObject”);
<>里面的C++的类名,“”里面的是这个类的标签,与xml文件里一致,这样读到xml文件的这个标签,它才知道去创建哪个类的对象。 -
第二种是简单action类的注册,通过依赖注入的方式,绑定tick函数生成不同的简单动作类,如:
factory.registerSimpleCondition(“CheckBattery”, & { return CheckBattery(); });
第一个参数“”里的是类标签,与xml文件里的一致。后面的lambda函数会绑定作为这个简单动作类的tick回调。2.2) 读取树结构文件创建树对象
auto tree = factory.createTreeFromFile(“./my_tree.xml”);2.3) 运行该树对象
tree.tickWhileRunning();
#include "behaviortree_cpp/bt_factory.h"
// file that contains the custom nodes definitions
#include "dummy_nodes.h"
using namespace DummyNodes;
int main()
{
// We use the BehaviorTreeFactory to register our custom nodes
BehaviorTreeFactory factory;
// The recommended way to create a Node is through inheritance.
factory.registerNodeType<ApproachObject>("ApproachObject");
// Registering a SimpleActionNode using a function pointer.
// You can use C++11 lambdas or std::bind
factory.registerSimpleCondition("CheckBattery", [&](TreeNode&) { return CheckBattery(); });
//You can also create SimpleActionNodes using methods of a class
GripperInterface gripper;
factory.registerSimpleAction("OpenGripper", [&](TreeNode&){ return gripper.open(); } );
factory.registerSimpleAction("CloseGripper", [&](TreeNode&){ return gripper.close(); } );
// Trees are created at deployment-time (i.e. at run-time, but only
// once at the beginning).
// IMPORTANT: when the object "tree" goes out of scope, all the
// TreeNodes are destroyed
auto tree = factory.createTreeFromFile("./my_tree.xml");
// 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.tickWhileRunning();
return 0;
}
/* Expected output:
*
[ Battery: OK ]
GripperInterface::open
ApproachObject: approach_object
GripperInterface::close
*/