行为树(Behavior Tree)实践(2)– 进一步的讨论

上次提到了一些行为树的基本概念,包括行为节点,控制节点(选择,序列,并行),此次来更多,更深刻的讨论行为树的一些东西,若是对行为树不是很了解,请参看这里程序员

一. 关于选择节点的讨论 数据结构

咱们说过选择节点的定义是经过判断子节点的前提条件来选择一个节点执行,这就牵涉到判断顺序的问题,是自左向右,仍是随机选择,或者其余的一些规则等等,这样就延伸出各类各样的选择节点。 dom

 

 

  • 带优先级的选择节点(Priority Selector):这种选择节点每次都是自左向右依次选择,当发现找到一个可执行的子节点后就中止搜索后续子节点。这样的选择方式,就存在一个优先级的问题,也就是说最左边的节点优先级最高,由于它是被最早判断的。对于这种选择节点来讲,它的子节点的前提设定,必须是“从窄到宽”的方式,不然后续节点都会发生“饿死”的状况,也就是说永远不会被执行到,为了更清楚的说明,看下面第一张图,这三个子节点在一个带优先级的选择节点下,它们的前提会被依次判断,能够看到这个三个子节点的前提从左向右,一个比一个更严格,若是咱们如今a为9,按照下图的定义会执行第一个子节点,若是a为7,则会执行第二个子节点,若是a=11,则会执行第三个子节点。下面的第二张图演示了一种节点“饿死”(Starvation)的状况,咱们看到第一个子节点的前提,比第二个子节点更宽泛,只要a<10,那自左向右判断的话,永远会进第一个节点,因此,若是要用到带优先级的选择节点,则必须检查每个子节点的前提,以防止节点饿死的状况.

 

bv-tree-priority-selector-1

bv-tree-priority-selector-2

  • 不带优先级的选择节点(Non-priority Selector):这种选择节点的选择顺序是从上一个执行过的子节点开始选择,若是前提知足,则继续执行此节点,若是条件不知足,则今后节点开始,依次判断每个子节点的前提,当找到一个知足条件的子节点后,则执行该节点。这种方式,是基于一种称之为“持续性”的假设,由于在游戏中,一个行为通常不会在一帧里结束,而是会持续一段时间,因此有时为了优化的目的,咱们能够优先判断上一个执行的节点,当其条件不知足时,再寻找下一个可执行的节点。这种寻找方式不存在哪一个节点优先判断的问题,因此对于前提的设置的要求,就是要保证“互斥”(Exclusion)。若是咱们用上面第一张图来讲明,若是咱们把控制节点换成不带优先级的选择节点,能够看到,当a=3时,第二个子节点会被执行,下一次当a变成9时,因为不是从头依次判断前提的,因此,咱们仍是会选择第二个节点,而不是咱们可能指望的第一个节点。正确的作法见下图,注意每个子节点的前提是“互斥的”。因此对于不带优先级的选择节点,它子节点的排列顺序就不是那么重要了,能够任意排列。

bv-tree-nonpriority-selector-1

  • 带权值的选择节点(Weighted Selector):对于这种选择节点,咱们会预先为每个分支标注一个“权值”(Weight Value),而后当咱们选择的时候,采用随机选择的方式来选,随机时会参考权值,而且保证已经被测试过的节点的不会再被测试,直到有一个节点的前提被知足,或者测试完全部的节点。带权值的选择节点对于子节点前提因为随机的存在,因此子节点的前提能够任意,而不会发生“饿死”的状况,通常来讲,咱们一般会把因此子节点的前提设为相同,以更好的表现出权值带来的几率上的效果。当全部子节点的权值同样时,这种选择节点就成为了随机选择节点(Random Selector)带权值的选择节点对于须要丰富AI行为的地方,很是适用,好比养成类游戏中,小狗表示开心的时候,可能会有各类各样的表现,咱们就能够用这种选择节点,添加各类子节点行为来实现。

