I want to expand PlayMontageAndWaitForEven
function (refered to https://github.com/tranek/GASDocumentation/blob/5.2/Source/GASDocumentation/Private/Characters/Abilities/AbilityTasks/GDAT_PlayMontageAndWaitForEvent.cpp#L55
), and get the Notify trigged in montage, but it does’n work.
The code:
AT_PlayMontageAndWaitForEvent.h
#pragma once
#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "AT_PlayMontageAndWaitForEvent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FPlayMontageAndWaitForEventDelegate, FGameplayTag, EventTag, FGameplayEventData, EventData, FName, NotifyName);
/**
*
*/
UCLASS()
class DEMO3_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;
UPROPERTY(BlueprintAssignable)
FPlayMontageAndWaitForEventDelegate OnNotifyBegin;
UPROPERTY(BlueprintAssignable)
FPlayMontageAndWaitForEventDelegate OnNotifyEnd;
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);
bool IsNotifyValid(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload) const;
UFUNCTION()
void OnNotifyBeginReceived(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload);
UFUNCTION()
void OnNotifyEndReceived(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload);
private:
UPROPERTY()
UAnimMontage* MontageToPlay;
UPROPERTY()
FGameplayTagContainer EventTags;
UPROPERTY()
float Rate;
UPROPERTY()
FName StartSection;
UPROPERTY()
float AnimRootMotionTranslationScale;
UPROPERTY()
bool bStopWhenAbilityEnds;
int32 MontageInstanceID;
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;
};
AT_PlayMontageAndWaitForEvent.cpp
#include "AT_PlayMontageAndWaitForEvent.h"
#include "AbilitySystemComponent.h"
#include "AbilitySystemGlobals.h"
#include "GameFramework/Character.h"
UAT_PlayMontageAndWaitForEvent::UAT_PlayMontageAndWaitForEvent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer), MontageInstanceID(INDEX_NONE)
{
Rate = 1.f;
bStopWhenAbilityEnds = true;
}
UAbilitySystemComponent* UAT_PlayMontageAndWaitForEvent::GetTargetASC()
{
return Cast<UAbilitySystemComponent>(AbilitySystemComponent);
}
void UAT_PlayMontageAndWaitForEvent::OnMontageBlendingOut(UAnimMontage* Montage, bool bInterrupted)
{
if (Ability && Ability->GetCurrentMontage() == MontageToPlay)
{
if (Montage == MontageToPlay)
{
AbilitySystemComponent->ClearAnimatingAbility(Ability);
// Reset AnimRootMotionTranslationScale
ACharacter* Character = Cast<ACharacter>(GetAvatarActor());
if (Character && (Character->GetLocalRole() == ROLE_Authority ||
(Character->GetLocalRole() == ROLE_AutonomousProxy && Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalPredicted)))
{
Character->SetAnimRootMotionTranslationScale(1.f);
}
}
}
if (bInterrupted)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
FName tmp;
OnInterrupted.Broadcast(FGameplayTag(), FGameplayEventData(), tmp);
}
}
else
{
if (ShouldBroadcastAbilityTaskDelegates())
{
FName tmp;
OnBlendOut.Broadcast(FGameplayTag(), FGameplayEventData(), tmp);
}
}
}
void UAT_PlayMontageAndWaitForEvent::OnAbilityCancelled()
{
if (StopPlayingMontage())
{
// Let the BP handle the interrupt as well
if (ShouldBroadcastAbilityTaskDelegates())
{
FName tmp;
OnCancelled.Broadcast(FGameplayTag(), FGameplayEventData(), tmp);
}
}
}
void UAT_PlayMontageAndWaitForEvent::OnMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
if (!bInterrupted)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
FName tmp;
OnCompleted.Broadcast(FGameplayTag(), FGameplayEventData(), tmp);
}
}
EndTask();
}
void UAT_PlayMontageAndWaitForEvent::OnGameplayEvent(FGameplayTag EventTag, const FGameplayEventData* Payload)
{
if (ShouldBroadcastAbilityTaskDelegates())
{
FGameplayEventData TempData = *Payload;
TempData.EventTag = EventTag;
FName tmp;
EventReceived.Broadcast(EventTag, TempData, tmp);
}
}
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<UAT_PlayMontageAndWaitForEvent>(OwningAbility, TaskInstanceName);
MyObj->MontageToPlay = MontageToPlay;
MyObj->EventTags = EventTags;
MyObj->Rate = Rate;
MyObj->StartSection = StartSection;
MyObj->AnimRootMotionTranslationScale = AnimRootMotionTranslationScale;
MyObj->bStopWhenAbilityEnds = bStopWhenAbilityEnds;
return MyObj;
}
bool UAT_PlayMontageAndWaitForEvent::IsNotifyValid(FName NotifyName,
const FBranchingPointNotifyPayload& BranchingPointNotifyPayload) const
{
return ((MontageInstanceID != INDEX_NONE) && (BranchingPointNotifyPayload.MontageInstanceID == MontageInstanceID));
}
void UAT_PlayMontageAndWaitForEvent::OnNotifyBeginReceived(FName NotifyName,
const FBranchingPointNotifyPayload& BranchingPointNotifyPayload)
{
if (IsNotifyValid(NotifyName, BranchingPointNotifyPayload))
{
FGameplayTag t1;
FGameplayEventData t2;
OnNotifyBegin.Broadcast(t1, t2, NotifyName);
}
}
void UAT_PlayMontageAndWaitForEvent::OnNotifyEndReceived(FName NotifyName,
const FBranchingPointNotifyPayload& BranchingPointNotifyPayload)
{
if (IsNotifyValid(NotifyName, BranchingPointNotifyPayload))
{
FGameplayTag t1;
FGameplayEventData t2;
OnNotifyEnd.Broadcast(t1, t2, NotifyName);
}
}
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)
{
// 设置 MontageInstanceID, 参考: PlayMontageCallbackProxy
if (FAnimMontageInstance* MontageInstance = AnimInstance->GetActiveInstanceForMontage(MontageToPlay))
{
MontageInstanceID = MontageInstance->GetInstanceID();
}
// Bind to event callback
EventHandle = 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);
// 设置Notify通知
AnimInstance->OnPlayMontageNotifyBegin.AddDynamic(this, &UAT_PlayMontageAndWaitForEvent::OnNotifyBeginReceived);
AnimInstance->OnPlayMontageNotifyEnd.AddDynamic(this, &UAT_PlayMontageAndWaitForEvent::OnNotifyEndReceived);
ACharacter* Character = Cast<ACharacter>(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());
FName tmp;
OnCancelled.Broadcast(FGameplayTag(), FGameplayEventData(), tmp);
}
}
SetWaitingOnAvatar();
}
void UAT_PlayMontageAndWaitForEvent::ExternalCancel()
{
check(AbilitySystemComponent.Get());
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 multicast
if (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()
{
if (Ability == nullptr)
{
return false;
}
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 montage
UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
if (ASC && Ability)
{
if (ASC->GetAnimatingAbility() == Ability
&& ASC->GetCurrentMontage() == MontageToPlay)
{
// Unbind delegates so they don't get called as well
FAnimMontageInstance* MontageInstance = AnimInstance->GetActiveInstanceForMontage(MontageToPlay);
if (MontageInstance)
{
MontageInstance->OnMontageBlendingOutStarted.Unbind();
MontageInstance->OnMontageEnded.Unbind();
}
ASC->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));
}
And I called it in blueprint, On Notify Begin
and On Notify End
do not called.