97. UE5 GAS RPG 实现闪电链技能(二)
书接上回,如果没有查看上一篇文章的同砚推荐先看上一章,我们接着实现闪电链技能。在上一章末了,我们实现了闪电链的第一条链,能够正确表现特效,接下来,我们先实现它的音效和一些bug修复。
https://i-blog.csdnimg.cn/direct/dcf478e2f6214e6eb2b228a82b4cf03e.png
我们在多端网络里,只能查看到脚色播放了闪电链开始攻击,但是没有持续攻击动画,原因是因为在脚色身上的变量没有设置可复制,我们修改一下即可。
添加音效
接下来,我们添加在持续释放闪电链时播放的音效,我们在设置Niagara 系统之后,附加一个音效节点,设置其不会被自动销毁,并将其生成的组件作为变量保存下来,并在取消技能时方便销毁。
https://i-blog.csdnimg.cn/direct/9d62324cfd764775a7c6fe80b7baf4e4.png
接着就是在停止闪电链技能时,设置销毁
https://i-blog.csdnimg.cn/direct/854b64c026554001acebef2784504f36.png
我们还可以使用fadeOut来实现声音慢慢变小停止
https://i-blog.csdnimg.cn/direct/4e4c9e5d553a4e44a88ece8ff517916a.png
使用这个时,留意自动销毁要开启,它会在音频停止时自动销毁组件
https://i-blog.csdnimg.cn/direct/29e74a5a5af542c39a62b59e4a1aaa60.png
为技能增加新通道
我们为了将移动和技能拾取分开,以是增加一个新的通道
在引擎-碰撞这里增加一个新通道
https://i-blog.csdnimg.cn/direct/81a9e9ebf362417eb80578b7baa086a5.png
默认设置阻挡
https://i-blog.csdnimg.cn/direct/f2d0739d2d8b44d4bcfa7ff43267ecff.png
它是第二个,以是我们可以在代码中获取
https://i-blog.csdnimg.cn/direct/6d3f5c82bfa643449a389b14c0e32118.png
代码中设置默认通道就是18个,和我们 可以自定义18个匹配
https://i-blog.csdnimg.cn/direct/44dfcbdb55fb4da88d20c04e03319583.png
在鼠标拾取这里,修改为新建的通道
https://i-blog.csdnimg.cn/direct/efb5589738ef4ba5a87f993764500c15.png
记得将一些不可让技能拾取的对象相应关掉
https://i-blog.csdnimg.cn/direct/1dc96a1e527d4b86b88409f84b5b18d1.png
还有我们之前的碰撞拾取溶解效果的题目,会出现往返闪的题目,我们可以通过添加简易碰撞,并设置项目默认来办理。
https://i-blog.csdnimg.cn/direct/68a523844f5d4ff090843a1438a2dbfa.png
项目默认是在引擎-物理这里修改
https://i-blog.csdnimg.cn/direct/7494153ab3cc4333b6a11aa5476b525b.png
设置获取第一个闪电掷中
接下来,我们实现获取第一个掷中效果,因为鼠标选中的中间有大概被阻挡,我们需要考虑这个因素,以是,将其判断出来,如果鼠标拾取和武器发射位置中间有其它敌人被阻挡,那么,我们将更新它的目的位置。
起首我们在闪电链技能里增加一个新的函数,用于获取闪电链掷中的第一个敌人
/**
* 拾取闪电链命中的第一个目标
* @param BeamTargetLocation 鼠标点击目标位置
* @note 有可能中间会被阻挡,拾取的目标不是鼠标选中的目标
*/
UFUNCTION(BlueprintCallable)
void TraceFirstTarget(const FVector& BeamTargetLocation);
接着,我们实现此函数
void URPGBeamSpell::TraceFirstTarget(const FVector& BeamTargetLocation)
{
//确保所有者继承了战斗接口
if(OwnerCharacter && OwnerCharacter->Implements<UCombatInterface>())
{
//获取到武器
if(USkeletalMeshComponent* Weapon = ICombatInterface::Execute_GetWeapon(OwnerCharacter))
{
TArray<AActor*> ActorsToIgnore; //当前需要忽略的对象数组
ActorsToIgnore.Add(OwnerCharacter);// 将自身忽略掉
const FVector SocketLocation = Weapon->GetSocketLocation(FName("TipSocket")); //获取技能发射位置
FHitResult HitResult; //命中结果的纯粹对象
//通过武器发射位置和命中位置生成一条球形线,获取第一个命中的结果
UKismetSystemLibrary::SphereTraceSingle(
OwnerCharacter,
SocketLocation,
BeamTargetLocation,
10.f,
TraceTypeQuery1,
false,
ActorsToIgnore,
EDrawDebugTrace::ForDuration, //如果需要debug,将其设置ForDuration,如果关闭设置为None
HitResult,
true);
//如果有命中的结果,修改拾取结果
if(HitResult.bBlockingHit)
{
MouseHitLocation = HitResult.ImpactPoint;
MouseHitActor = HitResult.GetActor();
}
}
}
}
我们将生成闪电链的函数折叠为了一个函数,在调用GameplayCue之前,调用此函数
https://i-blog.csdnimg.cn/direct/3621711e415648dd8892b4bdd2b1d0f8.png
然后查看效果
https://i-blog.csdnimg.cn/direct/b7fdf7850ec54a7fbbeb71b4c850b075.png
接下来,我们想实现如果掷中的是脚色,我们让闪电链结束的位置是目的脚色的位置,如允许以放置发生偏斜。
我们修改技能的生成闪电链的逻辑代码,先将GameplayCue的代码创建出来,然后判断掷中的第一个actor是否继承战斗接口。
如果继承战斗接口,那么目的就是敌人,我们将GameplayCue的所有者设置为敌人,在敌人殒命时,Cue也会被销毁。如果不是,那就是目的是场景,我们还是使用技能的所有者。
https://i-blog.csdnimg.cn/direct/82341eed049d496ca82604f38202019a.png
接着修改技能结束时,我们需要销毁掉GameplayCue,由于根据目的是否继承战斗接口,GameplayCue的所有者也不同,以是我们要通过所有者和配置项举行删除。
这里我们上面将是否继承战斗接口的布尔值给保存了下来,如果继承,就从敌人身上删除,如果不是,我们将从所有者身上删除。
https://i-blog.csdnimg.cn/direct/36b88e62ebb14adeb3b044f62f2c0c3a.png
接着,我们修改GC,在GC里,我们在While Active里起首从配置项里获取到所需的资源。
https://i-blog.csdnimg.cn/direct/63f6b236c93d4207b26651fafa7049e3.png
然后我们生成粒子系统
https://i-blog.csdnimg.cn/direct/ff45c9a8ec8d42d4a6544225cd5e26db.png
接着对SourceObject举行判断,在技能里,如果目的是敌人,SourceObject就是敌人,如果不是它就没有继承战斗接口,我们可以以此为依据,来设置闪电链特效的尽头位置。
https://i-blog.csdnimg.cn/direct/11e80ab7eb7241cbacef848cef540613.png
至此,完成了对目的的修改。运行查看,如果攻击到目的,闪电链能准确的攻击敌人的位置
https://i-blog.csdnimg.cn/direct/cad6e211619846dda9f334aa2cb9f5c4.png
实现获取附近最近的几个目的
要实现闪电链,我们要获取到攻击目的最近的几个目的,然后再生成闪电链,以是,我们要增加一个函数来获取。
我们增加一个蓝图可调用函数,来获取返回获取到的目的
/**
* 通过技能命中目标获取扩散的敌人目标
* @param OutAdditionalTargets 返回获取到的最近的目标数组
*/
UFUNCTION(BlueprintCallable)
void StoreAdditionalTargets(TArray<AActor*>& OutAdditionalTargets);
定义闪电链最多可以攻击的敌人数目
UPROPERTY(EditDefaultsOnly, Category="FireBolt")
int32 MaxNumShockTargets = 5; //最大散射的闪电链数
我们在函数里,通过内置函数,来获取范围内的所有目的,然后再遍历这些目的,获取到最近的几个,这里我设置的调试代码
void URPGBeamSpell::StoreAdditionalTargets(TArray<AActor*>& OutAdditionalTargets)
{
TArray<AActor*> ActorsToIgnore; //遍历时忽略的数组
ActorsToIgnore.Add(GetAvatarActorFromActorInfo()); //忽略自身
ActorsToIgnore.Add(MouseHitActor); //忽略鼠标命中的的敌人
TArray<AActor*> OverlappingActors; //存放遍历结果的数组
//通过封装的函数获取到技能范围内散射的目标
URPGAbilitySystemLibrary::GetLivePlayersWithinRadius(
GetAvatarActorFromActorInfo(),
OverlappingActors,
ActorsToIgnore,
850.f,
MouseHitActor->GetActorLocation());
//int32 NumAdditionalTargets = FMath::Min(GetAbilityLevel() - 1, MaxNumShockTargets);
int32 NumAdditionalTargets = 5;
//通过自定义函数来获取最近的几个目标
URPGAbilitySystemLibrary::GetClosestTargets(NumAdditionalTargets, OverlappingActors, OutAdditionalTargets, MouseHitActor->GetActorLocation());
}
获取最近目的的函数是我封装在函数库里的函数
/**
* 获取距离目标位置最近的几个目标
* @param MaxTargets 获取最大目标的数量
* @param Actors 需要计算的目标数组
* @param OutClosestTargets 返回获取到的最近的目标
* @param Origin 计算的位置
*/
UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayMechanics")
static void GetClosestTargets(int32 MaxTargets, const TArray<AActor*>& Actors, TArray<AActor*>& OutClosestTargets, const FVector& Origin);
它通过使用一个while循环,直到获取到所有的所需的目的停止
void URPGAbilitySystemLibrary::GetClosestTargets(int32 MaxTargets, const TArray<AActor*>& Actors, TArray<AActor*>& OutClosestTargets, const FVector& Origin)
{
//如果数量过于少,直接返回原数组
if(Actors.Num() <= MaxTargets)
{
OutClosestTargets = Actors;
return;
}
TArray<AActor*> ActorsToCheck = Actors; //没有引用就是复制,复制一份用于遍历
int32 NumTargetFound = 0; //当前已经遍历出最近距离的个数
//循环遍历,直到获得足够数量的目标时停止
while (NumTargetFound < MaxTargets)
{
if(ActorsToCheck.Num() == 0) break; //如果没有可遍历内容,将跳出循环
double ClosestDistance = TNumericLimits<double>::Max(); //记录中心于目标的位置,如果有更小的将被替换,默认是最大
AActor* ClosestActor; //缓存当前最近距离的目标
for(AActor* PotentialTarget : ActorsToCheck)
{
//获取目标和中心的距离
const double Distance = (PotentialTarget->GetActorLocation() - Origin).Length();
//比对当前计算的位置是否小于缓存的位置
if(Distance < ClosestDistance)
{
//如果小于,将替换对应信息
ClosestDistance = Distance;
ClosestActor = PotentialTarget;
}
}
ActorsToCheck.Remove(ClosestActor); //从遍历数组中删除缓存的对象
OutClosestTargets.AddUnique(ClosestActor); //添加到返回的数组中
++ NumTargetFound; //递增数量
}
}
接着,我们编译打开蓝图,在技能结束时,通过节点获取附近的目的,并打印它的位置
https://i-blog.csdnimg.cn/direct/447a020aae9146cd9b8d315d112cadd8.png
正确表现
https://i-blog.csdnimg.cn/direct/73737e6aaaaf478b9b01cfd798616e05.png
接着,我们在结束时,打印拾取的范围,并增加敌人数目
https://i-blog.csdnimg.cn/direct/f24b4b0cb88044edac7d96e6c4a5bd80.png
然后查看是否能够正确查找到目的
https://i-blog.csdnimg.cn/direct/c643c4e01de9411aac782725930e215d.png
实现闪电弧效果
我们先实现闪电链的第一形态,闪电弧,在生成闪电链的函数里,我们设置完成掷中目的的以后,先对掷中目的判断,如果掷中的是场景,将不实现扩散效果。然后去获取最近的几个目的,生成扩展的链
https://i-blog.csdnimg.cn/direct/29ac2fc958f94db7a20f8ad04d785d52.png
AddShockLoopCueToAdditionalTarget函数是我们将获取的每个目的生成和掷中目的的一条闪电链,在节点函数里,我们创建一个Map将目的和配置项存储起来,方便技能结束时清除
https://i-blog.csdnimg.cn/direct/e9a7bf6700dd40b2bfd53ec3fabe0073.png
在函数里,我们将创建配置项,并添加到映射中,并生成GameplayCue
https://i-blog.csdnimg.cn/direct/48c13061fe3b41cda9181e2677731e3a.png
在技能结束之前,我们将通过目的和配置项将GameplayCue清除,并在末了清空Map映射
https://i-blog.csdnimg.cn/direct/011cd0c649bc4c82b3b20251222cfc44.png
RemoveAdditionalCue函数里,我们用到了之前保存的数组,并在Map映射里找到对应的配置项,举行清除。
https://i-blog.csdnimg.cn/direct/d63e68dc1c1d4f39ae96b2f363c7388d.png
接着运行查看效果,发现攻击到敌人时,能够正确生成闪电弧
https://i-blog.csdnimg.cn/direct/d90e70c60a59466a806074541adaa057.png
在攻击地面时,也能正确表现
https://i-blog.csdnimg.cn/direct/526dfaff00ca42039d8bf252ca609d2a.png
设置伤害和消耗冷却
我们实现了闪电弧的效果,接下来,我们将设置技能的相干伤害,技能蓝量消耗和冷却
起首我们在技能伤害数据表里增加一条闪电链的伤害
https://i-blog.csdnimg.cn/direct/30c1f282b0b74f8fab474c00ad990bba.png
设置到技能上,并设置好伤害类型
https://i-blog.csdnimg.cn/direct/21d25ba0abe64279a7f5cdeae7c86ae5.png
然后创建两个GameplayEffect
https://i-blog.csdnimg.cn/direct/a59628f0a24c4150b028144dab2b3ca7.png
设置给技能
https://i-blog.csdnimg.cn/direct/c79631b2887349ae973f79894ecf036f.png
在消耗这里,我们设置修改蓝量消耗,并设置对应的表格对蓝量举行消耗
https://i-blog.csdnimg.cn/direct/71b6f9b5df3446788d6cf34f6812b477.png
在表格中增加不同等级的蓝量消耗
https://i-blog.csdnimg.cn/direct/899ef562be2d4ab78f537da315e7a9e1.png
添加对应的冷却标签
https://i-blog.csdnimg.cn/direct/6d032d3676ba4f3a8ee37dedaef6d7db.png
这个标签在激活技能时,应用到释放者身上。
https://i-blog.csdnimg.cn/direct/b549b50572a945d2a86ece6733c9d066.png
处置惩罚消耗冷却和脚色殒命时的bug
接下来,我们要实现正确触发技能冷却,并在每次造成伤害时,扣除脚色的蓝量。并在目的殒命时,让特效也能够正常表现。我们将在技能的类里,增加两个回调,可以监听脚色在殒命时,对不同的脚色殒命举行处置惩罚。
我们之前实现了对脚色殒命时,会触发殒命委托,在释放技能时,我们绑定对应的委托即可。
https://i-blog.csdnimg.cn/direct/d783071de3c440feb6620d92c5eebacb.png
然后在RPGBeamSpell中,增加两个函数回调,在蓝图里可以去实现对应的函数,为什么区分两个函数,因为鼠标掷中的重要敌人殒命后,无法将技能分散出去,而额外的脚色殒命,我们只需要将其相干的处置惩罚即可。
/**
* 鼠标命中的敌人死亡处理
* @param DeadActor 命中敌人
*/
UFUNCTION(BlueprintImplementableEvent)
void PrimaryTargetDied(AActor* DeadActor);
/**
* 额外的敌人死亡处理
* @param DeadActor 额外敌人
*/
UFUNCTION(BlueprintImplementableEvent)
void AdditionalTargetDied(AActor* DeadActor);
然后在技能掷中的第一个脚色身上,我们绑定重要脚色的殒命回调,这里时通过战斗接口判断,并判断当前是否绑定了对应的
https://i-blog.csdnimg.cn/direct/72f8cadf29d54bde8fc0343714907eee.png
额外的目的逻辑相同,一样绑定对应的殒命回调
https://i-blog.csdnimg.cn/direct/5a0f3301e662403e85e9f5cd1938b525.png
我们还需要增加一个技能结束时,取消绑定,因为技能结束后,不能再举行对应的函数回调触发。我们增加一个函数,由于额外的目的没有保存,我们调用时需要传入额外的目的
/**
* 技能结束时调用
*/
UFUNCTION(BlueprintCallable)
void OnEndAbility(TArray<AActor*> AdditionalTargets);
在函数里,我们实现对重要目的和额外的目的的殒命委托的取消绑定
void URPGBeamSpell::OnEndAbility(TArray<AActor*> AdditionalTargets)
{
//取消主要目标的死亡回调
if(ICombatInterface* CombatInterface = Cast<ICombatInterface>(MouseHitActor))
{
CombatInterface->GetOnDeathDelegate().RemoveDynamic(this, &URPGBeamSpell::PrimaryTargetDied);
}
//取消额外目标的死亡回调
for(AActor* TargetActor : AdditionalTargets)
{
if(ICombatInterface* CombatInterface = Cast<ICombatInterface>(TargetActor))
{
CombatInterface->GetOnDeathDelegate().RemoveDynamic(this, &URPGBeamSpell::AdditionalTargetDied);
}
}
}
我们在蓝图中实现对应目的殒命回调,重要目的殒命,我们直接结束技能
https://i-blog.csdnimg.cn/direct/dcfcac6cc03c4df6ba703400343dba7e.png
在额外目的殒命时,我们清除对应的表现效果即可,目的殒命后,将无法对其脚色举行应用GE
https://i-blog.csdnimg.cn/direct/aa3359518f5f48bfb0c6bb33bb2190a3.png
完成以后,我们将修改闪电链的蓝图。
在触发闪电链时,我们增加对目的是否已殒命的判断,如果脚色已经殒命,将结束技能,场景的目的则没有这个题目。如果目的没有殒命,我们将在技能持续阶段,通过定时器,在肯定时间一直造成伤害。
https://i-blog.csdnimg.cn/direct/f12ca39dcd254e5e91f53236b73734cb.png
在应用伤害时,分别对重要目的和额外目的举行调用
https://i-blog.csdnimg.cn/direct/c46ab34577ab4882b969572192783278.png
然后通过创建GE实例应用给目的,这里对ASC判断,是因为有时目的的ASC不存在会报错。
https://i-blog.csdnimg.cn/direct/7371a13a15324276ab719d68e082ac74.png
接下来就是技能结束时,我们需要设置技能进入冷却,并清除定时器
https://i-blog.csdnimg.cn/direct/e94198dbf3ff4621897462531b5cbfae.png
在函数PrepareToEndAbility中,我们将脚色设置为可移动,并取消脚色殒命绑定(要不然技能结束,目的殒命时,也会触发对应回调),并将目的身上的GameplayCue给清除,清除效果表现,并将对应数组清空。
https://i-blog.csdnimg.cn/direct/6b68f448a642437eb8d89ef5cd038c7f.png
实现技能延迟结束
如果我们直接释放技能并松开时,技能还没有播放就结束,以是,这里我们将实现保证技能最短时间,在技能播放完成攻击并播放特效后结束。
以是,我们增加一个变量,来设置技能最小执行时间
https://i-blog.csdnimg.cn/direct/5f500bdaf31b40019b3ac7e645afbbf2.png
然后在技能释放健抬起时,我们能够获取到抬起时间,通过和最小时间比对,如果时间没到,我们将延迟到最小时间,再结束技能。
https://i-blog.csdnimg.cn/direct/f737b7d98e0d4c87b64f0fd1d203f2d7.png
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]