Blend Tree Issue: Holding sprint button allows for slow-motion sprint animation to play instead of walk animation

Hello all! This issue is driving my a little crazy. Let me explain.

I have a simple Blend Tree of: Idle, Walk, Run. I have a speed perimeter. I’m using analog controls for the movement (Quest 3 joystick). It works fine when I slightly tilt the joystick, it transitions from Idle to Walk while playing a slow animation. if I hold the joystick down all the way, the walk animation plays at the appropriate speed.

The issue I’m having is that when I press and hold the right trigger which is my “sprint button”, then tilt my joystick slightly, instead of seeing my walk animation, I see a super slow sprint animation. Now this shouldn’t be possible because the blend tree animations are based on speed, not the button input. I have no idea why this is happening. If anyone has any ideas, I’d be most thankful.

THIRD PERSON CONTROLLER SCRIPT:

private void Move()
{
   // set target speed based on move speed, sprint speed and if sprint is pressed
   float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;


   // a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon


   // note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
   // if there is no input, set the target speed to 0
   if (_input.move == Vector2.zero) targetSpeed = 0.0f;


   // a reference to the players current horizontal velocity
   float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;


   float speedOffset = 0.1f;
   float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;


   // accelerate or decelerate to target speed
   if (currentHorizontalSpeed < targetSpeed - speedOffset ||
       currentHorizontalSpeed > targetSpeed + speedOffset)
   {
       // creates curved result rather than a linear one giving a more organic speed change
       // note T in Lerp is clamped, so we don't need to clamp our speed
       _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,
           Time.deltaTime * SpeedChangeRate);


       // round speed to 3 decimal places
       _speed = Mathf.Round(_speed * 1000f) / 1000f;
   }
   else
   {
       _speed = targetSpeed;
   }


   _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);
   if (_animationBlend < 0.01f) _animationBlend = 0f;


   // normalise input direction
   Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;


   // note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
   // if there is a move input rotate player when the player is moving
   if (_input.move != Vector2.zero)
   {
       _targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg +
                         _mainCamera.transform.eulerAngles.y;
       float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
           RotationSmoothTime);


       // rotate to face input direction relative to camera position
       transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
   }


   Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;


   // move the player
   _controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) +
                    new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);


   // update animator if using character
   if (_hasAnimator)
   {
       _animator.SetFloat(_animIDSpeed, _animationBlend);
       _animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
   }
}

INPUT ASSETS SCRIPT

namespace StarterAssets
{
   public class StarterAssetsInputs : MonoBehaviour
   {
public InputActionProperty moveAction;
public InputActionProperty jumpAction;
public InputActionProperty sprintAction;

       [Header("Character Input Values")]
       public Vector2 move;
       public Vector2 look;
       public bool jump;
       public bool sprint;

       [Header("Movement Settings")]
       public bool analogMovement;

       [Header("Mouse Cursor Settings")]
       public bool cursorLocked = true;
       public bool cursorInputForLook = true;

private void Start()
{
moveAction.action.performed += x => MoveInput(x.ReadValue<Vector2>());
moveAction.action.canceled += x => MoveInput(x.ReadValue<Vector2>());
jumpAction.action.performed += x => JumpInput(x.action.IsPressed());
sprintAction.action.performed += x => SprintInput(x.action.IsPressed());
sprintAction.action.canceled += x => SprintInput(x.action.IsPressed());
}

#if ENABLE_INPUT_SYSTEM
       public void OnMove(InputValue value)
       {
           MoveInput(value.Get<Vector2>());
       }


       public void OnLook(InputValue value)
       {
           if(cursorInputForLook)
           {
               LookInput(value.Get<Vector2>());
           }
       }


       public void OnJump(InputValue value)
       {
           JumpInput(value.isPressed);
       }


       public void OnSprint(InputValue value)
       {
           SprintInput(value.isPressed);
       }
#endif




       public void MoveInput(Vector2 newMoveDirection)
       {
           move = newMoveDirection;
       }


       public void LookInput(Vector2 newLookDirection)
       {
           look = newLookDirection;
       }


       public void JumpInput(bool newJumpState)
       {
           jump = newJumpState;
       }


       public void SprintInput(bool newSprintState)
       {
           sprint = newSprintState;
       }


       private void OnApplicationFocus(bool hasFocus)
       {
           SetCursorState(cursorLocked);
       }


       private void SetCursorState(bool newState)
       {
           Cursor.lockState = newState ? CursorLockMode.Locked : CursorLockMode.None;
       }
   }
  
}

Hello @EvanCheddar, this doesn't look like a problem with your blend tree, could you share a bit of your code so we can understand your logic and see where the problem comes from?

