U
tab 02Character & Combat9 lessons — tap to open
03 / 09 · 10 min
Character & Combat

Animator state machine

your project so far

Assets/

Scenes/

Arena.unity

Knight

Scripts/

PlayerMovement.csCharacterStats.csPlayerAnimator.cs

Animations/

AC_KnightLocomotion blend tree

you already havethis lesson adds

The Knight now moves with PlayerMovement, but it slides around frozen in a T-pose. This lesson gives it legs.

Your code never plays a clip directly. It sets parameters; the Animator decides which clip that means and blends between them.

animated diagram

Watch the character on the left act out whichever state is lit on the right — and the three parameters at the bottom are the only things your code ever touches.

Set up the controller

  1. 1

    Project: Create▸Animation▸Animator Controller, name it AC_Knight.

  2. 2

    Select the Knight, add an Animator component, drag AC_Knight into Controller and the model's Avatar into Avatar.

  3. 3

    Open the Window▸Animation▸Animator window and drag your Idle clip in — it becomes the orange default state.

  4. 4

    In the Parameters tab add: Speed (Float), IsGrounded (Bool), Attack (Trigger).

A blend tree beats separate walk/run states

  1. 1

    Right-click in the Animator → Create State → From New Blend Tree. Name it Locomotion and make it the default.

  2. 2

    Double-click it. Set the Blend parameter to Speed, then add three motions: Idle at 0, Walk at 2, Run at 6.

  3. 3

    Now one float smoothly crossfades idle → walk → run. No transition arrows needed for locomotion at all.

Driving it from code

PlayerAnimator.cs — on the Knight, next to PlayerMovement
using UnityEngine;

[RequireComponent(typeof(Animator))]
public class PlayerAnimator : MonoBehaviour
{
    private Animator anim;
    private CharacterController cc;

    // hashes are faster than strings
    private static readonly int SpeedHash    = Animator.StringToHash("Speed");
    private static readonly int GroundedHash = Animator.StringToHash("IsGrounded");
    private static readonly int AttackHash   = Animator.StringToHash("Attack");

    void Awake()
    {
        anim = GetComponent<Animator>();
        cc = GetComponent<CharacterController>();
    }

    void Update()
    {
        // horizontal speed only — vertical fall must not look like running
        Vector3 flat = new Vector3(cc.velocity.x, 0f, cc.velocity.z);

        anim.SetFloat(SpeedHash, flat.magnitude, 0.1f, Time.deltaTime); // 0.1f = damping
        anim.SetBool(GroundedHash, cc.isGrounded);

        if (Input.GetMouseButtonDown(0))
            anim.SetTrigger(AttackHash);
    }
}
  • The third and fourth arguments of SetFloat are damp time and delta time: the value eases instead of jumping, so the blend never pops.

  • A Trigger is a bool that resets itself after being consumed — exactly right for one-shot actions like Attack.

  • StringToHash once in a static field: string lookups every frame are wasted work.

Transition settings that fix 90% of jank

  • Uncheck Has Exit Time on any transition that must react to the player (attack, jump). Leave it on for animations that must finish.

  • Transition Duration 0.1–0.2 s feels responsive. Above 0.3 s the character feels drunk.

  • Interruption Source → Current State lets a new attack cut into the previous one instead of queueing.

Animation Events — hit exactly on the swing

Damage should not apply when you press the button; it should apply when the sword is actually out. Animation Events call a method at a chosen frame of the clip.

C# script
// on the same GameObject as the Animator
public void OnAttackHit()      // pick this name in the clip's Events row
{
    Debug.Log("hit frame");
    // damage the enemies in range
}