Download

Montage Branching Points Firing Prematurely

Hi guys,

I am having a big issue with branching points. I will describe it as best I can, but I’m not particularly certain where the problem arises. Any help would be hugely appreciated…

The Situation

My game contains a combat system. The player can click to swing a weapon, and they can only swing again once the original swing has finished. Because I have various events tied to certain branching points, and because I desired a little more control, I have derived UAnimInstance into my own subclass, UCharacterAnimator. This contains a few variables that allow me to control the various states of my characters animation, including bools that say whether or not the player is currently mid-animation, if the animation is interruptible, and various methods for playing and stopping animations. Two in particular I feel are important:


void UCharacterAnimator::StopAnimation(UAnimMontage* animName, bool instant)
    {
    	if (animName)
    	{
    		Montage_Stop(0.5f, animName);
    	}
    	else
    	{
    		Montage_Stop(0.5f);
    	}
    
    	if (instant)
    	{
    		Montage_Stop(0);
    	}
    	
    	EndAnimation();
    }
    
    void UCharacterAnimator::EndAnimation()
    {
    	Animating = false;
    	Interruptable = true;
    	AnimationMovesCharacter = false;
    	CanMove = true;
    	Caller = 0;
    
    	//Debugging
    	FString animName = GetCurrentActiveMontage()->GetFullName();
    	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Magenta, "~~~ENDANIMATION~~~" + animName + " " + GetOwningActor()->GetName());
    }

My blueprint looks like this, and is derived from this new subclass (please ignore the bottom right…):

a4994d8f5bcbeb16c4399c585e0906ae.png

So, when the required branch point is hit, the “End Animation” method (shared above) is fired, and all of the variables (set in the play method) are reset. So far so good.

The Problem

In this situation, I am testing what would happen if a character attempts a melee attack and is subsequently blocked. They should swing, but when blocked, the animation is stopped (StopAnimation is called) and then a “Knockback” animation is played immediately after (which will prevent them attacking until the animation is over, and prevent them moving).** The problem is that the “EndAnimation” branching point for the animation is fired immediately after playing the animation, meaning that the player is free to move and attack again, despite being mid-knockback-animation.**

Here is what the game prints out, thanks to the debug text, when trying this functionality:

e9386d8b3a8031e7904fba473df71650.png

So, going from the bottom to the top. The hit is blocked, and the animation is stopped, thus the first purple line is printed out immediately. No problem there, although it cannot seem to print the name. However, it then immediately prints out the second line, with the full animation name - even though the branch point has not been met. And then, lastly, EndAnimation is called again by the blueprint, and this time the branch point has actually been met.

So a rough stack of functions would be:

  • Player clicks to swing the weapon: PlayAnimation is called
  • It is blocked: StopAnimation is called, which itself calls EndAnimation
  • Immediately after, PlayAnimation is again called for the knockback animation

So, can anyone provide any hint into what is going wrong? I can guarantee that the branching points are in the correct places, and on the weapon swing, it gets hit just fine. I am wondering if perhaps branching points are somehow called by the Montage_Stop function?

Any and all help would be greatly appreciated, as the solution to this has really eluded me

Thanks

It’s likely unable to print the name because there is no active montage at that time. (GetFullName can be called on null pointers without crashing, as you’ve likely noticed.) IIRC, a montage that’s blending out is not considered active, so GetCurrentActiveMontage() is returning null since you just stopped it.

Branching points and notifies still get called when animations are blending out, which might not be what you want. In any event, blends tend to be a frequent source of issues with animation notifies, so try turning it off for now to see if it helps.

If that seems to be the issue, I know I had some corner cases where I needed to treat blending out animations as active so I use the provided FOnMontageBlendingOutStarted/FOnMontageEnded delegates to monitor that:


void AMyCharacter::OnMontageBlendingOut( class UAnimMontage* Montage, bool bInterrupted )
{
	UE_LOG( LogMyGame, Verbose, TEXT("%.4f] AMyCharacter::OnStandardMontageBlendingOut %s"), GetWorld()->GetTimeSeconds(), *GetNameSafe( this ) );

	CurrentStandardAnimation.bIsBlendingOut = true;
}

void AMyCharacter::OnMontageEnded( class UAnimMontage* Montage, bool bInterrupted )
{
	UE_LOG( LogMyGame, Verbose, TEXT("%.4f] AMyCharacter::OnStandardMontageEnded %s"), GetWorld()->GetTimeSeconds(), *GetNameSafe( this ) );

	CurrentStandardAnimation.bIsBlendingOut = false;
}

And the registration of these delegates, done right after Montage_Play:


auto MontageBlendOutDelegate = FOnMontageBlendingOutStarted::CreateUObject( this, &AMyCharacter::OnMontageBlendingOut );
auto MontageEndedDelegate = FOnMontageEnded::CreateUObject( this, &AMyCharacter::OnMontageEnded );

AnimInstance->Montage_SetBlendingOutDelegate( MontageBlendOutDelegate );
AnimInstance->Montage_SetEndDelegate( MontageEndedDelegate );

I have a lot of those verbose logs across all my animation functions so I can carefully follow what happens in animation.

Let me know if that unblocks anything.

-Camille

You put me on the right track here - I refactored a bit of my animator and added the delegate in for the montage ending with some custom logic. Works now.

Thanks a lot :slight_smile:

Hey could you explain the difference between these two:

This is from WarriorAnimInstance.cpp right after MontagePlay



Montage_Play(FallMontage, 1.0f);
FOnMontageEnded OnEndJumpDelegate;
OnEndJumpDelegate.BindUObject(this, &UWarriorAnimInstance::OnWarriorMontageEnded);
Montage_SetEndDelegate(OnEndJumpDelegate, FallMontage);


versus below in the same place



Montage_Play(FallMontage, 1.0f);
FOnMontageEnded OnEndJumpDelegate = FOnMontageEnded::CreateUObject(this, &UWarriorAnimInstance::OnWarriorMontageEnded);
Montage_SetEndDelegate(OnEndJumpDelegate, FallMontage);


It works either way but I don’t understand if I need to UnBind somewhere or do any memory clean up to avoid a leak?