@Cyber_Cats, ​hey there! I included a hyperlink to my scripts above.

What are these two variables: _animIDSpeed and _animIDMotionSpeed ?

5 Answers

5

Hello @EvanCheddar,

In your case you have 2 possibilities: it’s either the blend tree or the script, to know if your Blend Tree works fine you can remove your script from your player, go in runtime and open the Animator window to change the value of Speed, if the player is animated the way you expect him then it’s a script issue

I’m going to read your script but I need you to be sure the animator is wired correctly, so test it and keep us in touch please

I've checked the blend tree and it works perfectly as intended. The issue must be in the script(s).

The second step after understanding that the error comes from the script would be to see which part of your code is interacting with the animator, in your case it’s the 2 lines:

_animator.SetFloat(_animIDSpeed, _animationBlend);
_animator.SetFloat(_animIDMotionSpeed, inputMagnitude);

Can you confirm no other part of your script is modifying the animator state?
if you comment these lines your player should not be animated when you sprint or walk, can you confirm that?

These are the only 2 lines of code (that I can tell) that control the animations. There's no other script accessing the animations. if i comment the lines out, movement works fine but there are no animations (accept jump animation).

So only these two lines are modifying the animator, why 2 variables? in your case you only have the speed so we will limit the scope by removing the other line.
You will comment the line that is interfering with the animator and that is not changing the value of Speed then test again to see what’s happening

Once you are sure there is only one line that is changing the animator and it’s still not working as intended we can check what this line is made of

Can you confirm that if you only keep _animator.SetFloat(_animIDSpeed, _animationBlend); the Blend Tree is still not working as intended?

Edit: based on your comments I guess I understand what is happening, I should have been more attentive to the variable names, you are using MotionSpeed as an animation speed variable…

So everything is working fine but since your running animation needs to have the MotionSpeed at it’s maximum to look normal you should change that, so let me explain everything:

Your animator’s Speed variable is calculated using _animationBlend which is a variable used to lerp between the current speed value and the targetSpeed but when you press the Sprint button, the targetSpeed becomes the SprintSpeed so even if your input is only slightly tilted, the targetSpeed is 1 and the lerp is too quick so since your Animation speed is based on the InputMagnitude which is the “tilt” you are referring to, when you sprint but slightly move the joystick the animation will be sprint in slow motion

A way for you to better understand what is happening would be to place a Text UI with as a text the value of _animationBlend, you will see that when you hold the sprint button the value becomes 1 (SprintSpeed)

If I only keep that line of code, the animations don't play properly. There is something hinging on the MotionSpeed variable that I don't fully grasp (as well as InputMagnitude).

Can you make a reply where you share a screenshot of your animator window, maybe the problem is neither the script not the blend tree but the wiring of the animator

Sure thing, need a few minutes though.

Wow very astute. I really appreciate your time combing through this, it was driving me crazy. It makes sense now. I’m not exactly sure how to adjust the script so that my MotionSpeed matches correctly.

Well you can simply do that:

_animator.SetFloat(_animIDMotionSpeed, _input.sprint ? 1 : inputMagnitude);

Edit: Okay so thanks to your comments now I fully understand what you wanted and what is happening:
What you wanted was that the animation limit would be 0.5 (so the walk animation) and when the sprint button is pressed this limit would go to 1. The problem lied in the fact that you were using the variable SpeedChangeRate for both the movement speed blending and the animator’s Speed variable’s blending so in this part of the code:

{
   //...

    _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude, Time.deltaTime * SpeedChangeRate);

   //...
} else {
   _speed = targetSpeed;
}

_animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);

You should change

_animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);

by

_animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * AnimationSpeedChangeRate);

and so add a new variable AnimationSpeedChangeRate with the desired value

so when you were slightly moving the joystick the value of _animationBlend was jumping from 0 to the targetSpeed directly due to SpeedChangeRate being a too big value, so it was happening during the walk but you couldn’t see because there wasn’t an intermediate state.

Remember to not use the same variable for different usages, try to split your code logic and avoid sharing variables between the different parts :slight_smile:

So I tried inserting that line instead of: _animator.SetFloat(_animIDMotionSpeed, inputMagnitude) But it now plays the sprint animation at full speed with a slight tilt.

Isn't that what you wanted?

I thought your running animation was looking weird in slow motion so you wanted it to be at normal speed, Can you explain what you want exactly?

So, when I hold the sprint button, the speed variable should increase from 0.5 to 1. But the animation should still go IDLE > WALK > SPRINT. The speed is dictated by the tilt of the joystick, so even though sprint is held down, the animation for sprinting shouldn't play until the speed variable records at least 0.5 and onward until 1.

float speedOffset = 0.1f;