- Home /
What is the proper way to implement a 180 turn while running (Plant and turn) animation?
Hello, I'm kind of in bind here. We're entering a contest in the coming weeks and I still can't make our Plant And Turn (180 rotation while running) animation work properly.
I'm hoping someone with experience can help. It would mean a lot to fix this.
Our setup:
We use root motion for forward movement and the rotation is handled in script relative to the player's orientation and the screen.
The "inputMagnitude" parameter is used in a blend tree to define if the player is in "slow walk", "walk", "jog" or "run" animation and an euler angle rotation (0 and 360 being in front of the player) is pushed to the animator to trigger the plant and turn animations.
Here are the current "plant and turn" transition conditions:
Plant and turn left:
upload_2019-12-10_16-30-4.png
Plant and turn right:
upload_2019-12-10_16-29-44.png
For the exit transition "Has exit time" is checked and "Exit time" is set to 1
I realize they are some gaps in the RelativeEulerDir coverage but it's not what's causing my problems.
Problem 1:
I'm having problems making the end rotation of the "plant and turn" line up with user input. The animation's rotation does a fixed 180 turn and once the animation finishes if the user's input is not perfectly aligned the player's rotation appears to snap when it catches up to the input position.
Note that input driven rotation is disabled when in the "plant and turn" state and when transitioning in and out of it, which is part of the problem but I can't let both the input and the animation drive the rotation.
I tried several things to make it work:
Completely overriding the root rotation and letting the input drive it
Disabling the root rotation once RelativeEulerDir = 0
Disabling the root rotation once RelativeEulerDir <= toleranceValue (like 0.1, 1, 5)
Using a long transition time
I could try using the remaining animation time with the current rotation and the input to scale the animation's rotation so it ends flush with the input but that would create new problems like... what if the player changes direction radically?
Problem 2:
Triggering the plant and turn not always reliable. On the keyboard it's easier (the change in direction is quite direct) but when using a game pad it's another story. We have to flip the joystick in the opposite direction quite fast/hard for it to work otherwise the inputMagnitude drops too fast.
Lowering the inputMagnitude works but then users walking at a slower pace can initiate a plant and turn if they try real hard.
Adding a velocity check could fix this but the velocity I get from unity's character controller is quite jittery, almost seems like movement from the animations gets added on top.
FYI: We use Rewired, that's why you see player.GetAxis, but beside that it's the same as Unity's input.
All parameters are updated in Update().
Input is refreshed as follows:
inputVector.x = player.GetAxis ("Horizontal");
inputVector.y = player.GetAxis ("Vertical");
inputVectorRaw.x = player.GetAxisRaw ("Horizontal");
inputVectorRaw.y = player.GetAxisRaw ("Vertical");
Animation parameters are pushed using hashes:
animator.SetFloat(hash_InputX, inputVector.x);
animator.SetFloat(hash_InputY, inputVector.y);
animator.SetFloat(hash_InputMagnitude, inputMagnitude);
animator.SetFloat(hash_InputMagnitudeRaw, inputMagnitudeRaw);
animator.SetFloat(hash_Velocity, currentVelocity);
This code handles rotation and sets the RelativeEulerDir:
// Get targetDirection relative to the camera in world space (filtered input for keyboard)
Vector3 targetDirection = Camera.main.transform.TransformDirection(new Vector3(inputVector.x, 0f, inputVector.y));
// Get targetDirectionRaw relative to the camera in world space (raw input for accuracy with animator)
Vector3 targetDirectionRaw = Camera.main.transform.TransformDirection(new Vector3(inputVectorRaw.x, 0f, inputVectorRaw.y));
// Make sure Y is zeroed
targetDirection.y = 0;
targetDirectionRaw.y = 0;
// Init target rotations
Quaternion targetRotation = Quaternion.identity;
Quaternion targetRotationRaw = Quaternion.identity;
// Translate targetDirectionRaw to player's local space
Vector3 playerRelativeTargetDirRaw = transform.InverseTransformDirection(targetDirectionRaw);
// Get the target rotations required to rotate the player to the target directions
if (targetDirection != Vector3.zero) targetRotation = Quaternion.LookRotation(targetDirection, Vector3.up);
if (playerRelativeTargetDirRaw != Vector3.zero) targetRotationRaw = Quaternion.LookRotation(playerRelativeTargetDirRaw);
// Rotate the player transform
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, freeRunRotationSpeed * Time.deltaTime);
// Inform the animator of the requested rotation
animator.SetFloat(hash_RelativeEulerDir, targetRotationRaw.eulerAngles.y);
Can someone help?
Answer by tcz8 · Dec 12, 2019 at 09:57 PM
Here are the solutions:
Problem 1
In a state machine behavior:
Override and set to zero Y axis of the values you get from
animator.deltaRotation.eulerAngles
Apply the modified root rotation to your character using something like
transform.Rotate(rotation)
In your character controller:
Do the rotation manually using
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, customRotationSpeed * Time.deltaTime);
Detect when the character is in transition to and out of your plant and turn
Detect when the character is IN the plant and turn state
Apply a custom rotation speed reserved just for the plant and turn
Adjust the speed until its perfect
If you still have issues after this (animation looks weird) its believe it's because Quaternion.RotateTowards
will always rotate to a target rotation using the shortest rotation possible. So sometimes it'll turn left other times it'll turns right.
I haven't implemented it yet but what you need to do is define if you need to turn left or right by detecting which transition you are in (plant and turn left or right) and the apply either negative or positive rotation until the target rotation is reached. You want to force the direction of the rotation until your are done transitionning out of the plant and turn anim (not just he state itself, you must detect the transitions!)
OR you can look into implementing your own Quaternion.RotateTowards
. Use google, the source code is out there.
Problem 2
This fix was two fold:
I Implemented a low pass filter on the values I push to the "velocity" animation parameter to filter out the noise (it was too noisy making it unreliable to trigger transitions). Here is the code:
newVelocity = (charCtrl.velocity.magnitude * velocityLowPassFactor) + (prevVelocity * (1.0f - velocityLowPassFactor));
I made sure we only set the RelativeEulerDir animation parameter when it is within range of the plant and turns and when we do set it, do not set it again for a configurable delay. The value was changing too fast making it hard to meet all the requited transition conditions
With all the parameters, speeds, delays and transition properly adjusted it now looks like this: https://youtu.be/RVnRhFQuzpA
Your answer
Follow this Question
Related Questions
Easier way of creating transitions in the animator? 1 Answer
Why does Unity animator makes calling children's SetActive fail? 0 Answers
Unity OnStateExit StateMachineBehaviour is never getting called 0 Answers
Can an empty state in Animator cause performance issues? 1 Answer
Mec Anim transition Bug? 0 Answers