Game Thinking 25: BehaviorTree Source Code Analysis Behavior Tree (to be continued, 2023-04-04)


Link: https://www.behaviortree.dev/docs/3.8/tutorial-advanced/asynchronous_nodes
Original link: https://github.com/BehaviorTree/BehaviorTree.CPP/tree/3.8.0

1. Pre-understanding

1) Behavior tree introduction

2) Introduction of main concepts

3) XML topic introduction

2. Basic tutorial

1) Build the first behavior tree

  • First make a behavior tree of sequential nodes, the structure diagram is as follows
    insert image description here

(1) Three ways to create behavior tree nodes (inheritance, dependency injection, definition class)

  • (1) Default conventional method: 继承do
// Example of custom SyncActionNode (synchronous action)
// without ports.(port表示的意思是节点的传入、传出参数,这个ApproachObject节点没有传入传出参数)
class ApproachObject : public BT::SyncActionNode
{
    
    
public:
	//构造函数,传入行为树节点名字
  ApproachObject(const std::string& name) :
      BT::SyncActionNode(name, {
    
    })
  {
    
    }

  // You must override the virtual function tick()
  //需要重写tick虚函数,返回对应的节点状态(必须返回RUNNING, SUCCESS or FAILURE 这几个状态)
  BT::NodeStatus tick() override
  {
    
    
    std::cout << "ApproachObject: " << this->name() << std::endl;
    return BT::NodeStatus::SUCCESS;
  }
};
  • (2) The second method:依赖注入
BT::NodeStatus myFunction()
BT::NodeStatus myFunction(BT::TreeNode& self) 
  • example
using namespace BT;

// Simple function that return a NodeStatus
BT::NodeStatus CheckBattery()
{
    
    
  std::cout << "[ Battery: OK ]" << std::endl;
  return BT::NodeStatus::SUCCESS;
}
  • (3) The third method: define a class method to define a behavior tree node (here you can call the open and close of this class)
// 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
};

(2) Main main function call behavior tree node (link static library for debugging, link dynamic library for actual use)

  • Calling nodes by linking static libraries (registering all the nodes one by one.)
int main()
{
    
    
  //1、 We use the BehaviorTreeFactory to register our custom nodes
  BehaviorTreeFactory factory;//这个是用来注册节点的

	//2、
  // Note: the name used to register should be the same used in the XML.
  // Note that the same operations could be done using DummyNodes::RegisterNodes(factory)

  using namespace DummyNodes;

  // The recommended way to create a Node is through inheritance.
  // Even if it requires more boilerplate, it allows you to use more functionalities
  // like ports (we will discuss this in future tutorials).
  factory.registerNodeType<ApproachObject>("ApproachObject");

  // Registering a SimpleActionNode using a function pointer.
  // you may also use C++11 lambdas instead of std::bind
  factory.registerSimpleCondition("CheckBattery", std::bind(CheckBattery));

  //You can also create SimpleActionNodes using methods of a class
  GripperInterface gripper;
  factory.registerSimpleAction("OpenGripper",
                               std::bind(&GripperInterface::open, &gripper));
  factory.registerSimpleAction("CloseGripper",
                               std::bind(&GripperInterface::close, &gripper));

	//3、跑行为树逻辑
  // Trees are created at deployment-time (i.e. at run-time, but only once at the beginning).
  // The currently supported format is XML.
  // IMPORTANT: when the object "tree" goes out of scope, all the TreeNodes are destroyed
  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.tickRootWhileRunning();

}

  • Link dynamic library to call node
int main()
{
    
    
  //1、 We use the BehaviorTreeFactory to register our custom nodes
  BehaviorTreeFactory factory;//这个是用来注册节点的

	//2、
  // Load dynamically a plugin and register the TreeNodes it contains
  // it automated the registering step.
  factory.registerFromPlugin("./libdummy_nodes_dyn.so");//(源码宏定义已经注册)

	//3、跑行为树逻辑
  // Trees are created at deployment-time (i.e. at run-time, but only once at the beginning).
  // The currently supported format is XML.
  // IMPORTANT: when the object "tree" goes out of scope, all the TreeNodes are destroyed
  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.tickRootWhileRunning();

}

(3) XML parsing

 <root main_tree_to_execute = "MainTree" >

     <BehaviorTree ID="MainTree">
        <Sequence name="root_sequence">
            <CheckBattery   name="battery_ok"/>
            <OpenGripper    name="open_gripper"/>
            <ApproachObject name="approach_object"/>
            <CloseGripper   name="close_gripper"/>
        </Sequence>
     </BehaviorTree>

 </root>
  • note

