BehaviorTree.CPP教程二:基本端口

(翻译)BehaviorTree.CPP教程二:基本端口:https://www.behaviortree.dev/tutorial_02_basic_ports/

 

 

输入和输出端口

正如我们前面所解释的,自定义 TreeNodes 可用于执行任意简单或复杂的软件。他们的目标是提供一个具有更高抽象层次的接口

因此,它们在概念上与函数没有不同。

与函数类似,我们通常希望:

  • 将参数/参数传递给节点(输入)
  • 从节点(输出)获取某种信息。
  • 一个节点的输出可以是另一个节点的输入。

BehaviorTree.CPP提供了一种通过端口的数据的基本机制,它简单易用,而且类型又灵活又安全。

 

输入端口

一个有效的输入可以是:

  • 可以由节点解析的静态字符串,或
  • 指向 Blackboard 入口的“指针”,由一个 key 所标示。

一个 blackboard 是由树的所有节点共享的一个简单 key / value 存储

 Blackboard 的一个 entry  是一个 key / value 对。

输入端口可以读取 Blackboard 中的 entry ,而输出端口可以写入 entry 。

假设我们要创建一个名为 SaySomething 的 ActionNode ,它应该在 std::cout 上打印给定的字符串。

此类字符串将使用名为 message 的输入端口传递。

请考虑以下替代 XML 语法:

    <SaySomething name="first"    message="hello world" />
    <SaySomething name="second"   message="{greetings}" />

第一个节点中的属性消息表示:

"静态字符串'hello world'被传递到'SaySomething'的端口'message' "

消息是从XML文件中读取的,因此不能在运行时更改。

第二个节点的语法表示:

“读取 blackboard 上名为‘greetings’的 entry 的当前值”。

此值可以在运行时更改(而且可能会更改)。

ActionNode SaySomething 可以实现如下:

// SyncActionNode(同步动作)带有输入端口。
class SaySomething : public SyncActionNode
{
  public:
    //  如果您的节点有端口,则必须使用此构造函数署名
    SaySomething(const std::string& name, const NodeConfiguration& config)
      : SyncActionNode(name, config)
    { }

    // 定义这个静态方法是强制性的。
    static PortsList providedPorts()
    {
        // 此操作有一个称为“message”的输入端口。
        // 任何端口必须有一个名称。类型可选。
        return { InputPort<std::string>("message") };
    }

    // 像往常一样,您必须重写虚函数tick()
    NodeStatus tick() override
    {
        Optional<std::string> msg = getInput<std::string>("message");
        // 检查optional是否有效。如果没有,抛出它的错误
        if (!msg)
        {
            throw BT::RuntimeError("missing required input [message]: ", 
                                   msg.error() );
        }

        // 使用方法value()提取有效消息。
        std::cout << "Robot says: " << msg.value() << std::endl;
        return NodeStatus::SUCCESS;
    }
};

 或者,可以在一个简单的函数中实现相同的功能。这个函数以BT:TreeNode的实例作为输入,以便访问“message”输入端口:

// 返回节点状态的简单函数
BT::NodeStatus SaySomethingSimple(BT::TreeNode& self)
{
  Optional<std::string> msg = self.getInput<std::string>("message");
  // 检查optional是否有效。如果没有,抛出它的错误
  if (!msg)
  {
    throw BT::RuntimeError("missing required input [message]: ", msg.error());
  }

  // 使用方法value()提取有效消息。
  std::cout << "Robot says: " << msg.value() << std::endl;
  return NodeStatus::SUCCESS;
}

当一个自定义TreeNode有输入和/或输出端口时,这些端口必须在静态方法中声明:

    static MyCustomNode::PortsList providedPorts();

可以使用模板方法 messageTreeNode::getInput<T>(key) 读取来自端口的输入。

此方法可能由于多种原因而失败。它由用户来检查返回值的有效性并决定做什么:

  • 返回?NodeStatus::FAILURE
  • 抛出异常?
  • 使用其他默认值?
重点注意:

始终建议在tick()中调用getInput()方法,而不是在类的构造函数中。

c++代码不能对输入的性质做出任何假设,输入可以是静态的,也可以是动态的。动态输入可以在运行时更改,因此应该定期读取它。

 

 

输出端口

指向 blackboard 入口的输入端口只有在另一个节点已经在同一 entry 中写入了“something”时才有效。

ThinkWhatToSay 是使用输出端口将字符串写入 entry 的Node示例。

class ThinkWhatToSay : public SyncActionNode
{
  public:
    ThinkWhatToSay(const std::string& name, const NodeConfiguration& config)
      : SyncActionNode(name, config)
    {
    }

    static PortsList providedPorts()
    {
        return { OutputPort<std::string>("text") };
    }

    // 该操作将一个值写入端口“text”中。
    NodeStatus tick() override
    {
        // 每次tick()的输出都可能发生变化。这里我们保持简洁。
        setOutput("text", "The answer is 42" );
        return NodeStatus::SUCCESS;
    }
};

或者,出于调试目的,大多数时间,可以使用称为 SetBlackboard 的内置操作将静态值写入条目。

 <SetBlackboard   output_key="the_answer" value="The answer is 42" />

 

 

一个完整的示例

在此示例中,将执行 5 个操作的序列:

  • 操作 1 和 4 读取来自静态字符串的输入消息。

  • 操作 3 和 5 读取黑板中名为 the_answer 的条目的输入消息。

  • 动作2在黑板的条目中写上一些叫做 the_answer 的东西。

saything2是一个SimpleActionNode(简单行为节点)。

<root main_tree_to_execute = "MainTree" >
    <BehaviorTree ID="MainTree">
       <Sequence name="root_sequence">
           <SaySomething     message="start thinking..." />
           <ThinkWhatToSay   text="{the_answer}"/>
           <SaySomething     message="{the_answer}" />
           <SaySomething2    message="SaySomething2 works too..." />
           <SaySomething2    message="{the_answer}" />
       </Sequence>
    </BehaviorTree>
</root>

C++代码:

#include "behaviortree_cpp_v3/bt_factory.h"

// 包含自定义节点定义的文件
#include "dummy_nodes.h"

int main()
{
    using namespace DummyNodes;

    BehaviorTreeFactory factory;

    factory.registerNodeType<SaySomething>("SaySomething");
    factory.registerNodeType<ThinkWhatToSay>("ThinkWhatToSay");

    // SimpleActionNodes不能定义自己的方法providedPorts()。
    // 如果我们想让动作能够使用getInput()或setOutput(),我们应该显式传递一个PortsList;
    PortsList say_something_ports = { InputPort<std::string>("message") };
    factory.registerSimpleAction("SaySomething2", SaySomethingSimple, 
                                 say_something_ports );

    auto tree = factory.createTreeFromFile("./my_tree.xml");

    tree.tickRoot();

    /*  Expected output:

        Robot says: start thinking...
        Robot says: The answer is 42
        Robot says: SaySomething2 works too...
        Robot says: The answer is 42
    */
    return 0;
}

我们使用相同的键(the_aswer)将输出端口连接到输入端口;这意味着它们“指向”黑板上的同一个条目。

这些端口可以相互连接,因为它们的类型是相同的,即std::string。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值