基于前面几次的面试,自己进行行了总结:
在问到我项目里的怪物AI逻辑的时候,我自己项目里用的是状态机制,算是行为树的一层,但是复杂的行为树有很多很多层,比较能完成的实习NPC或者怪物行为。自己也在百度上找了许多资料,对此谈下我自己的理解!
基础-Basics
1、行为树的名字很好地解释了它是什么。不像有限状态机(Finite State Machine)或其他用于 AI 编程的系统,行为树是一棵用于控制 AI 决策行为的、包含了层级节点的树结构。树的最末端——叶子,就是这些 AI 实际上去做事情的命令;连接树叶的树枝,就是各种类型的节点,这些节点决定了 AI 如何从树的顶端根据不同的情况,来沿着不同的路径来到最终的叶子这一过程。
2、行为树可以非常地“深”,层层节点向下延伸。凭借调用实现具体功能的子行为树,开发者可以建立相互连接的行为树库来做出非常让人信服的 AI 行为。并且,行为树的开发是高度迭代的,你可以从一个很简单的行为开始,然后做一些分支来应对不同的情境或是实现不同的目标,让 AI 的诉求来驱动行为,或是允许 AI 在行为树没有覆盖到的情境下使用备用方案等等。
树的遍历-Tree Traversal
行为树的一个特点是,它会“一层一层”地去对节点依次进行检查,而这每一层都需要花费一个 tick 的时间,所以它需要花数个 tick 才能完成从顶部走到底的过程,来完成其逻辑,这和一般用代码实现功能是很不同的。
这并不是一个很有效率的方式,尤其是当你的树变得非常深的时候。我认为行为树的实现必须具备可以在一个 tick 内完成整个行为树的判断逻辑。
工作流-Flow
行为树由多种不同类型的节点构成,它们都拥有一个共同的核心功能,即它们会返回三种状态中的一个作为结果。这三种状态分别是:
成功-Success;
失败-Failure;
运行中-Running;
一、前两个,正如它们的名字,是用来向它们的父节点通知运行的成功或失败的结果。第三种是指还在运行中,结果还未决定,在下一个 tick 的时候再去检查这个节点的运行结果。
二、这个功能非常重要,它可以让一个节点持续运行一段时间来维持某些行为。比如一个“walk(行走)”的节点会在计算寻路和让角色保持行走的过程中持续返回“Running”来让角色保持这一状态。如果寻路因为某些原因失败,或是除了某些状况让行走的行为不得不中止,那么这个节点会返回“Failure”来告诉它的父节点;如果这个角色走到了指定的目的地,那么节点返回“Success”来表示这个行走的指令已经成功完成。
三、 一共有三种节点类型,它们分别是:
组合节点-Composite
修饰节点-Decorator;
叶节点-Leaf;
组合节点 -Composite
1、组合节点通常可以拥有一个或更多的子节点。这些子节点会按照一定的次序或是随机地执行,并会根据执行的结果向父节点返回“Success”、“Failure”,或是在未执行完毕时“Running”这样的结果值。
2、最常用的组合节点是 Sequence(次序节点),它很简单地按照固定的次序运行子节点,任何一个子节点返回 Failure,则这个组合节点向它的父节点返回 Failure;当所有子节点都返回 Success 时,这个组合节点返回 Success。
修饰节点-Decorator
1、修饰节点也可以拥有子节点,但是不同于组合节点,它只能拥有一个子节点。取决于修饰节点的类型,它的功能要么是修改子节点返回的结果、终止子节点,或是重复执行子节点等等。
2、一个比较常见的修饰节点的例子是 Inverter(逆变节点),它可以将子节点的结果倒转,比如子节点返回了 Failure,则这个修饰节点会向上返回 Success,以此类推。
叶节点-Leaf
1、叶节点是最低层的节点,它们不会拥有子节点。叶节点是最强大的节点类型,它们是真正让你的树做具体事情的基础元素。通过与组合节点和修饰节点的配合,再加上你自己对叶节点功能的定义,你可以实现非常复杂的、智能的行为逻辑。
2、拿代码作为类比的话,组合节点和修饰节点就好比那些改变代码 flow 的 if 判断和 while loop 等等,而叶节点就是那些真正起作用的被调用的方法,去让角色做什么或是进行某些条件判断。
3、参数可以在这些节点中起到作用,比如 Walk 的这个叶节点可以包含一个具体将要移动到的位置的参数。这些参数可以从其他变量里获得,比如角色将要前往的一个地点可以被 GetSafeLocation 这个节点所决定,存入一个变量里,然后 Walk 节点可以使用这个变量来定义它的目的地。行为树的运行中,这些不同的节点通过数据上下文来共同储存或使用一些持久数据(persistent data),使得行为树的功能变得强大。
4、另一种叶节点的类型是调用其他的行为树并把当前行为树的数据传给对方。