首页 > 代码库 > AI行为树

AI行为树

如何完成行为树的实现的呢,要找清楚黑板,行为树,AI_Controller的关系,黑板实际上就是行为树的数据库。

1.       先进行整体架构,创建Character,AIController,行为树,黑板,将其关系理顺,首先将控制器与角色链接在一起,让控制器去控制角色,其次在控制器中实现行为树,Run BehaviorTree,再者就是将黑板绑定到行为树上去使用,在行为树中Detail面板中的BlackBoard绑定上刚创建的黑板,这样就将四者联系在一起了。

2.       架构行为树,Root节点下只能链接一个Composites节点,架构过程中暂时全都用Selector,之后有需要进行更改,设想一下还是有四个状态,Patrol,Chase,Attack,Back,接着在下面分别添加四个Selector,每个下面添加一个Task.(写逻辑)

3.       如何实现四种状态下的切换呢,还是要用到枚举,写入四种不同的状态,状态有了,如何切换呢,那么想到要用到枚举种不同的状态,黑板是行为属的数据库,想用枚举这个数据就要将枚举添加到数据库中,四种状态的Selector分别都添加一个黑板但是每个黑板所呈现的枚举状态不同,这样就可以实现,改变黑板上的State来实行切换状态。

技术分享

这是添加到Selector上的黑板可编辑的东西,FlowControl值得是这条分支的流程,如果其中的Result或者Value改变那么就会出现改变Flow,(Observer aborts(改变什么))。

下边的Blackboard呢,Key Query是判定条件,(等于,大于,小于等等)BlackboardKey是黑板中的关键值,需要什么选什么,KeyValue是关键值得大小或者选项等等。通过改变KeyValue来对四纵不同的状态进行定义。

4.       基本整体架构就是这样,下面接着先写Patrol,首先要完成的自己巡逻状态,打开Task,覆写一个Tick事件作为驱动,首先用的节点就是SimpleMoveToLocation,这里需要输入两个值,Controller和Location,Comtroller的输入有两种方法,第一中就是将Controller写进黑板,初始化赋值之后使用。第二种较为简便,其实Tick事件上的Owner Actor就是一个Controller,只需要将其Castto为需要的AI_CON就可以了,那么Location就要需要黑板传入了,这里只介绍一次,黑板上的值如何初始化和使用,之后一笔带过,但这也是行为树中比较重要的地方。首先在黑板上创建一个Vector的变量,命名为HomeLocation,然后在Task中调用,先创建一个BlackBoard类型的变量,从中引出一个GetBlackBoardValueAsVector。但是此时并不能保证所连的Vector就是HomeLocation,将黑板变量眼睛打开,编译,点击行为树中的Task就会发现多了一个Default,而变量就是黑板变量,在黑板变量中选择Homelocation就OK了,这样就保证了引出的Vector就是Homelocation。还需要一个让其四处移动的一个节点,GetRandomPointinNavigableRadius(在AI->Navigable中可以找到),设置好后链接到Goal上,这时还是不可以移动,那是因为HomeLocation还是个空值,没有对他进行赋值,这时候就要在Controller中进行赋值了。

5.       在赋值的时候有一个细节不可以忘记SetValueasVector节点上的KeyName是说要付给值得名字,这时候要创建一个Name变量,但是切记创建完变量后编译后一定要将DefaultValue改成要改变的名字。

6.       120帧是1秒。

7.       将动画设置好,那么自动巡逻就写成了。

8.       但是不意味着Patrol这个状态就结束了,这个状态下还是要写发现Player并跳转到Chase状态,因为这里跳转状态会经常用到,那么我就直接将将其写作一个Task,ChangeStateTo.

9.       就用上述的方法,创建一个黑板变量引出SetBlackBoardValueasEnum,可以直接改变Value的值来控制切换的是哪个状态,但是这样很不方便,为了直观,可以再创建一个Enum变量并打开眼睛,使其在Task中可以改变。这样改变状态是完成了,可是如何跳转下去呢,在Task中完成跳转的节点是FinishExecute,如果Success就会返回到根节点,如果Success没有选中,那么回从左向右跳到下一个子节点。但是这里我们改变状态当然是希望跳转回根节点,重新进入分支。

10.   这时发现还是缺少一个事件,就是FindTarget,创建并打开,因为Find的话就要每帧都去检测,所以复写一个Tick事件,还是用之前的方法进行检测,PawnSensing,在Controller中添加事件OnSeePwan,附一个值传给黑板,在Task中判断这个值是否有效,有效则进行跳转。不要忘记在最后写上FinishExecute,但是这个要是Success不选中的,因为想让其条状到ChangeTask上。

11.   这是发现问题了,巡逻Task是不断在改变位置的,不能进行跳转,换句话说是常态,不能进行判断跳转,并且跟Find是并列关系,所以这里可以考虑用SimpleParallel,平行关系,但是主支可以操控跳转,副支跟随着主支跳转,自己不能控制。正好适用我的这个情况。这样Patrol就算写完了,接下来写Chase.(这里简述一下SimpleParallel,(插播概念以及细节)它与Selector,Sequence统称为Composites节点,Composites节点定义一个分支的根以及该分支如何执行的规则,规则可以有Decoratars(装饰者)节点决定,此外也可以将Services节点附着在上面,不过Services要在其子节点执行之后才开始生效。行为树的节点也就分为四种:Composites,Decoratars,Service,Task。Composities节点中需要注意的是执行顺序,还有要注意的是SimpleParallel中的FinishMode,其中两种方式分别是Immediate(主任务完成后,整个背景树执行终止)Delayed(主任务完成后,背景树继续执行),在Decorators中暂时先更新一个BlackBooard,在Detail面板中FlowControl下Notify Observer(节点检测)下有两个选项,OnresultChange(条件发生变换时继续评估)OnValueChange(观测的黑板键发生变化时再次评估,存在疑问),下面的ObserverAborts(检测中止abort流产中止,其中LowerPriority是指终止此节点右边的节点),剩下的都提过,)

12.   先下ChaseTask,追踪的话还是用节点,SimpleMoveToActor。事件写完之后就要考虑可能出现的情况了,距离小于攻击距离+50就切换至Attack状态,距离大于追踪距离就切换至Back状态。这里如果用之前的Patrol状态的方法也可以,但这里使用一个新的方法,Service.

13.   Interval间隔时间,休息时间,deviation偏差值,这是Service的俩个可编辑的值,主要意思是Service有一套自己的时间轴,可以编辑的间隔时间和偏差值。只有进入此支线时Service才起作用,在其中写什么呢,主要写当距离小于攻击距离+50就切换至Attack状态,距离大于追踪距离就切换至Back状态。这里就不需要用finish来进行跳转了,那么如何完成跳转呢,上面说过,在黑板上有控制FlowControl,这里就用到了,将这个Selecter上的黑板上的FlowControl修改,这里是改变了StateEnum所以将NotifyObserve改为OnResultChange,Observe aborts改为Both,就会出现当Service中将State中的值改变了,那么久会直接回到根节点。因为Service的时间轴是独立的,所以会有误差。

AI行为树