斯坦福UE4C++课程P46-P49AI、EQS再生逻辑
首先回顾任务3
为了能够拓展Health Potion,我们可以写一个PowerupActor基类,供生命药水类继承。
这个基类继承自AActor和ISGameplayInterface,设置一个Mesh组件,具体逻辑在Interact_implementation函数实现。
然后我们要让OnHealthChanged事件发生时的伤害显示到视口中(使用Damage控件蓝图变量的Expose on Spawn)。
我们在Damage控件蓝图新建变量DamageDelta,勾选Expose on Spawn。此时创建Damage控件节点多出来DamageDelta变量可赋值,用角色类蓝图的OnHealthChanged(AttributeComp)事件的Delta连上DamageDelta即可。

在Damage控件蓝图给伤害文本绑定函数,只需把DamageDelta作为返回值即可。

此时所受伤害值显示在角色上。


然后是布置作业4

我们之后的工作会基于视频内容和作业内容,所以需要保留在项目中。
我们需要给AI添加低生命值时前往远离玩家位置,并缓慢回血功能(60秒一次)。
首先我们给AI添加掉血、死亡功能(具体方法参考前面给角色添加的同样的功能,牵扯到Damage控件蓝图、动画蓝图),复制作者github的Minion模型发现动画蓝图中有为我们做好的一部分功能,直接用即可。
AI死亡逻辑简单地在AI角色蓝图用:

接下来正式做作业4
依次添加Service、EQS、Task。
行为树如下:

首先是检查低血量的Service:
先在AIController类中给SelfActor黑板键赋值为GetPawn(),然后在检查低血量Service类cpp文件中:(我们在Attribute类中新写了若干函数GetHealth、GetHealthMax、SetHealthToMax以便于判断血量、设置血量)
回到UE编辑器,新建EQS,让AI前往远离玩家、靠近自己的地点。

新建Task,ExecuteTask函数如下:
现在,除了60秒冷却时间没做之外,AI行为树从根执行,检查到LowHealth时就会走到特定位置回满血然后等待8秒,重新从根开始走行为树(设置了Lower Priority观察者中止)
60秒冷却目前不熟悉行为树节点,先跳了。

这一章我们用EQS生成AI、丰富AI的功能(死亡(stop behavior、'ragdoll' physics)、受击逻辑(another 'sense'))
首先新建一个EQS,命名为Query_FindBotSpawn,从Root拖出一个Donut。后面我们将从Gamemode类中生成AI,这里先这样。
新建一个QueryContext_BlueprintBase继承蓝图类QueryContext_AllPlayers,重载Provide Actors Set(我们希望返回所有的玩家players)

Get All Actors Of Class遍历关卡中所有Actor,把SCharacter放入数组中作为返回值,效率低,但此方法比较便利,况且我们的关卡简单,没有特别多Actor。
把Query_FindBotSpawn的Donut节点的center选为上面的QueryContext。

现在我们在场景中复制我们的角色,并新建一个EQSTestingPawn蓝图类,拖入场景,选择其EQS为Query_FindBotSpawn。现在可以看到每个角色周围300-1000距离有3个8点环作为生成AI的位置:

我们可以微调生成位置,打开EQS,选中Donut节点,半径选1000-3000,勾上Use Spiral Pattern:

现在一个角色的AI生成点可能离另外一个角色过于近,所以我们在EQS添加一个test,选择在AllPlayers的至少1000距离之外生成:

现在,我们按P查看导航网格,发现白色墙下面也有导航网格,所以AI生成点也可能出现在里边,导致AI生成时被卡在里面。
最理想的办法,可以从左侧拖进场景一个Nav Modifier Volume,覆盖掉墙里面的导航网格:

此时如果AI生成点落在墙里面,我们让它尝试去最近的导航点。打开EQS,选择Donut节点->Project Data->Trace Mode为Navigation。此时墙里的AI生成点只剩下墙外的了。
第二种方法代价较高,我们在EQS里添加PathFinding的test:

如果有一排火焰在AI前面,用这种方法可以让AI删掉经过火焰的路径。这里我们用这种方法也可以删掉墙里的生成点;同时,可以避免第一种方法忘记刷掉墙里的导航网格。

接下来我们添加一个游戏模式类,游戏模式类是gameplay框架,包含游戏规则,指定了从哪个Pawn和Controller开始游戏,所以可以通过选择不同GameMode来控制不同的角色集合。
新建C++类->选择Game Mode Base,命名SGameModeBase。
tip:gamemode类没有BeginPlay函数,因为它负责调用世界中所有Actor的BeginPlay。所以用StartPlay()代替。
下面我们在Gamemode类写AI生成逻辑。
.h
.cpp
编译后,回到UE编辑器,创建SGameModeBase继承的蓝图类GameModeBP,选择Minion Class为MinionRangedBP,Spawn Bot Query为之前的生成AI出生点的EQS,保存。最后在World Settings中选择Game Mode为GameModeBP(意思是针对当前关卡,选择该游戏模式)。
现在运行游戏,除了开始游戏时的AI外,隔2秒生成的AI站在原地不动。我们只需进入MinionRangedBP,调整Auto Possess AI为放置在世界&&生成即可。

也可以在AI角色C++类构造函数设置该选项:
现在运行游戏,AI会不断生成,没有数量限制。我们添加上限,并添加CurveFloat,让上限随着时间的变化而变化。内容浏览器新建CurveFloat,命名为DifficultyCurve,添加4点,0秒1个敌人,5秒2个,10秒5个,60秒10个,还可以调整函数曲线的离散性(防止四舍五入)。

我们在GamemodeC++类.h文件添加CurveFloat资产:
在.cpp文件设置逻辑:
该节作者有个bug,因为没有给AI添加Attribute组件而用了其中的isAlive()函数,而我之前作业做好了,所以编译、在蓝图分配资产后正常运行。现在隔2秒生成AI(不超过当前时间的上限):
