Animating a Character

When animating a character, you should query the character's state and/or subscribe to its numerous events, feeding this information to your AnimationController parameters to ensure your animation stays perfectly in sync with the character's state, such as whether it is grounded, falling, jumping, etc.

ECM2 does not require the use of animation or any specific animation techniques. You have the freedom to animate your characters using 'plain Unity code' or your preferred method.

In this example, we will animate the included UnityCharacter model, syncing its animator with the information provided by the Character class.

/// <summary>
/// This example shows how to animate a Character,
/// using the Character data (movement direction, velocity, is jumping, etc) to feed your Animator.
/// </summary>

public class AnimationController : MonoBehaviour
{
    // Cache Animator parameters
    
    private static readonly int Forward = Animator.StringToHash("Forward");
    private static readonly int Turn = Animator.StringToHash("Turn");
    private static readonly int Ground = Animator.StringToHash("OnGround");
    private static readonly int Crouch = Animator.StringToHash("Crouch");
    private static readonly int Jump = Animator.StringToHash("Jump");
    private static readonly int JumpLeg = Animator.StringToHash("JumpLeg");
    
    // Cached Character
    
    private Character _character;

    private void Awake()
    {
        // Cache our Character
        
        _character = GetComponentInParent<Character>();
    }

    private void Update()
    {
        float deltaTime = Time.deltaTime;

        // Get Character animator

        Animator animator = _character.GetAnimator();

        // Compute input move vector in local space

        Vector3 move = transform.InverseTransformDirection(_character.GetMovementDirection());

        // Update the animator parameters

        float forwardAmount = _character.useRootMotion && _character.GetRootMotionController()
            ? move.z
            : Mathf.InverseLerp(0.0f, _character.GetMaxSpeed(), _character.GetSpeed());

        animator.SetFloat(Forward, forwardAmount, 0.1f, deltaTime);
        animator.SetFloat(Turn, Mathf.Atan2(move.x, move.z), 0.1f, deltaTime);

        animator.SetBool(Ground, _character.IsGrounded());
        animator.SetBool(Crouch, _character.IsCrouched());

        if (_character.IsFalling())
            animator.SetFloat(Jump, _character.GetVelocity().y, 0.1f, deltaTime);

        // Calculate which leg is behind, so as to leave that leg trailing in the jump animation
        // (This code is reliant on the specific run cycle offset in our animations,
        // and assumes one leg passes the other at the normalized clip times of 0.0 and 0.5)

        float runCycle = Mathf.Repeat(animator.GetCurrentAnimatorStateInfo(0).normalizedTime + 0.2f, 1.0f);
        float jumpLeg = (runCycle < 0.5f ? 1.0f : -1.0f) * forwardAmount;

        if (_character.IsGrounded())
            animator.SetFloat(JumpLeg, jumpLeg);
    }
}

As you can observe, this is standard Unity code that leverages the character's state information to control our animator.

Root Motion

The Character class, includes built-in support for root motion.

Utilizing Root Motion

To enable root motion, follow these steps:

  1. Add the RootMotionController component to your model's GameObject. This RootMotionController is responsible for providing the animation's velocity, rotation, etc., to the Character.

  2. Enable the useRootMotion property in the Character. This property can be toggled as needed.

Once a character is moved using root motion, the animation assumes complete control over the character's movement. This replaces all procedural movement, rendering properties such as maxWalkSpeed, maxFallSpeed, etc., irrelevant, as the character is entirely driven by the animation.

Worth noting, the character's ground constraint still applies when root motion is enabled. This implies that any vertical movement in your root motion-based animation won't function unless you explicitly disable this constraint. In such cases, it's advisable to assign the Flying movement mode, as it automatically disables the ground constraint and allows for vertical movement.

To enable vertical root motion movement, the character's movement mode must be set to Flying.

Toggling Root Motion

This example demonstrates how to toggle root motion at run-time. In this case, enabling root motion only while the character is in the Walking movement mode.

public class RootMotionToggle : MonoBehaviour
{
    private Character _character;
    
    private void OnMovementModeChanged(Character.MovementMode prevMovementMode, int prevCustomMovementMode)
    {
        // Allow root motion only while walking
        
        _character.useRootMotion = _character.IsWalking();
    }

    private void Awake()
    {
        _character = GetComponent<Character>();
    }

    private void OnEnable()
    {
        // Subscribe to Character events
        
        _character.MovementModeChanged += OnMovementModeChanged;
    }

    private void OnDisable()
    {
        // Un-Subscribe from Character events
        
        _character.MovementModeChanged -= OnMovementModeChanged;
    }
}

In this example, we utilize the MovementModeChange event, which is triggered each time the character changes its current movement mode, making it perfect this case.

Last updated