How do we make a "180 turn" animation blend out properly?

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” entry transition’s conditions:

Plant and turn left:
5268567--527325--upload_2019-12-10_16-30-4.png

Plant and turn right:
5268567--527322--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 this 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? Brain melting…

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);

Please help
Fred.

5268567--527316--upload_2019-12-10_16-28-56.png
5268567--527322--upload_2019-12-10_16-29-44.png

Here are the solutions:

Problem 1

In a state machine behavior:

  1. Override and set to zero Y axis of the values you get from animator.deltaRotation.eulerAngles
  2. Apply the modified root rotation to your character using something like transform.Rotate(rotation)

In your character controller:

  1. Do the rotation manually using transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, customRotationSpeed * Time.deltaTime);
  2. Detect when the character is in transition to and out of your plant and turn
  3. Detect when the character is IN the plant and turn state
  4. Apply a custom rotation speed reserved just for the plant and turn
  5. 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:

  1. 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));
  2. 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 transitions properly adjusted it looks like this:

1 Like

Haven’t read all of it but I use a different animation layer to fade in out turning animations. This way you can keep your main animations just active and reacting to camera view and inputs and fade in out the layer weight via script. I first thought in the direction you did but remembered if something sounds too complicated there’s always an easier way. :wink:

EDIT: I just looked at your graph. Yup. Glad I started using layers.

@ibbybn Fortunately I got it to work, so I won’t have to change things but It’s a good idea to use layers!

We also use them, a little… I attached a few examples.

They all look like a that and those sub state machines (those pointy ended states) you can dig in any one of them and most of the times find a similar graph. Some even have sub states within sub states… real inception stuff.

A lot of those states also have multiple StateMachineBehaviour on them.

That’s our main nav

This some of the parkour stuff

This is the reload sub state machine in the left hand layer. More STM.

OMG Blend trees within blend trees

Looking back at it now I still can’t believe I made this thing :smile:

1 Like

BTW I was checking your site and I love what you did with the hair!

<— That’s supposed to be a chick underwater.

Black Hollow, I’ll keep that in mind. Reminds me of the scenes in water world when he dived to the ruins at the bottom of the ocean. Loved those scenes. Will we be able to do that?

PM me when you release updated footage!

Oh BTW, I’m curious, what’s your AI solution? I’ve been using behaviour designer but… I feel like running to the comfort of c# every second I use it. Feels like it would be much faster to code it directly.

That is quite the spiderweb you got there. :wink: Well if it works why not! I guess the amount of animations is not so surprising but I got rid of most of my node connectors by crossfading in script. Also doing more and more procedural IK animations for diving because this way the body kind of leads the way and the legs are at an angle when turning. Looks more natural.

Thanks! Yes the game has alot of diving through ruins. Whole cities under water basically. Recently switched to houdini to procedurally create all those broken houses needed. Was going insane using blender for that before!

I looked at stuff like behaviour designer and playmaker like 4 years ago but decided against it. Imho it’s way faster to just type that in code than fiddle around with all those nodes. And you’re basically just taking away alot of possibilities from c# ( I doubt there are nodes for everything I want to do… ) and trading them for visuals.

I couldn’t have said it better. Feels sooo slow to use any type of node based coding. I’m going to have to take a closer look to cross fading in script but I have the feeling it must be a pain to properly adjust transitions. Either create variables for all the transition timings (that makes a lot of variable to create) so you can iterate in play mode or you hard code it and recompile.

I guess you could create a custom class for transition names and settings and shove them into a public list.

Those animator graphs are a nightmare…