11 min read

BehaviorTree

什么是行为树

行为树,是一棵用于控制AI决策行为的,包含了层级节点的树结构

原理: 遍历

当我们要决策当前这个角色要做什么样的行为的时候,我们就会自顶向下的,通过一些条件来搜索这棵行为树,最终确定需要做的行为(叶节点),并且执行它,这就是行为树的基本原理

组成

行为树的组成主要是4个节点抽象而成的: 组合节点,装饰节点,条件节点,行为节点

组合节点(Composites)

主要包括: Sequence顺序条件,Selector选择条件,Parallel平行条件以及他们之间相互组合的条件

  • Sequence 类似于编程语言中的"&&"符号,它从左到右,每帧只执行一个子节点
    • 如果当前子节点返回Running,那么Sequence也返回Running。下一帧继续执行当前这个子节点
    • 如果当前子节点返回Failing,那么Sequence节点本身返回失败
    • 如果当前子节点返回成功,还有下一个子节点,那么Sequence本身返回Running,下一帧回切换到下一个子节点;如果子节点都执行完毕了,那么Sequence节点返回成功,整个节点结束
  • Selector Selector与Sequence执行顺序相同,逻辑正巧是“||”的逻辑。它也是从左到右,每帧只执行一个子节点。
    • 如果子节点返回Running,那么Selector也返回Running,下一帧继续执行这个子节点
    • 如果当前子节点返回失败,那么Selector本身返回Running,下一帧执行下一个子节点;如果所有子节点都失败了,就返回失败
    • 如果当前子节点返回成功,那么Selector返回成功
  • Parallel 从返回值来看它是 “&&” 逻辑。与Sequence的区别是,在每一桢,它都执行所有子节点一次~~。
    • 所有子节点都Running,那么Parallel节点也返回Running。
    • 有任何一个节点返回失败,那么Parallel立刻结束,返回失败。还处于Running的子节点也会终止(从界面上可以看出,正在Running的被假设为失败)。
    • 有任何一个节点返回成功,那么该子节点下一帧就不会被调用了,但是Parallel本身仍然返回Running,直到所有子节点都返回成功,Parallel才返回成功。
  • Parallel Selector 并行的OR 从返回值来看是 “||” 逻辑。它是并行的,每一桢执行所有子节点一次~~
    • 所有子节点都Running,那么Parallel Selector节点也返回Running
    • 有任何一个节点返回失败,那么Parallel Selector 本身返回Running,直到所有子节点都失败了,它才返回失败。
    • 有任何一个节点返回成功,Parallel Selector 直接返回成功。
  • Random Sequence
    • Sequence是从左到右串行,Random Sequence 也是串行完全一样,只是它从还没执行过的N个子节点中随机挑选一个执行
  • Priority Selector, Random Selector
    • 这二者是Selector的变体,也都是串行。分别是根据优先级挑选、随机挑选、自定义挑选顺序
    • 再强调一下,串行情况下,如果有节点还在running,那么肯定先执行running的节点。“挑选”的意思是说,在没有running的节点时,从还没执行过的节点中,根据规则挑出一个
  • Selector Evaluator, Utility Selector
    • 这两种类型的特殊之处在于:在每一帧,都要重新计算子节点的优先级或者效用,就算节点正在running,也有可能因为优先级变化而切换节点。它们既不是并行也不是串行。
    • Utility Selector 是一种选择器,它是基于“Utility”也就是“效用”进行选择,用在《模拟人生》这种游戏中会非常有效,就是当你面对吃法、睡觉、上厕所这三件事时,你选效用最大的那一件事去做即可,而且如果有必要可以随时终止当前正在做的事情。

装饰节点(Decorators)

连接树叶的树枝,就是各种类型的装饰节点,这些节点决定了AI如何从树的顶端根据不同的情况,来沿着不同的路径来到最终的叶子这一过程

如让子节点循环操作(Loop)或者让子task一直运行知道其返回某个运动状态值(Util),或者将task的返回值取反(NOT)等等

  • Inverter 条件判断或动作的返回结果取反,成功变失败,失败变成功,Running不变
  • Reapter 循环执行,可以调节循环次数等参数
  • Return Failure 返回值无论成功还是失败都返回失败,但Running还是Running
  • Return Success
  • Until Failure 循环直到失败,换句话说如果成功就再次执行子节点
  • Until Sucess

行为节点(Actions)

用于判断某条件是否成立,目前看来,时Behavior Designer为了贯彻职责单一的原则,将判断专门作为一个节点独立处理,比如判断某目标是否在视野内,其实在攻击Action里面也可以写,但是这样Action就不单一了,不利于视野判断处理的复用。一般条件节点出现在Sequence控制节点中,其后紧跟条件成立后的Action节点

条件节点(Conditions)

行为节点时真正做事的节点,行为节点在树的最末端,都是叶子节点,就是这些AI实际上去做事情的命令

通过使用行为树内的节点之间的关联来驱动角色的行为,比直接用具体的代码告诉一个角色去做什么事情,要来得有意思得多,这也是行为树最让人兴奋的一点。这样我们只要抽象好行为,就不用去理会战斗中具体发生了什么。

树的遍历 Tree Traversal

行为树的一个特点是,他会一层一层地去对节点一次进行检查,而这每一层都需要花费一个tick的事件,所以它需要花数个tick才能完成从顶部走到第的过程,来完成其逻辑,这和一般用代码实现的功能很不同

这并不是一个很有效率的方式,尤其是当你的树变得非常深的时候。行为树的实现必须具备可以在一个tick内完成整个行为树的逻辑判断

工作流 WorkFlow

行为树由多种不同类型的节点构成,他们都拥有一个共同的核心功能,即他们会返回三种状态的一个作为结果

  • 成功-Success
  • 失败-Failure
  • 进行中-Running

前两个们是用来向他们的父节点通知运行的成功或失败的结果。第三种是指还在运行中,结果还未决定,在下一个tick的时候再去检查这个节点的运行结果

这个功能非常重要,可以让一个节点维持运行一段时间来维持某些行为

问题汇总

  • 当两种行为之间切换太快时,会导致AI动作出现抖动现象,如何避免这种情况
    • 可以延时处理条件节点,当判断结果与上次不相同时且在规定时间内依旧返回上次结果
  • AI的位置移动如果不实时发送,会导致玩家位置同步卡顿
    • 可以使用平滑处理,使用当前位置与最新获取的位置做插值运算即可
  • 每个客户端都在跑AI的行为树嘛,怎么把本地AI的画面同步给其它客户端
    • 只有本地依附的AI才有权限跑行为树,然后把对应的动作同步给其它客户端,当依附的玩家死亡后回找新的玩家依附,如果找不到会自动销毁
  • 如果需要新增AI的行为或者条件时,如何做
    • 只需要集成对应的AbsRoleAIAction/AbsRoleAICondition即可,在对应的OnUpdate里编写对应逻辑
  • 遇到return success或者fail,然后整颗树终止不再运行的情况
    • 两种解决办法:1.restartWhenComplete;2. 设置一个Idle节点,保持Running,不让树complete