13.4 GAS与攻击
创始人
2024-02-01 13:43:59

目录

      • 1. 由GA砍出的第一刀
      • 2. 挥剑时的命中检测
      • 3. 完善:UI显示当前血量
      • 参考:

1. 由GA砍出的第一刀

有了前面章节的经验,我们可以很容易创建一个专用于攻击的GA:

在这里插入图片描述

其中PlayMontageAndWait任务节点负责攻击动画及相应回调的绑定。

在这里插入图片描述
但是仅仅这样并不足以支撑整个技能,因为缺少伤害的触发和判定。比如说技能触发,砍中一的一刻,应该要有伤害GE的触发,可以使用射线检测进行砍中事件的检测,然后发送相应事件。所以最优先的,需要扩展技能任务PlayMontageAndWait的功能,使其支持更多类型的回调(扩展为PlayMontageAndWaitForEvent,使其具备监听特定事件的特性)。

参照GASDocumentation的PlayMontageAndWaitForEvent的写法:

#pragma once#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "AT_PlayMontageAndWaitForEvent.generated.h"DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FPlayMontageAndWaitForEventDelegate, FGameplayTag, EventTag, FGameplayEventData, EventData);/*** */
UCLASS()
class INSIDEGAS_API UAT_PlayMontageAndWaitForEvent : public UAbilityTask
{GENERATED_BODY()public:UAT_PlayMontageAndWaitForEvent(const FObjectInitializer& ObjectInitializer);virtual void Activate() override;virtual void ExternalCancel() override;virtual FString GetDebugString() const override;virtual void OnDestroy(bool AbilityEnded) override;UPROPERTY(BlueprintAssignable)FPlayMontageAndWaitForEventDelegate OnCompleted;UPROPERTY(BlueprintAssignable)FPlayMontageAndWaitForEventDelegate OnBlendOut;UPROPERTY(BlueprintAssignable)FPlayMontageAndWaitForEventDelegate OnInterrupted;UPROPERTY(BlueprintAssignable)FPlayMontageAndWaitForEventDelegate OnCancelled;UPROPERTY(BlueprintAssignable)FPlayMontageAndWaitForEventDelegate EventReceived;UFUNCTION(BlueprintCallable, Category = "Ability|Tasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))static UAT_PlayMontageAndWaitForEvent* PlayMontageAndWaitForEvent(UGameplayAbility* OwningAbility,FName TaskInstanceName,UAnimMontage* MontageToPlay,FGameplayTagContainer EventTags,float Rate = 1.f,FName StartSection = NAME_None,bool bStopWhenAbilityEnds = true,float AnimRootMotionTranslationScale = 1.f);private:UPROPERTY()UAnimMontage* MontageToPlay;UPROPERTY()FGameplayTagContainer EventTags;UPROPERTY()float Rate;UPROPERTY()FName StartSection;UPROPERTY()float AnimRootMotionTranslationScale;UPROPERTY()bool bStopWhenAbilityEnds;bool StopPlayingMontage();UAbilitySystemComponent* GetTargetASC();void OnMontageBlendingOut(UAnimMontage* Montage, bool bInterrupted);void OnAbilityCancelled();void OnMontageEnded(UAnimMontage* Montage, bool bInterrupted);void OnGameplayEvent(FGameplayTag EventTag, const FGameplayEventData* Payload);FOnMontageBlendingOutStarted BlendingOutDelegate;FOnMontageEnded MontageEndedDelegate;FDelegateHandle CancelledHandle;FDelegateHandle EventHandle;
};

相应的cpp文件:

#include "AT_PlayMontageAndWaitForEvent.h"#include "AbilitySystemComponent.h"
#include "AbilitySystemGlobals.h"
#include "GameFramework/Character.h"UAT_PlayMontageAndWaitForEvent::UAT_PlayMontageAndWaitForEvent(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
{Rate = 1.f;bStopWhenAbilityEnds = true;
}UAbilitySystemComponent* UAT_PlayMontageAndWaitForEvent::GetTargetASC()
{return Cast(AbilitySystemComponent);
}void UAT_PlayMontageAndWaitForEvent::OnMontageBlendingOut(UAnimMontage* Montage, bool bInterrupted)
{if (Ability && Ability->GetCurrentMontage() == MontageToPlay){if (Montage == MontageToPlay){AbilitySystemComponent->ClearAnimatingAbility(Ability);// Reset AnimRootMotionTranslationScaleACharacter* Character = Cast(GetAvatarActor());if (Character && (Character->GetLocalRole() == ROLE_Authority ||(Character->GetLocalRole() == ROLE_AutonomousProxy && Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalPredicted))){Character->SetAnimRootMotionTranslationScale(1.f);}}}if (bInterrupted){if (ShouldBroadcastAbilityTaskDelegates()){OnInterrupted.Broadcast(FGameplayTag(), FGameplayEventData());}}else{if (ShouldBroadcastAbilityTaskDelegates()){OnBlendOut.Broadcast(FGameplayTag(), FGameplayEventData());}}
}void UAT_PlayMontageAndWaitForEvent::OnAbilityCancelled()
{if (StopPlayingMontage()){// Let the BP handle the interrupt as wellif (ShouldBroadcastAbilityTaskDelegates()){OnCancelled.Broadcast(FGameplayTag(), FGameplayEventData());}}
}void UAT_PlayMontageAndWaitForEvent::OnMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{if (!bInterrupted){if (ShouldBroadcastAbilityTaskDelegates()){OnCompleted.Broadcast(FGameplayTag(), FGameplayEventData());}}EndTask();
}void UAT_PlayMontageAndWaitForEvent::OnGameplayEvent(FGameplayTag EventTag, const FGameplayEventData* Payload)
{if (ShouldBroadcastAbilityTaskDelegates()){FGameplayEventData TempData = *Payload;TempData.EventTag = EventTag;EventReceived.Broadcast(EventTag, TempData);}
}UAT_PlayMontageAndWaitForEvent* UAT_PlayMontageAndWaitForEvent::PlayMontageAndWaitForEvent(UGameplayAbility* OwningAbility,FName TaskInstanceName, UAnimMontage* MontageToPlay, FGameplayTagContainer EventTags, float Rate, FName StartSection, bool bStopWhenAbilityEnds, float AnimRootMotionTranslationScale)
{UAbilitySystemGlobals::NonShipping_ApplyGlobalAbilityScaler_Rate(Rate);UAT_PlayMontageAndWaitForEvent* MyObj = NewAbilityTask(OwningAbility, TaskInstanceName);MyObj->MontageToPlay = MontageToPlay;MyObj->EventTags = EventTags;MyObj->Rate = Rate;MyObj->StartSection = StartSection;MyObj->AnimRootMotionTranslationScale = AnimRootMotionTranslationScale;MyObj->bStopWhenAbilityEnds = bStopWhenAbilityEnds;return MyObj;
}void UAT_PlayMontageAndWaitForEvent::Activate()
{if (Ability == nullptr){return;}bool bPlayedMontage = false;UAbilitySystemComponent* GDAbilitySystemComponent = GetTargetASC();if (GDAbilitySystemComponent){const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();if (AnimInstance != nullptr){// Bind to event callbackEventHandle = GDAbilitySystemComponent->AddGameplayEventTagContainerDelegate(EventTags, FGameplayEventTagMulticastDelegate::FDelegate::CreateUObject(this, &UAT_PlayMontageAndWaitForEvent::OnGameplayEvent));if (GDAbilitySystemComponent->PlayMontage(Ability, Ability->GetCurrentActivationInfo(), MontageToPlay, Rate, StartSection) > 0.f){// Playing a montage could potentially fire off a callback into game code which could kill this ability! Early out if we are  pending kill.if (ShouldBroadcastAbilityTaskDelegates() == false){return;}CancelledHandle = Ability->OnGameplayAbilityCancelled.AddUObject(this, &UAT_PlayMontageAndWaitForEvent::OnAbilityCancelled);BlendingOutDelegate.BindUObject(this, &UAT_PlayMontageAndWaitForEvent::OnMontageBlendingOut);AnimInstance->Montage_SetBlendingOutDelegate(BlendingOutDelegate, MontageToPlay);MontageEndedDelegate.BindUObject(this, &UAT_PlayMontageAndWaitForEvent::OnMontageEnded);AnimInstance->Montage_SetEndDelegate(MontageEndedDelegate, MontageToPlay);ACharacter* Character = Cast(GetAvatarActor());if (Character && (Character->GetLocalRole() == ROLE_Authority ||(Character->GetLocalRole() == ROLE_AutonomousProxy && Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalPredicted))){Character->SetAnimRootMotionTranslationScale(AnimRootMotionTranslationScale);}bPlayedMontage = true;}}else{UE_LOG(LogTemp, Warning, TEXT("UGDAbilityTask_PlayMontageAndWaitForEvent call to PlayMontage failed!"));}}else{UE_LOG(LogTemp, Warning, TEXT("UGDAbilityTask_PlayMontageAndWaitForEvent called on invalid AbilitySystemComponent"));}if (!bPlayedMontage){UE_LOG(LogTemp, Warning, TEXT("UGDAbilityTask_PlayMontageAndWaitForEvent called in Ability %s failed to play montage %s; Task Instance Name %s."), *Ability->GetName(), *GetNameSafe(MontageToPlay), *InstanceName.ToString());if (ShouldBroadcastAbilityTaskDelegates()){//ABILITY_LOG(Display, TEXT("%s: OnCancelled"), *GetName());OnCancelled.Broadcast(FGameplayTag(), FGameplayEventData());}}SetWaitingOnAvatar();
}void UAT_PlayMontageAndWaitForEvent::ExternalCancel()
{check(AbilitySystemComponent);OnAbilityCancelled();Super::ExternalCancel();
}void UAT_PlayMontageAndWaitForEvent::OnDestroy(bool AbilityEnded)
{// Note: Clearing montage end delegate isn't necessary since its not a multicast and will be cleared when the next montage plays.// (If we are destroyed, it will detect this and not do anything)// This delegate, however, should be cleared as it is a multicastif (Ability){Ability->OnGameplayAbilityCancelled.Remove(CancelledHandle);if (AbilityEnded && bStopWhenAbilityEnds){StopPlayingMontage();}}UAbilitySystemComponent* GDAbilitySystemComponent = GetTargetASC();if (GDAbilitySystemComponent){GDAbilitySystemComponent->RemoveGameplayEventTagContainerDelegate(EventTags, EventHandle);}Super::OnDestroy(AbilityEnded);}bool UAT_PlayMontageAndWaitForEvent::StopPlayingMontage()
{const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();if (!ActorInfo){return false;}UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();if (AnimInstance == nullptr){return false;}// Check if the montage is still playing// The ability would have been interrupted, in which case we should automatically stop the montageif (AbilitySystemComponent && Ability){if (AbilitySystemComponent->GetAnimatingAbility() == Ability&& AbilitySystemComponent->GetCurrentMontage() == MontageToPlay){// Unbind delegates so they don't get called as wellFAnimMontageInstance* MontageInstance = AnimInstance->GetActiveInstanceForMontage(MontageToPlay);if (MontageInstance){MontageInstance->OnMontageBlendingOutStarted.Unbind();MontageInstance->OnMontageEnded.Unbind();}AbilitySystemComponent->CurrentMontageStop();return true;}}return false;
}FString UAT_PlayMontageAndWaitForEvent::GetDebugString() const
{UAnimMontage* PlayingMontage = nullptr;if (Ability){const FGameplayAbilityActorInfo* ActorInfo = Ability->GetCurrentActorInfo();UAnimInstance* AnimInstance = ActorInfo->GetAnimInstance();if (AnimInstance != nullptr){PlayingMontage = AnimInstance->Montage_IsActive(MontageToPlay) ? MontageToPlay : AnimInstance->GetCurrentActiveMontage();}}return FString::Printf(TEXT("PlayMontageAndWaitForEvent. MontageToPlay: %s  (Currently Playing): %s"), *GetNameSafe(MontageToPlay), *GetNameSafe(PlayingMontage));
}

这样就得到一个全新的节点(相比于原生的PlayMontageAndWait,多了待接收事件的Tag的过滤器,以及相应的EventReceived回调):

在这里插入图片描述
如此,我们对劈砍技能进一步玩善,添加砍中后的一系列处理,如伤害,命中特效:

在这里插入图片描述
伤害的处理是在一个特定的伤害GE中去进行处理的,写法在前面章节提到过。

在这里插入图片描述
同样,我这里是简要处理,并没有为角色赋予攻击力抑或是伤害能力的属性,这里暂时使用一个hardcode来充当伤害。

命中特效是利用GC来实现,更具体来说,是利用GCN_Burst_Latent这个GAS内置的Actor类,我们将碰撞那一刻的位置和法线传入,令特效和声效等能够在希望的位置触发。更具体的,这里使用了一个Niagara特效,一个声效,一个CameraShake效果,还有一个ForceFeedback(手柄的震动力反馈):

在这里插入图片描述

GA里的逻辑部分到这里就算是准备就绪,我们下面要考虑砍中那一刻相关的内容了。

2. 挥剑时的命中检测

在武器下设置检测点(SceneComponent),只要适时根据依照这个轨迹线去探测是否集中某个物体就可以按照需要发送命中事件。
在这里插入图片描述

在这里插入图片描述
在BeginPlay做轨迹球的初始化,并利用变量WeaponTraceControl来作为检测的开关(默认关闭):

在这里插入图片描述
在这里插入图片描述
相应的Tick阶段利用WeaponTraceControl变量进行流程控制:

在这里插入图片描述
开启检测后,如果有命中,则利用SendGameplayEventToActor发送命中事件,这里使用DoOnce,意义在一次挥剑我只希望去单次触发命中事件;WeaponHitResult用来存储命中结果,供GA访问使用(如使用命中的位置,法线等信息)。此外在关闭检测后,需要完成一些清理工作。
在这里插入图片描述
最后利用Montage里的NotifyState来进行射线检测控制的开关:

在这里插入图片描述
主要就是在ReceivedNotifyBeginReceivedNotifyEnd两个时间节点控制WeaponTraceControl变量:

在这里插入图片描述
在这里插入图片描述
到这里,一个能造成伤害,能触发攻击命中效果的技能就完成了。

在这里插入图片描述

3. 完善:UI显示当前血量

血条的核心在于血量与最大血量的比值,这个比值和ProgressBar结合可以很快构建出血条的效果:

在这里插入图片描述
然后利用WidgetComponent挂载到角色身上简单配置即可。

在这里插入图片描述

参考:

属性 - Attributes

属性集 - Attribute Set

游戏效果 - Gameplay Effects

游戏技能 - Gameplay Abilities

技能任务 - Ability Tasks

游戏反馈 - Gameplay Cues

相关内容

热门资讯

北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...