本文档描述的是3.6及以后版本,对于3.5及以前的老版本请参考分类“3.5”。
子树可以用来复用已有的行为树。通过子树节点
,一个行为树可以作为另一个行为树的子树,而
作为子树的那个行为树将被父树所“调用”。
子树还可以类似编程语言中的函数调用一样通过传递参数来使用。
类似动作节点,子树
节点根据子树的执行结果也会返回一样的执行结果(即成功、失败或正在执行),其父节点按照自己的控制逻辑来控制接下来的运行。
1. 编辑类型信息
首先,我们需要添加一个Agent类,后面会利用这个Agent类来创建行为树。
打开类型信息浏览器,分别为Agent类“FirstAgent”添加成员属性、方法和任务。
添加int类型的成员属性p1,如下图所示:
添加成员方法Say,该方法带有一个string&类型的参数,如下图所示:
添加任务t1,该任务带有一个int类型的参数,如下图所示:
2. 不带参数的子树
首先给出子树最直接的用法——不带参数的子树:
首先,创建行为树“subtree”,将动作节点的方法Say的参数设置为“Hello subtree!”,如下图所示:
Say方法的功能是输出指定的字符串,如下代码所示:
void FirstAgent::Say(behaviac::string& param0) { ///<<< BEGIN WRITING YOUR CODE Say printf("\n%s\n\n", param0.c_str()); ///<<< END WRITING YOUR CODE }
然后,创建行为树“maintree”,并将行为树列表中的节点“subtree”直接拖拽到该树中,如下图所示:
这样,行为树“maintree”就可以“调用”子树“subtree”了。
加载并执行行为树“maintree”后,结果如下图所示:
可以看到,输出了“Hello subtree!”字符串,说明子树“subtree”得到了正确的调用和执行。
3. 带参数的子树
有时候,我们可能需要给子树传递参数来使用,这样子树会更加模块化。
对于这种需要传参的子树,可以按照如下操作进行:
首先,创建行为树“subtree_task”,并为其添加的第一个子节点必须是任务节点,如下图所示:
为上图中的任务节点,在其属性窗口中配置其“任务”参数为“t1”,如下图所示:
由于任务“t1”带有参数“param0”,所以在上面行为树“subtree_task”的条件节点中可以使用该参数“param0”,如上图所示。可以借鉴程序语言的说法,该参数“param0”就类似于函数的形参。
然后,创建行为树“maintree_task”,并将行为树列表中的节点“subtree_task”直接拖拽到该树中,如下图所示:
选中上图中的子树节点,配置任务的参数值“param0”为2(这个参数类似于函数中的实参),如下图所示:
这样,行为树“maintree_task”就可以“调用”子树“subtree_task”,并为该子树传递参数了。
加载并执行行为树“maintree_task”后,结果如下图所示:
可以看到,输出了“Hello subtree_task!”字符串,说明子树“subtree_task”得到了正确的调用和执行。
4. 添加子树节点的补充说明
- 如上所述,可以从编辑器左侧的行为树节点列表中,通过鼠标选择并拖拽一棵行为树到另一棵行为树中来生成子树节点。被拖拽的行为树的路径被设置到
引用文件名
。需要指出的是,并非任意一个行为树都可以作为另外一个行为树的子树。3.6.32及之前版本,作为子树的Agent类型必须是父树的Agent类型的同类或父类。而3.6.33及之后版本,作为子树的Agent类型可以是父树的Agent类型的同类或父类或子类,但是需要保证执行该父树的Agent实例是父树和子树的Agent类型的子类或更低子类的实例(例如,假设父树的Agent类型是A,子树的Agent类型是B:如果A是B的子类,那么执行该父树的Agent实例必须是A或者A的子类的实例;如果B是A的子类,那么执行该父树的Agent实例必须是B或者B的子类的实例)。 - 另外,也可以像添加其他节点那样,在节点列表中选取子树,拖拽到相应的位置,然后点击该子树节点,在右侧的属性窗口中配置
引用文件名
或任务
。
- 如果手工配置的子树的路径是空的或无效的,在检查错误时会报错,表示该树不允许被导出。如果配置的是属性或方法,在编辑器中无法知其是否有效,只有运行的时候才会报运行时错误,如果运行过程中该值无效。
本教程相关的工作区和代码工程详见源码包的目录tutorials/tutorial_5。