bv-tree-weighted-selector-1

这些就是经常使用的选择节点类型,咱们能够根据须要,定义更多的选择节点的选择行为,其实咱们能够看到,不一样的选择行为对于子节点前提的要求会有略微的不一样,这是在咱们搭建行为树的时候须要注意的地方。 编辑器

二. 关于并行节点结束条件的讨论 工具

咱们每一个节点都会有一个运行状态,来表示当前行为是否结束。对于控制节点来讲,它的运行状态就是其子节点的运行状态,选择节点和序列节点比较好处理,由于对于这两种控制节点来讲,每时刻,只会有一个子节点在运行,只要返回在运行的这个子节点的状态便可。但对于并行节点来讲,它同时刻会有多个子节点运行,那咱们如何来处理并行节点的运行状态问题呢?通常有两种: 测试

  • 与:只有全部的子节点都运行结束,才返回结束。
  • 或:只要有一个子节点运行结束,就返回结束。

为何要须要有节点的运行状态呢? 优化

  • 序列控制节点中,须要用运行状态来控制序列的执行
  • 外部世界须要了解行为的运行状态,来决定是否要更新决策(若是行为树在决策层)/请求(若是行为树在行为层),关于AI分层,请参考这里

对于第二点,能够举个例子,好比咱们有一个行为是“走到A点”,假设这个行为是不可被打断的,那当咱们在走向A点的过程当中,行为树的运行状态就是“正在执行”,当到达A点时,行为树就返回“已完成”,这样,对外部来讲,当咱们看到行为树是“正在执行”的时候,咱们就不须要作任何新的行为(为了优化,或者为了行为抖动等等),当看到“已完成”的时候,咱们就能够作新的决策或者行为了。这样一个运行状态还有助于咱们检测行为树的状态,帮助调试。 google

三.关于具体实现的讨论 spa

行为树的实现能够有多种多样,我这边提出一些建议,通常来讲,行为树每一个节点须要有进入(Enter),离开(Exit),运行(Execute)等部分,须要有行为节点(ActionNode),控制节点(ControlNode),前提(Precondition)等基类,而后,还须要定义行为树的输入(InputParam)和输出(OutputParam),通常来讲,咱们但愿行为树是一个黑盒,也就是说,它仅依赖于预约义的输入。输入能够是黑板(Blackboard),工做池(Working Memory)等等数据结构,输出能够是请求(Request),或者其余自定义的数据结构,以下图: 插件

bv-tree-arch

代码的话,就不写了,由于blog没有代码插件,写代码效果不是很好,之后我会在TsiU里面发布一个行为树的库的版本。

四.关于绘制和调试的讨论

看到行为树的定义后,做为程序员的直觉,咱们很天然的就会想到,这好像应该能作一个工具来辅助行为树的建立和调试,咱们能够把预约义好的前提和节点,在一个可视化的编辑器里搭建成行为树,而后再导出成数据给游戏用。对于调试来讲,咱们可让工具和游戏通讯,而后实时的检测行为树的运行情况,好比当前在哪一个分支中等等。因为行为树的逻辑是可见的,而且是静态的,因此咱们看其选择的路径,咱们就能够知道AI为何会做出这样的决策了。当我刚接触到行为树的时候,就在想作这样一个编辑器,但迫于项目压力,一直没有时间作(工做量仍是挺大的),有兴趣,有时间的朋友,能够考虑作一个。顺便说一句,我如今对于行为树的搭建都是在代码中完成的,虽然没有数据驱动那么“先进”,但经过宏定义,排版等方式,仍是能很是清晰的表示树的总体结构。

关于行为树,我想这个系列就到这里了。在使用行为树的过程当中,可能还会碰到这样和那样的问题,包括我本身在实践中的一些经验,我想就先不包括在这个系列里了,之后再单独拿出来聊,这个系列做为行为树的入门,但愿对你们有所帮助,欢迎指教和讨论。

相关文章
相关标签/搜索