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

TomLooman_ActionRoguelike_第十三章AI与框架扩展

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

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


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

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

2023_08_12

敲定AI,扩展框架:bot的死亡和布娃娃效果,bot对受伤的反应,静态函数,改进bot的受伤逻辑

 

我们在GameMode中生成bot时有一个判断,当世界中的bot过多时就再生成bot。之前我们把这个判断放在环境查询后,但是其实不需要生成bot时也就不需要进行环境查询了,所以我们把这个判断放到环境查询前。

 

我们希望bot和玩家一样在血量小于零后有死亡的效果,所以我们在AICharacter的OnHealthChanged成员函数中实现该功能。

这里判断bot死亡用到的静态函数下面会讲,但该函数只是简单地判断bot血量是否小于零。当bot死亡后我们主要做三件事:(1)停止行为树的运行,(2)启用bot的布娃娃效果,模拟死亡动画,(3)设置bot剩余生命时间。

停止行为树运行要通过控制bot的AIController完成。GetBrainComponent

获得AIController中负责行为的BrainComponent,它的StopLogic成员函数能够停止正在运行的逻辑,StopLogic的参数不影响结果,只是对为什么停止行为的comment。

bot的布娃娃效果要通过SetAllBodiesSimulatePhysics启用所有骨骼Mesh的物理模拟,且将碰撞预设从CharacterMesh改变为"Ragdoll",否则bot会直接穿过地板(见下面这两种碰撞的区别)。除了这种方法,使用动画蓝图也是可以的,就像我们处理玩家角色的死亡那样。

通过SetLifeSpan,我们可以改变Actor的剩余生命时间,时间耗尽后,该对象会被销毁。

另外,我们模仿玩家角色死亡后的处理,取消了胶囊体碰撞,防止尸体吞子弹,但是这样bot的尸体和玩家也没有碰撞了,可能会有点奇怪。

 

 

我们使用的bot的物理资产如下,它有一个个胶囊体和立方体处理碰撞,而不是用三角形。

到目前为止,我们的bot对玩家攻击是没有反馈的,我们希望当玩家攻击到bot时,bot能将该玩家视为TargetActor,从而进行后续的反击。“当受到攻击时将对方设置为TargetActor”这一逻辑自然地可以写在bot的OnHealthChanged中,因为OnHealthChanged的参数中就有触发属性组件中OnHealthChanged委托的Actor的指针。

因为在多处涉及到了访问bot的AIController的行为树的黑板,并对其中的TargetActor进行赋值的操作,所以我们将这一逻辑抽象为一个函数。

但是回想一下,目前为止,在属性组件类中负责广播OnHealthChanged委托的ApplyHealthChange中,OnHealthChanged.Broadcast的第一个参数,也就是InstigatorActor,仍然是nullptr。所以我们需要进行修改,传入真实的InstigatorActor,这样也就要求调用ApplyHealthChange需要多传入一个参数。

相应的,一些需要调用ApplyHealthChange的类,比如血包和子弹,都需要修改调用形式,血包和子弹的调用方式如下(血包其实与TargetActor无关,但为了形式统一还是加上了)。其中子弹类比较特殊,因为当bot被子弹攻击时,它不应该把子弹当做TargetActor,而是应该把子弹的Instigator当做TargetActor。

需要注意的是,这里bot攻击时并不检查TargetActor是否是友军,或者将某个Actor视为TargetActor时不检查该Actor是否是友军,所以可能会出现bot之间相互攻击的情况。

 

有一些重复使用的简单功能需要很长的代码去实现,比如获取属性组件成员变量,此时我们可以将这个功能抽象为一个静态函数。

我们在属性组件类中创建一个获得Actor对象的属性组件类成员函数的静态函数如下,函数的实现和子弹类或者医疗包类完全相同。

这样我们在想获得一个Actor的属性组件类成员变量时,就不需要先GetComponentByClass再Cast<USAttributeComponent>。

除此之外我们还在属性组件类中定义了一个静态函数判断Actor是否存活,其中的说明符meta = (DisplayName = "IsAlive")可以指定该函数在蓝图中显示的函数名称。需要注意的是,在IsActorAlive或者类似的函数中,对默认返回值的设置要格外小心。

 

现在的bot并不知道玩家是否死亡,如果玩家角色死亡倒下了,我们会发现bot仍然在攻击玩家角色,这是因为bot的TargetActor仍然是玩家角色,所以仍然会执行攻击逻辑。我们可以根据TargetActor是否存活,判断是否中断bot的攻击。我们在行为树的远程攻击节点中实现这个功能。

我们给bot的远程攻击加了一个判断,除了判断TargetActor是否有效外,还判断TargetActor是否活着,这里就用到了上面定义的静态函数。

理论上来说树的一个任务节点失败后,整棵树会重新运行,那么我们就需要清除上一轮中TargetActor这样的变量。但是这个操作并不归远程攻击任务节点管,所以我们不在这里实现。

 

现在bot对我们的攻击太精准的,我们希望能给bot的攻击加一些偏移,就像真人那样。我们依然在bot行为树的远程攻击节点中实现这个功能。

之前我们获得了bot的子弹生成旋转,我们可以在这个旋转上通过FMath::RandRange增加一些随机数来实现偏移的效果。随机数范围的上下限是我们创建的成员变量,在构造函数中初始化,并暴露在蓝图中。

这里我们并没有指定随机数种子。但随机数可以视为一个由随机数种子决定的函数,函数的自变量可以是游戏时间,应变量就是我们想要的随机数。


TomLooman_ActionRoguelike_第十三章AI与框架扩展的评论 (共 条)

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