欢迎光临散文网 会员登陆 & 注册

TomLooman_ActionRoguelike_第十一章自定义任务和EQS的AI

2023-08-18 15:14 作者:别叫我小红  | 我要投稿

该专栏用于保存对TomLooman的ActionRoguelike项目的学习笔记,学习过程中的思考与记录不一定准确。


教程参考:https://github.com/tomlooman/ActionRoguelike

基于UE5.0的项目实现:https://github.com/CarolBaggins2023/TomLooman_ActionRogueLike_Tutorial.git

自定义任务和EQS的中级AI:用于远程攻击的自定义C++行为树任务,环境查询,感应组件,改进机器人动画

 

我们之前在行为树中插入了MoveTo和Wait这两个自带的节点,除了自带节点外,我们还可以自定义行为树节点,并插入行为树。

 

像MoveTo和Wait这样表示具体行为的节点称为Task节点,我们在添加节点时Task那一栏下面可以找到它们。

 

我们可以自定义远程攻击的任务节点,先创建BTTaskNode的C++类。


在BTTaskNode基类中,这里用到了成员函数virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory);

,该函数表示开始该节点代表的任务。

我们在自定义的行为树任务节点类中覆盖它。我们要进行远程攻击,这里的逻辑是,远程攻击其实就是Spawn子弹类实例(参考角色的远程攻击),而Spawn时需要Spawn的Location和Rotation,Location来自机器人,Rotation来自机器人和目标(这里就是玩家)的Location的差值。所以我们要在任务节点类的成员函数中访问机器人和玩家。因为ExecuteTask的参数中有使用该节点的行为树,可以从该行为树入手,那么就类似于之前的Service节点(行为树作为TickNode函数参数),(1)通过行为树访问使用行为树的AI控制器,再通过AI控制器访问被控制的机器人,(2)通过行为树访问行为树使用的黑板,通过黑板得到TargetActor(到这里为止,TargetActor是在AIController的BeginPlay中被定义的)。

ExecuteTask函数返回行为树节点的运行结果,包括Succeeded, Failed or InProgress,这里我们将在C++类中定义了,但是在蓝图中没有赋值资产的情况作为Failed。


因为实现中用到了子弹类型,所以我们也要将子弹类型作为类的成员变量,并在编辑器中进行赋值。


 

在创建自定义的任务节点后,我们可以在行为树中执行它。我们想实现这样一个效果,当玩家在机器人射程内时,机器人攻击三次,然后进入一段时间的冷却,我们先实现了下面的行为树分支,

其中的Cooldown节点控制它的子树的执行间隔,Loop节点控制它子树的循环执行次数。但是这里Wait和RangedAttack只执行了一次,因为Cooldown节点认为一次循环就算是一次执行,所以执行一次Wait和RangedAttack马上进入冷却。因此,我们不能把Cooldown节点和Loop节点放在一起。而是要采用下面的结构,

这里的第一个节点时Sequence或Selector都可以,因为之后是单分支且不做判断。这样的结构能够实现我们的目标。

另外,这里的黑板节点要设置对低优先级的监听中断,也就是一旦玩家进入机器人射程,就执行攻击,而不是等Wait5s或者寻找玩家的分支执行完成。也可以同时设置对自身的监听中断,这样一来,当机器人在攻击时,如果玩家走出攻击范围,则机器人停止攻击。

 

我们可以让我们的机器人时刻面朝玩家,这可以通过行为树中的Focus服务实现,也就是说Focus也类似于每个一段时间执行一次的函数。

 

 

现在我们的机器人的行动路径都是笔直地朝玩家走来,这是比较恐怖的。所以我们想对此进行修改,让机器人先在一定范围内运动,当靠近玩家后再执行上面定义的攻击任务。我们通过UE的Environment Query System(EQS)实现这个功能。

EQS可以对场景中的信息(比如位置)进行打分,根据打分结果对信息进行筛选,然后选择合适的信息给AI机器人使用。例如在这里,我们可以通过EQS对环境中的一些位置进行打分,打分的依据是该位置与玩家的距离,然后筛选掉距离玩家太远的一些位置,最后将距离玩家最近的位置或者随机一个比较近的位置作为行为树中MoveTo节点的目标。

 

我们可以在Artificial Intelligence里新建Environment Query,

环境查询的结构和行为树有点像,我们可以从根节点引出Generator节点,Generator节点的作用是在环境中生成一些表示位置的点或者Actor,来供后续打分和筛选,这里我们选择了Points: Donut,来生成圆弧分布的点。

在DonutGenerator中,我们可以选择点所在圆弧的内外半径、方向、角度等。

接着我们可以在Generator中添加一些Test用来对Generator产生的点进行筛选,这里我们先讲Distance。

DistanceTest根据生成点与指定对象的距离,对生成点进行筛选。这里的指定对象可以是使用该EQS的对象(我们之后会将它作为行为树的节点,那么就是使用带有这个EQS的行为树的机器人),也可以是我们自定义的环境查询背景(后面讲)。