1) BehaviorTree IDThe role is to identify the root node of the behavior tree, so that when the behavior tree is executed, it starts from the correct node.
2) SequenceIndicates a sequential node
3) root main_tree_to_executeIndicates that when tickRootWhileRunning() is called, the tree logic whose BehaviorTree ID is MainTree is executed, and MainTree is the root node

  • original string
static const char* xml_text = R"(

 <root main_tree_to_execute = "MainTree" >

     <BehaviorTree ID="MainTree">
        <Sequence name="root_sequence">
            <CheckBattery   name="battery_ok"/>
            <OpenGripper    name="open_gripper"/>
            <ApproachObject name="approach_object"/>
            <CloseGripper   name="close_gripper"/>
        </Sequence>
     </BehaviorTree>

 </root>
 )";

The R here means:
The R before the brackets in the xml_text variable is a prefix of the raw string literal, which means that any escape sequences in the string (such as \n for newline) are treated as literal characters rather than their special meaning. In this case, it allows XML literals to be written as a single multiline string without escaping any characters.

(4) output result

  • Notice

It turned out that it was linking to the linux dynamic library. Here it needs to be changed to linking to the win dynamic library. The win dynamic library needs to be put together with the execution file.
insert image description here

2) Blackboard data and port

(1) The role of the blackboard (realized through the port)

① Pass parameters to the node
② Obtain the output information of the node
③ Pass the output information of a node as input information to another node

(2) Blackboard test case mind map (this blackboard only stores simple key-value pairs)

insert image description here

(3) Ports of Input input parameters are used as examples

  • example
1)main函数注册SaySomething函数
  factory.registerNodeType<SaySomething>("SaySomething");
2)SaySomething这个类
class SaySomething : public BT::SyncActionNode
{
    
    
  public:
    SaySomething(const std::string& name, const BT::NodeConfiguration& config)
      : BT::SyncActionNode(name, config)
    {
    
    
    }

    // You must override the virtual function tick()
    NodeStatus tick() override;

    // It is mandatory to define this static method.
    static BT::PortsList providedPorts()
    {
    
    
        return{
    
     BT::InputPort<std::string>("message") };
    }
};
这里的static BT::PortsList providedPorts()是强制性要求写成静态方式的;而且必须重写虚函数NodeStatus tick() override;

(4) Examples of ports for output parameters

  • original xml
 <root main_tree_to_execute = "MainTree" >

     <BehaviorTree ID="MainTree">
        <Sequence name="root">
            <SaySomething     message="hello" />
            <SaySomething2    message="this works too" />
            <ThinkWhatToSay   text="{the_answer}"/>
            <SaySomething2    message="{the_answer}" />
        </Sequence>
     </BehaviorTree>

 </root>
  • Here ThinkWhatToSay modifies the value of the_answer corresponding to the text, causing the following the_answer to change accordingly
class ThinkWhatToSay : public BT::SyncActionNode
{
    
    
public:
  ThinkWhatToSay(const std::string& name, const BT::NodeConfiguration& config) :
    BT::SyncActionNode(name, config)
  {
    
    }

  // This Action simply write a value in the port "text"
  BT::NodeStatus tick() override
  {
    
    
    setOutput("text", "The answer is 42");
    return BT::NodeStatus::SUCCESS;
  }

  // A node having ports MUST implement this STATIC method
  static BT::PortsList providedPorts()
  {
    
    
    return {
    
    BT::OutputPort<std::string>("text")};
  }
};

(5) Explanation of t02_basic_ports project

1) SaySomething prints hello in tick
2) ThinkWhatToSay output parameter port to the_answer
3) Here, because SaySomething2 corresponds to this works too before, but because ThinkWhatToSay modifies the value of the_answer, the message of SaySomething2 will correspond to two, print ( 注意这里两个ThinkWhatToSay节点都是唯一, so will be 打印两次)

        Robot says: this works too
        Robot says: The answer is 42

3) Blackboard passes generic parameters -port with generic types

4) Reflexive behavior

5) Using subtrees

6)port remapping

7)use multiple xml files

8) Pass additional parameters

3. Advanced tutorial (asynchronous process)

4. Nodes Library

1) Decorator

2) Callback function

3) Sequential nodes

  • Behavior tree node classification
    1) Sequence node:
    execute all child nodes sequentially, if all succeed, return success, if one fails, return failure

2) Loop node
Loop executes child nodes for a specified number of times and returns success. If the number of loops is "-1", it will loop infinitely

3) Condition Condition node
returns success or failure according to the comparison result of the condition

4) The Action node
returns "Success", "Failure" or "Run" according to the action result

5) Wait for the Wait node
to return "running" and return "success" until the specified time has elapsed

Guess you like

Origin blog.csdn.net/weixin_43679037/article/details/127269116