Handling rotational root motion with MecanimBridge and RichAI?

I have a 3D humanoid character using RichAI + MecanimBridge on a Recast graph. I greatly prefer not to edit A*'s scripts and subclass them where I can, but since MecanimBridge is an example script, and I’d have to edit it to subclass it anyway, I have my own version UnitMecanimBridge.cs that’s just a copy for now.

Using the X and Y parameters as set by MecanimBridge, my character has a directional blend tree that uses those values to animate. Since they’re direction values, not rotation, the character moves laterally left with (-1,0) XY values, laterally right with (1,0), etc. This is fine for directional movement, but the obvious problem is that the character rotates itself without animating.

Before discovering MecanimBridge I had two locomotion blend trees: one for directional, as described above, and one for rotational. The rotational blend tree used a magnitude of X & Y to get a sense of how much directional movement there was, and an angular speed value (normalized to values between -1 and 1) to track how much the character was turning. If the turn value was above an arbitrary value, say 30 degrees, the character would switch from the directional blend tree to the rotational one. This was definitely a clunky way to do it, and I’m trying to make a better version using MecanimBridge as a base.

Here’s how the magnitude (“MoveSpeed”), rotation (“TurnRadius”), and direction (“VelocityX” and “VelocityZ”) were calculated in Update() in my old script:

void Update()
{
                normalizedAngularSpeed = getAngularYSpeed(unit.transform);
		normalizedSpeed = unit.ai.velocity.magnitude / unit.ai.maxSpeed;
                Vector3 localVelocity = unit.transform.InverseTransformDirection(unit.ai.velocity);
		normalizedVelocityX = localVelocity.x / unit.ai.maxSpeed;
		normalizedVelocityZ = localVelocity.z / unit.ai.maxSpeed;

                unit.animator.SetFloat("MoveSpeed", normalizedSpeed);
		unit.animator.SetFloat("TurnRadius", normalizedAngularSpeed);
		unit.animator.SetFloat("VelocityX", normalizedVelocityX);
		unit.animator.SetFloat("VelocityZ", normalizedVelocityZ);
}

private float getAngularYSpeed(Transform transform)
    {
    	// Calculate how much the object has rotated since the last frame
    	float delta = Mathf.DeltaAngle(lastRotation, transform.eulerAngles.y);
    	lastRotation = transform.eulerAngles.y;
    	// Add some smoothing
    	angularSpeed = Mathf.Lerp(angularSpeed, delta / Time.deltaTime, 4 * Time.deltaTime);

    	// Convert the angular speed to a value between -1 and 1
    	// min/max for angularSpeed tested at around -320/320
    	float normalizedAngularSpeed = angularSpeed / angularSpeedNormalizer;
    	return normalizedAngularSpeed;
    }

Hopefully the above snippet gives a sense of how I was handling rotational animations.

Now this is how MecanimBridge does it:

void OnAnimatorMove () {
		Vector3 nextPosition;
		Quaternion nextRotation;

		ai.MovementUpdate(Time.deltaTime, out nextPosition, out nextRotation);

			var desiredVelocity = (ai.steeringTarget - tr.position).normalized * 2;//ai.desiredVelocity;
			//var desiredVelocity = ai.desiredVelocity;
			var desiredVelocityWithoutGrav = desiredVelocity;
			desiredVelocityWithoutGrav.y = 0;

			anim.SetFloat("InputMagnitude", ai.reachedEndOfPath || desiredVelocityWithoutGrav.magnitude < 0.1f ? 0f : 1f);

			// Calculate the desired velocity relative to the character (+Z = forward, +X = right)
			var localDesiredVelocity = tr.InverseTransformDirection(desiredVelocityWithoutGrav);

			smoothedVelocity = Vector3.Lerp(smoothedVelocity, localDesiredVelocity, velocitySmoothing > 0 ? Time.deltaTime / velocitySmoothing : 1);
			if (smoothedVelocity.magnitude < 0.4f) {
				smoothedVelocity = smoothedVelocity.normalized * 0.4f;
			}

			anim.SetFloat("X", smoothedVelocity.x);
			anim.SetFloat("Y", smoothedVelocity.z);

			// Calculate how much the agent should rotate during this frame
			var newRot = RotateTowards(desiredVelocityWithoutGrav, Time.deltaTime * ai.rotationSpeed);
			// Rotate the character around the currently grounded foot to prevent foot sliding
			nextPosition = ai.position;
			nextRotation = ai.rotation;

			nextPosition = RotatePointAround(nextPosition, CalculateBlendPoint(), newRot * Quaternion.Inverse(nextRotation));
			nextRotation = newRot;

			// Apply rotational root motion
			nextRotation = anim.deltaRotation * nextRotation;

			// Use gravity from the movement script, not from animation
			var deltaPos = anim.deltaPosition;
			deltaPos.y = desiredVelocity.y * Time.deltaTime;
			nextPosition += deltaPos;

			// Call the movement script to perform the final movement
			ai.FinalizeMovement(nextPosition, nextRotation);
		}

		static Vector3 RotatePointAround (Vector3 point, Vector3 around, Quaternion rotation) {
			return rotation * (point - around) + around;
		}

		/// <summary>
		/// Calculates a rotation closer to the desired direction.
		/// Returns: The new rotation for the character
		/// </summary>
		/// <param name="direction">Direction in the movement plane to rotate toward.</param>
		/// <param name="maxDegrees">Maximum number of degrees to rotate this frame.</param>
		protected virtual Quaternion RotateTowards (Vector3 direction, float maxDegrees) {
			if (direction != Vector3.zero) {
				Quaternion targetRotation = Quaternion.LookRotation(direction);
				return Quaternion.RotateTowards(tr.rotation, targetRotation, maxDegrees);
			} else {
				return tr.rotation;
			}
		}

This seems really weird to me, because I thought the whole point of MecanimBridge was to let animations’ root motion determine movement. Yet MecanimBridge seems to be rotating the character by force, not using any animations/root motion to turn.

To keep things simple I’ve edited MovementUtilities.cs so the character does not move forward when rotating:

float directionSpeedFactor = Mathf.Clamp(dot+0.707f, 0.001f, 1.0f);

So my current plan is to re-implement the rotation blend tree I used before, but using MecanimBridge to do it. So if the character is moving directionally, it will use the XY Movement Blend Tree. If the character is standing still but rotating, it will use a rotational blend tree.

I’m just not sure of the best way to do this using MecanimBridge:

  1. Should I delete the ai.FinalizeMovement(nextPosition, nextRotation) line? Am I correct that this is only for rotation, since MecanimBridge is using the animations’ root motion for directional movement? Is the newRot variable what I should use for rotation?
  2. How can I safely modify MecanimBridge to use root motion for rotation the same way its used for directional movement?

I’d really appreciate any advice you have on modifying MecanimBridge for rotational root motion, since I don’t want to inadvertently break something deeper in A*.

1 Like