决策树(BehaviorTree)的学习心得&&个人见解(持续更新)
前言
本人在准备RoboMaster比赛时负责编写哨兵机器人的决策代码,在查询资料后可知需要进行关于BehaviorTree(以下简称BT树)的学习,不过BT树的官方教程过于简单并且并无过多言语描述并且网上我暂时没有搜索到系统性BehaviorTree_cpp的学习路线,更多的只是与虚幻引擎当中的行为树蓝图有关的教程。
本着没有教程就创造教程以及作为自己的备忘录的初衷,本人决定开启本文的编写。由于本人对于端口、xml文件编写的了解程度可算作为0,所以当中的表述会有些出入甚至是完全错误,也请各位在发现本人表述上有错误时可以及时指正,本文持续更新。
那么让我们开始关于BT树的学习路程吧!
一、何为BT树
1.概念:
与有限状态机不同,行为树是控制“任务”执行流的分层节点树。(有限状态机(缩写:FSM)又称有限状态自动机(英语:finite-state automaton,缩写:FSA),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型。——来自维基百科)
2.节点(建议先了解数据结构中树的概念):
2.1 TreeNode(树节点)
孩子数量:1...N
树节点包含一下提到的所有节点,类似于复数对于整数、虚数、实数等等的概念。
2.2 LeafNode(叶子节点)
LeafNodes,那些没有任何子节点的 TreeNodes, 是实际的命令,即行为树的节点 与系统的其余部分交互。操作节点是最常见的 LeafNode 类型。
2.3 ControlNode(控制节点)
官方解释:通常,根据其兄弟姐妹或/和其自身状态的结果来勾选子项。
2.4 DecoratorNode(装饰节点)
孩子数量:1
官方解释:除其他外,它可能会改变其子项的结果或多次勾选它。
人话:这小子俩作用,其一:原本返回的结果是SUCCESS,经过它就变为FIELD了
其二:类比for函数,也就是多次执行某操作节点(ActionNode)
2.5 ConditionNode(条件节点)
孩子数量:0
官方解释:不应更改系统,不能返回RUNNING(毕竟都叫条件节点了,当然只能非对即错咯)
2.6 ActionNode(操作节点)
孩子数量:0
官方解释:就是“做某事”的节点
注:该节点分为同步节点以及异步节点。前者以原子方式执行并阻塞树,直到返回 SUCCESS 或 FAILURE。相反,异步操作可能会返回 RUNNING 来传达 该操作仍在执行中。我们需要再次访问它们,直到最终返回 SUCCESS 或 FAILURE。(想具体了解异步操作的可以看四)
2.7 NodeStatus(节点状态)
节点状态分为三种:SUCCESS(成功) FIELD(失败) RUNNING(运行)
不同的节点状态集成在NodeStatus并由NodeStatus进行return,return回来的不同状态会很大程度的决定了BT树的下一步操作到底是结束还是继续还是其它怎么样。
2.8不同节点间的包含关系
3.Blackboard(黑板)与端口
- “Blackboard”是所有节点共享的简单键/值存储 树。
- Blackboard 的“条目”是键/值对。
- 输入端口可以读取 Blackboard 中的条目,而输出端口可以写入条目。
输入端口有效输入:1.Node读取和解析的静态字符串 2.指向BlackBoard条目的指针,由键表示
输出端口有效输入:仅当另一个节点已在同一条目内写入“某些内容”时,指向黑板条目的输入端口才有效。
在我看来,黑板的作用类似于一个仓库,InputPort端口是放东西,OutputPort端口是拿东西。
4.关键函数
4.1tick()及其工作原理
我们可以看作为ROS中的spin()函数,其用法就是通过override(复写)tick函数来完成我们想要进行的操作,将想写的代码写入其中即可。
工作原理:一个树中tick函数会照从左到右的顺序单线程执行,如果执行当中有任意一方法阻塞(也就是不反悔三种状态中的一种时),则整个执行的流均会阻塞,尤其是在sequence中,当前一个操作节点执行失败,则在该节点之后的所有节点均不会被执行。
二、理论联系实际(初级)
0.执行流程的综述(个人见解)
以官方流程代码为例子,想要创建以上的BT树大体需要经历如下几个阶段:
创建CheckBattery和其余四个操作节点----->在main函数中创建BehaviorTreeFactory实例---->用factory.registerNodeType("name")创建名为name的端口?(反正这步的意义就是让你写的操作节点不在xml文件中报错)---->编写xml文件---->在main函数中写auto tree=factory.createTreeFromFile("xml文件路径")来构造树---->tree.tickWhileRunning();
注:随着ActionNode类型的不同,是否有端口,在main函数用factory注册的时候所用的子函数都不同。
1.1创建ActionNode(无端口)
官方推荐的方法时继承,继承的东西以及接下来书写的代码形式我没有进行过多了解,直接背就完事了。
//在没有端口的情况下自定义SyncActionNode示例(同步操作) 。 class ApproachObject : public BT::SyncActionNode { public: ApproachObject(const std::string& name) : BT::SyncActionNode(name, {}) {} // 你必须复写tick()函数 BT::NodeStatus tick() override { std::cout = _completion_time) { std::cout B && color != BLUE" else="FAILURE">
我们预计以下黑板条目包含:
- "msg:字符串“你好世界”
- A:别名THE_ANSWER对应的整数值。
- B:真实值3.14
- C:与枚举 RED 对应的整数值
C++代码是:
enum Color { RED = 1, BLUE = 2, GREEN = 3 }; int main() { BehaviorTreeFactory factory; factory.registerNodeType("SaySomething"); // 我们可以将这些枚举添加到脚本语言中。 // 检查 magic_enum 的限制 factory.registerScriptingEnums(); // 或者我们可以手动为标签“THE_ANSWER”分配一个数字。 // 这不受任何范围限制的影响 factory.registerScriptingEnum("THE_ANSWER", 42); auto tree = factory.createTreeFromText(xml_text); tree.tickWhileRunning(); return 0; }
预期输出为:
Robot says: 42.000000
Robot says: 3.140000
Robot says: hello world
Robot says: 1.000000
十一、记录器和观察器
更新日志:
2.19—— 内容一至三的编写
2.20—— 内容四的编写 || 更改文章题目
2.21—— 内容五至十的编写 || 二.1.3的编写 || 更改文章题目