这里的Test Purpose项包括筛选、打分、打分并筛选,单纯打分不会筛去生成的点。筛选可以根据距离的上下限等,打分可以设置参数。

 

创建完环境查询后,我们可以在行为树中添加执行环境查询的节点,

我们用刚刚创建的环境查询对EQS节点进行赋值。这里的RunMode指在通过筛选的节点中选择哪一个,可以选分数最高的,或者分数前5%之类的。这里的BlackboardKey很关键,它是EQS与任务直接关联的一个参数,决定了EQS最终选择的点(或者Actor)要赋值给行为树的黑板中的哪个参数。

因为这里我们在EQS中生成的是点(FVector),所以我们将其赋给MoveToLocation,并在MoveTo节点中使用。

此外我们希望,当玩家在机器人攻击范围内,但机器人的攻击处于冷却时,机器人会随意地走动,所以我们删除了MoveToSequence上的黑板节点,这样即使WithinAttackRange为true,也会执行下面的MoveTo任务。

 

在上面的实现中,我们在EQS中以机器人为中心生成了一些点作为机器人运动的目标,并根据这些点与机器人的距离进行筛选。但是我们其实应该在玩家周围生成这些点,这样才能让机器人靠近玩家。但是DonutPoints的Center参数选项中并没有玩家,所以我们要自定义一个Environment Query Context(环境查询背景)。

我们通过创建蓝图的方式自定义一个环境查询背景,

我们可以在其中覆盖一些函数,根据我们想要这个环境查询返回什么来决定(位置或者是Actor)。因为这里我们想要得到对玩家的查询结果,而不仅是对玩家位置的查询结果,所以覆盖ProvideSingleActor。

蓝图中的逻辑和机器人远程攻击中访问玩家的逻辑相同,都是通过AIController获得行为树的黑板,再获取黑板中的TargetActor变量。

然后我们就可以在EQS中使用这个ContextQueryContext了,我们将它作为DonutPoints的中心,以及修改圆弧方向。

 

现在我们已经实现了在玩家周围生成一些点,机器人向这些点移动,如果玩家进入攻击范围则执行攻击任务。但我们不希望机器人与目标点之间有障碍物,比如一堵墙,此时我们可以使用TraceTest节点。

Trace节点类似于我们之间制作物体交互功能时使用的LineTrace,可以检测从某个环境查询背景出发(或到某个环境查询背景截止)的射线途中是否与其他物体发生碰撞。当Filter中的Bool Match为true时,则有碰撞的点被保留,否则无碰撞的点被保留。

 


到目前为止,机器人会尝试逐渐靠近玩家,但是这个玩家是我们在AIController的Beginplay中通过UGameplayStatics::GetPlayerPawn(this, 0)实现的。我们希望机器人能去靠近自己“看见”的玩家。为了让机器人拥有“视觉”,我们可以使用PawnSensing组件。(还有一种方法是用AIPerception,相比之下PawnSensing虽然比较老,但是比较容易)

我们将PawnSensing组件作为AICharacter的成员变量。UPawnSensingComponent中声明了一个单参数委托,监听拥有该组件的对象是否看见其他Pawn,并在触发后进行广播。所以我们的AICharacter中需要有一个成员函数与该委托绑定,并在该成员函数中完成对行为树的黑板中TargetActor变量的赋值。

以下是该成员函数的实现,并且我们在PostInitializeComponents中完成函数与委托的绑定(比在构造函数中绑定更安全)。

因为在AICharacter中完成了对TargetActor的赋值,所以AIController中基于UGameplayStatics::GetPlayerPawn(this, 0)的赋值就可以取消了。

 

在机器人的蓝图中,我们可以对PawnSensing组件的参数,比如视野大小等,进行调整。

 

但是此时出现了一个问题,就是我们只有在第一看见玩家后,机器人的TargetActor才有值。在此之前,因为行为树黑板的TargetActor为空,那么我们自定义的环境查询背景返回空值,行为树中的EQS无法运行,黑板中的MoveToLocation就没有值,MoveTo任务就无法运行,最终机器人也就不会运动。

所以,当黑板的TargetActor为空时,我们在自定义的环境查询背景中要直接给TargetActor一个值,如下

 

 

另外是一些角色动画的内容。在移动组件中,我们可以选择UseControllerDesiredRotation来让角色的转向不那么突兀,而是有一定过度,更加自然。在选择UseControllerDesiredRotation的同时,我们要取消UseControllerRotationYaw。这两者是冲突的,因为它们都控制角色的Rotation,同样冲突的还要加上前面玩家角色控制中提到的GetCharacterMovement()->bOrientRotationToMovement。


TomLooman_ActionRoguelike_第十一章自定义任务和EQS的AI的评论 (共 条)

分享到微博请遵守国家法律