13.4 GAS与攻击
创始人
2024-02-01 14:50:48
0

目录

      • 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

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...