(为免误导,特免责声明如下:本文所有内容,只是基于个人当前理解和实际做法,后面亦会有更正和修订,但任何版本都不免有个人能力有限、理解有误或者工作环境不同的状况,故文中内容仅供参考。任何人都可以借鉴或者直接使用代码片段,但对任何直接引用或者借鉴产生的技术问题等后果,作者不承担任何责任。)
本文简介
介绍节点如何与系统打交道。因为节点是由树工厂根据xml创建的节点实例,如何传参给这些实例对象,把系统当前的一些状态告诉节点,是一个非常重要的问题。
引入
到目前为止,每个例子我们都被要求提供一个这样式的构造函数:
MyCustomNode(const std::string& name, const NodeConfig& config);
很多情况下,我们需要把额外的参数,变量,指针,引用等传递到节点类的构造函数。
注意 有人采用黑板来这么做,不要!
接下来我们下面就用变量这个词。
理论上说,是可以用输入端口来传递这些变量,但这不是正确的方法,因为下面的情形:
- 变量是在部署的时候(当构建树的时候)才被知道的.
- 变量在运行时不会改变.
- 变量不需要从XML设置
上面的情况下,就极其不推荐使用端口或者黑板。
推荐做法
推荐的做法是在构造函数里面增加变量,看下下面这个定制的节点类:Action_A.
我们想传递2个额外的参数,可以是任意复杂对象,并不限定于内建类型。
// Action_A has a different constructor than the default one.
class Action_A: public SyncActionNode
{
public:
// additional arguments passed to the constructor
Action_A(const std::string& name, const NodeConfig& config,
int arg_int, std::string arg_str):
SyncActionNode(name, config),
_arg1(arg_int),
_arg2(arg_str) {}
// this example doesn't require any port
static PortsList providedPorts() { return {}; }
// tick() can access the private members
NodeStatus tick() override;
private:
int _arg1;
std::string _arg2;
};
注册节点和传递参数可以简单的做到:
BT::BehaviorTreeFactory factory;
factory.registerNodeType<Action_A>("Action_A", 42, "hello world");
// If you prefer to specify the template parameters
// factory.registerNodeType<Action_A, int, std::string>("Action_A", 42, "hello world");
使用一个初始化函数 “initialize”
如果,因为某种原因,你需要传递不同的值给一个节点的不同实例,可以考虑下面的模式:
class Action_B: public SyncActionNode
{
public:
// The constructor looks as usual.
Action_B(const std::string& name, const NodeConfig& config):
SyncActionNode(name, config) {}
// We want this method to be called ONCE and BEFORE the first tick()
void initialize(int arg_int, const std::string& arg_str)
{
_arg1 = arg_int;
_arg2 = arg_str;
}
// this example doesn't require any port
static PortsList providedPorts() { return {}; }
// tick() can access the private members
NodeStatus tick() override;
private:
int _arg1;
std::string _arg2;
};
注册和初始化Action_B有点不同:
BT::BehaviorTreeFactory factory;
// Register as usual, but we still need to initialize
factory.registerNodeType<Action_B>("Action_B");
// Create the whole tree. Instances of Action_B are not initialized yet
auto tree = factory.createTreeFromText(xml_text);
// visitor will initialize the instances of
auto visitor = [](TreeNode* node)
{
if (auto action_B_node = dynamic_cast<Action_B*>(node))
{
action_B_node->initialize(69, "interesting_value");
}
};
// Apply the visitor to ALL the nodes of the tree
tree.applyVisitor(visitor);
- 理解:
- visitor 是lambda函数
- tree.applyVisitor(visitor) 是用这个函去访问所有tree生成的节点,即如果根据xml生成的树里N个节点,则分别把这些节点对象的指针当做参数,调用visitor(pCurNodeObj额),调用N次。
- 每次调用,函数内会对节点对象进行类型转换(dynamic_cast),如果能转换成功,说明这个节点就是Action_B类的实例,则调用它的initialize方法,把参数传递进去,当然也可以再根据它的名称做不同的处理(xml里面可以指定node实例的名称)。