Playing AnimMontage right after IsCrouching is true on clients makes them uncrouch after it's done

Here’s how i play the AnimMontage:

void UPhosphorCharMvtComponent::TickComponent(float DeltaTime,enum ELevelTick TickType,FActorComponentTickFunction *ThisTickFunction)
{
	Super::TickComponent(DeltaTime,TickType,ThisTickFunction);

	ACharacter* character = GetCharacterOwner();
	if (character == nullptr)
		return;

	if (WantedStance != Stance)  
	{
		if (character->GetCurrentMontage() != TransitionMontage) //done playing
		{
			Stance = WantedStance;
			TransitionMontage = nullptr;
			UpdateMovementSpeeds();
		}

		return;
	}

	ECharStance newStance = ECharStance::Stand;
	if (MovementMode == EMovementMode::MOVE_Walking)
	{
		if (IsCrouching())
			newStance = ECharStance::Sneak;
	}
	else if (MovementMode == EMovementMode::MOVE_Custom)
	{
		newStance = static_cast<ECharStance>(CustomMovementMode);
	}

	if (newStance != Stance)
	{
		WantedStance = newStance;

		if (Stance == ECharStance::Stand && newStance == ECharStance::Sneak)
			TransitionMontage = FindAnimMontageAsset("Stand-Sneak");
		else if (Stance == ECharStance::Sneak && newStance == ECharStance::Stand)
			TransitionMontage = FindAnimMontageAsset("Sneak-Stand");
		else
			TransitionMontage = nullptr;		
				
		if (TransitionMontage != nullptr)
		{
			//if (CharacterOwner->Role >= ROLE_AutonomousProxy)
				character->PlayAnimMontage(TransitionMontage);
		}
		else //just transition immediately
		{
			Stance = WantedStance;
			UpdateMovementSpeeds();
		}		
	}
}

Stance,WantedStance,TransitionMontage arent replicated or anything, they’re just member vars.

This is what i get on the server (watching a client move):

This is what i get on the client (watching the server, or another client, move):

And if I uncomment that if (CharacterOwner->Role >= ROLE_AutonomousProxy) check so that AnimMontages dont play on simulated proxies, i get: When Not Playing RootMotion On SimulatedClients, Uncrouching Does Not Happen GIF... | Gfycat

When watching the videos, look at the GREEN debug output, that’s what i added to Character.cpp and CharacterMovementComponent.cpp in the functions specified to debug the issue. There’s an extra UnCrouch(false) call in the faulty case.
I think this AnimMontage business is happening because of CharacterMovementComponent rewinding to before the AnimMontage is played (after its done playing), at which point bWantsToCrouch is false in the rewind PerformMovement and it immediately uncrouches him.

This is further confirmed by, when i was debugging the issue, i set breakpoints to where bIsCrouched is set inside CharacterMovementComponent, as well as OnRep_IsCrouched in Character, and most of the time, the issue didnt happen, probably because the DeltaTime after i resumed the program just skipped over the AnimMontage completely.

Engine 4.6-preview built from source github commit f34ec74177b9cd4628611e7d2318e6fb1c2fa84a

EDIT: A hacky way of ‘fixing’ this was to change ACharacter::OnRep_IsCrouched to this:

void ACharacter::OnRep_IsCrouched()
{
	if (CharacterMovement)
	{
		if (bIsCrouched)
		{
			CharacterMovement->bWantsToCrouch = true; //added this
			CharacterMovement->Crouch(true);
		}
		else
		{
			CharacterMovement->bWantsToCrouch = false; //added this    
			CharacterMovement->UnCrouch(true);
		}
	}
}

No clue what havok that might wreck with regards to latency tolerant move replaying and such, i only tested in PIE windows.

Hi ,

Are you seeing this in a new project from one of the templates? If so, what template are you using and what are the exact steps you are using to set this up?

Ok, so there are very specific circumstances to this happening:

  1. The transition montage played MUST have Root Motion enabled (by enabling it in the associated AnimSequences), if you’re wondering why I have it enabled for an animation with no root motion, it’s because without it enabled, the character can still move during the transition, while with, he’s locked to playing it.
  2. The AnimBP MUST be set to ‘Ignore Root Motion’ or ‘No Root Motion Extraction’ in it’s defaults. Setting it to ‘Root Motion from AnimMontages only’ makes this work correctly.

Here’s a repro project based on TP C++ template and the AnimationStarterPack (i only left in the animations that i used, and I’ve changed the anims i made the AnimMontages from to have root motion, as well as adding a Slot player in the AnimBP and changing it to ‘Ignore root motion’)
https://files.sshnuke.net/CrouchRepro.zip

The exact culprit is this:
After UCharacterMovementComponent::Crouch(true) is called from ACharacter::OnRep_IsCrouched, in UCharacterMovementComponent::SimulatedTick the “if we were simulating root motion, we’ve been ignoring regular ReplicatedMovement updates.” else case fires, which does PerformMovement, which finds that bWantsToCrouch is false, and calls UnCrouch.

Hi ,

Thank you for the additional information and sample project. I was able to see the issue that you described, and have entered a report about it to have it investigated further (UE-5968).