前言
在UE开发中,AI逻辑的书写是一个极其重要的部分。在本人于UE的学习过程中,认为AI的逻辑主要由以下几个基本的部分构成:AIController、作为AI的Character、BehaviorTree、Blackboard、Tasks、Service以及装饰器。本文也将主要就这几个部分讲述在C++以及蓝图中如何实现、配合,用于总结记录个人的学习过程。
首先,假定我们的开发目标为:一个AICharacter在未发现玩家时进行随机移动,在发现玩家后朝着玩家奔跑,并当玩家在AICharacter的攻击范围内时进行攻击。
模块
想要实现以上所述的功能,必须要先添加"AIModule", "GameplayTasks", "NavigationSystem"这三个模块。AIModule是AI逻辑必须要用的,GameplayTasks是实现任务节点需要用的,NavigationSystem是在AI随机寻路的时候需要用的。
AZombie
我们创建一个继承自Character的类命名为Zombie作为我们的AI角色。在Zombie类中添加UBehaviorTree类型的变量,用作AI需要运行的行为树,并在蓝图中为其赋值。
同时需要一个攻击的函数,这里我选择使用播放攻击的蒙太奇动画实现攻击。
AZombieAIController
该类继承自AIController类。
为了让AI有视觉感知,需要添加感知组件。这里为什么选择在AIController内添加感知组件而不是在Character类中添加,是因为在AIcontroller类的构造函数中直接设置感知组件,避免更多的麻烦。
然后需要重写父类的OnPossess函数,该函数在AIController成功控制一个Character时触发,因此可在该函数内实现行为树的运行。
然后是设置是否看到角色,该逻辑由于是与感知组件挂钩,因此选择写在这里。而感知组件的更新是通过动态委托实现的,因此需要在源码内查看委托签名中函数需要的参数,并在函数前加上UFUNCTION的宏标记。函数内的获取黑板组件来通过名称设置黑板中的黑板键,并将感知的对象判断是否为玩家,赋值给Zombie类。
UBTT_FindRandomLocation
该类继承自UBTTaskNode,也就是任务节点。
首先需要一个黑板键选择器变量,用于和UE编辑器中的黑板相匹配。并在构造函数中设置其类型,并且必须要实现父类的InitializeFromAsset函数,主要作用是从指定的资产中加载和初始化任务节点的属性、变量和逻辑,确保任务节点在行为树中能够正确执行对黑板键选择器做出处理。
然后是寻路逻辑,核心是利用K2_GetRandomLocationInNavigableRadius函数,其中的RandomLocation参数是一个用于保存随机位置结果的参数。并把这个结果设置给黑板键选择器,这样就可以在行为树中将数据传给编辑器中的黑板键了,可用于后续移动到该位置的逻辑。后续两个参数分别代表中心位置和半径。
UBTT_FindPlayerLocation
然后是寻找玩家位置的任务节点,同样继承自UBTTaskNode。
要想追击玩家,自然需要一个追击目标或者追击位置,那么这里有两个选择:一个是告诉AI一个Location,另一个是告诉AI一个指向目标的指针。第一种,知道目标Location的话,则可以调用MoveTo来到达位置,但是此方法有一个弊端,就是只有在到达Location的时候才会返回成功,所以会出现玩家已经移动但AI还在前往玩家上一个位置的情况。因此,这里我选择直接传一个指向玩家的指针,所以AI需要接触玩家才会返回成功。然后对黑板键的处理和上面同理。
然后是在任务开始时,将玩家指针传给黑板键。这里我已经在Zombie类中获取了玩家指针,这里只需要拿到就行。
接下来直接在行为树中调用自带的MoveTo向目标移动,记得将黑板键设置为创建的Player。
UBTS_CheckDistance
创建一个服务,该类继承自UBTService。
有了向玩家移动,那么下一步就是向玩家攻击,而攻击就需要有一个范围判定,判定玩家是否进入了攻击距离,这就是这个类的作用。重写父类的TickNode,用于在tick中检测。函数内检测的核心是利用GetDistanceTo,来计算AI与玩家的距离,若小于指定值,则将黑板键bool值设置为对应值。
UBTT_Attack
该类继承自UBTTaskNode。
在检测到进入攻击范围后,就需要进行攻击的行为了。攻击主要方式为播放攻击的蒙太奇动画,至于后期想应用伤害可以通过攻击碰撞的回调去做。
行为树
在C++各类节点写好后就可以搭建行为树了,如图,在所有逻辑的最上层为一个selector,左侧连接随机寻路,右侧链接追击玩家,因此在发现玩家后,由于黑板键的判断 ,即可走右边。同时selector下面为写好的距离判断。
至此,完成本文的所有目标逻辑。