Play Montage in c++ with OnBlendOut, OnInterrupted, etc

How do I play an animation montage on a skeletal mesh component from c++ such that I can get callbacks for OnCompleted, OnBlendOut, OnInterrupted, etc… just like the “PlayMontage” node in the picture?

I found this function but I don’t see any way to bind callbacks to it:

274078-blueprint.png

Thanks in advance for your help!

2 Likes

You can look into the UPlayMOntageCallbackProxy how it’s done in the engine.

But what basically happens is,
The montage is started with AnimInstance->Montage_Play and if that is successful you set right callbacks on the animinstance:

AnimInstance->Montage_SetBlendingOutDelegate
AnimInstance->Montage_SetEndDelegate
AnimInstance->OnPlayMontageNotifyBegin.AddDynamic
AnimInstance->OnPlayMontageNotifyEnd.AddDynamic

You also should make sure to unbind your callbacks at the right moments.

The function which shows how to add is this:

void UPlayMontageCallbackProxy::PlayMontage(class USkeletalMeshComponent* InSkeletalMeshComponent, 
	class UAnimMontage* MontageToPlay, 
	float PlayRate, 
	float StartingPosition, 
	FName StartingSection)
{
	bool bPlayedSuccessfully = false;
	if (InSkeletalMeshComponent)
	{
		if (UAnimInstance* AnimInstance = InSkeletalMeshComponent->GetAnimInstance())
		{
			const float MontageLength = AnimInstance->Montage_Play(MontageToPlay, PlayRate, EMontagePlayReturnType::MontageLength, StartingPosition);
			bPlayedSuccessfully = (MontageLength > 0.f);

			if (bPlayedSuccessfully)
			{
				AnimInstancePtr = AnimInstance;
				if (FAnimMontageInstance* MontageInstance = AnimInstance->GetActiveInstanceForMontage(MontageToPlay))
				{
					MontageInstanceID = MontageInstance->GetInstanceID();
				}

				if (StartingSection != NAME_None)
				{
					AnimInstance->Montage_JumpToSection(StartingSection, MontageToPlay);
				}

				BlendingOutDelegate.BindUObject(this, &UPlayMontageCallbackProxy::OnMontageBlendingOut);
				AnimInstance->Montage_SetBlendingOutDelegate(BlendingOutDelegate, MontageToPlay);

				MontageEndedDelegate.BindUObject(this, &UPlayMontageCallbackProxy::OnMontageEnded);
				AnimInstance->Montage_SetEndDelegate(MontageEndedDelegate, MontageToPlay);

				AnimInstance->OnPlayMontageNotifyBegin.AddDynamic(this, &UPlayMontageCallbackProxy::OnNotifyBeginReceived);
				AnimInstance->OnPlayMontageNotifyEnd.AddDynamic(this, &UPlayMontageCallbackProxy::OnNotifyEndReceived);
			}
		}
	}

	if (!bPlayedSuccessfully)
	{
		OnInterrupted.Broadcast(NAME_None);
	}
}
7 Likes

This really helped me, thanks!

Did you managed to bind it? I bound it without errors, however the bound function is never called when even though the AnimMontage is clearly playing nothing is logged:

void UCombatComponent::BeginPlay()
{
// ... 
	if (AnimInstance)
	{
		AnimInstance->OnPlayMontageNotifyEnd.AddDynamic(
this, &UCombatComponent::PlayMontageNotifyEnd);
	}
}

void UCombatComponent::PlayMontageNotifyEnd(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload)
{
	UE_LOG(LogTemp, Warning, TEXT("%s"), *FString(__FUNCTION__));
}
1 Like

(For the next people that get stuck here) I had the same problem and managed to fix it by adding UFUNCTION() infront of the PlayMontageNotifyEnd(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload) in header file.

1 Like

Though problem solved, for ones who want to implement a really complicated system with montage timing callbacks, I highly recommend you to use Ability System Component. Example here shows how a montage play tasks with such delegates is made:

If markdown jump not working, see 4.7.3

2 Likes

I know this long time ago, I found this solution

UPlayMontageCallbackProxy* PlayMontageCallbackProxy = UPlayMontageCallbackProxy::CreateProxyObjectForPlayMontage(
			Mesh,
			VaultMontage
		);

		PlayMontageCallbackProxy->OnCompleted.AddDynamic(this, &UVaultComponent::OnVaultMontageCompleted);

Yoo Can you show me how you did it?
||
void UMCombatComponent::PlayChainedAttackMontage(UAnimMontage* MontageToPlay)
{
/* Play chained attack and set the character in air */

// Add delay before playing If needed
M_CharacterBase->PlayAnimMontage(MontageToPlay, 1.0f);

if (IsInAir()) SetMovementMode(EMovementMode::MOVE_Flying); // maybe remove it later

}
void UMCombatComponent::OnMontageNotifyBegin(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointPayload)
{
UE_LOG(M_CombatComponent, Display, TEXT(“Montage BeingNotify”));

AttackIndex = -1;
if (IsInAir())
{
	SetMovementMode(EMovementMode::MOVE_Falling);
}
//Mesh->GetAnimInstance()->OnPlayMontageNotifyBegin.RemoveDynamic(this, &UMCombatComponent::OnMontageNotifyBegin);

}
void UMCombatComponent::OnMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
/* this function checks if the montage completed playing or if it was interupted */
if (bInterrupted)
{
UE_LOG(M_CombatComponent, Display, TEXT(“Montage Interupped”));
if (AttackIndex == GetChainedCombo().ComboMax)
{
AttackIndex = -1;
if (IsInAir())
{
SetMovementMode(EMovementMode::MOVE_Falling);
}
}
}
else
{
UE_LOG(M_CombatComponent, Display, TEXT(“Montage Complete”));
AttackIndex = -1;
}
//Mesh->GetAnimInstance()->OnMontageEnded.RemoveDynamic(this, &UMCombatComponent::OnMontageEnded);
}
||
In BeginPlay

// Montage Delegates
Mesh->GetAnimInstance()->OnMontageEnded.AddDynamic(this, &UMCombatComponent::OnMontageEnded);
Mesh->GetAnimInstance()->OnPlayMontageNotifyBegin.AddDynamic(this, &UMCombatComponent::OnMontageNotifyBegin);

and i my header

UFUNCTION() void OnMontageNotifyBegin(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointPayload);
UFUNCTION() void OnMontageEnded(UAnimMontage* Montage, bool bInterrupted);