Quick answer: The most common cause is a transition that never triggers. Check that your transition conditions use the correct parameter names (they are case-sensitive), that you are setting the right parameter type (Bool, Trigger, Float, or Int), and that 'Has Exit Time' is not blocking the transition from firin...

Here is how to fix Unity animation not playing animator. You set up your Animator Controller, created the states, added transitions, and wrote the code to trigger them — but the animation just sits on the idle state and refuses to play. You call SetTrigger("Attack") and nothing happens. Or the character gets stuck in one animation and will not transition out. Unity’s Animator state machine is powerful but unforgiving: a single mismatched parameter name or an overlooked checkbox can silently prevent every transition from firing.

The Symptom

You have an Animator Controller with multiple animation states and transitions between them. In the Animator window at runtime, you can see the state machine — the current state is highlighted in blue (or orange during transitions). But when your code calls SetTrigger, SetBool, or SetFloat, the expected transition never happens. The Animator stays locked on the current state.

Alternatively, the transition partially works: you can see the Animator enter the transition (orange highlight), but it immediately snaps back to the previous state instead of completing. Or the animation plays once but then freezes on the last frame instead of looping or transitioning back to idle.

In some cases, the Animator window shows the parameter value changing when you inspect it at runtime, confirming that your code is setting it correctly. But the transition still does not fire, which means the problem is in the transition configuration itself, not your C# code.

What Causes This

The Animator state machine has several configuration points that must all align for a transition to fire:

1. Parameter name mismatch. Animator parameter names are case-sensitive. If your code calls SetTrigger("attack") but the parameter in the Animator is named "Attack", Unity will log a warning in the Console but the trigger will not fire. The same applies to SetBool, SetFloat, and SetInteger. This is the most common cause and the easiest to miss because the names can look identical at a glance.

2. Has Exit Time is blocking the transition. Every transition in the Animator has a "Has Exit Time" checkbox. When enabled, the transition will only start after the current animation has played for the specified Exit Time fraction (0 to 1). If your current state is a 3-second animation with Exit Time set to 1.0, the transition cannot fire until those 3 seconds are up — even if the trigger condition is already true. For responsive gameplay (attacks, jumps, damage reactions), Has Exit Time should usually be disabled.

3. Wrong parameter type. The Animator has four parameter types: Bool, Trigger, Float, and Int. If you create a Bool parameter but call SetTrigger on it (or vice versa), Unity logs a warning and the value does not change. Each type requires its matching setter method. Triggers are particularly confusing because they are one-shot (they auto-reset after being consumed by a transition), while Bools persist until explicitly set back.

4. No Animator Controller assigned. The Animator component exists on the GameObject but has no Controller assigned in its Controller field. This can happen when prefabs are duplicated, when the Animator Controller asset is moved or deleted, or when the component is added via script without assigning a controller.

5. Transition conditions conflict. A transition with multiple conditions requires ALL of them to be true simultaneously. If you have a transition from Idle to Attack that requires both isAttacking == true AND isGrounded == true, the transition will not fire unless both parameters are set correctly at the same time.

The Fix

Step 1: Verify parameter names and types match exactly between code and the Animator.

Use string constants or Animator.StringToHash to avoid typos and improve performance:

using UnityEngine;

public class PlayerAnimator : MonoBehaviour
{
    private Animator anim;

    // Cache parameter hashes to avoid string lookups every frame
    private static readonly int IsRunning = Animator.StringToHash("IsRunning");
    private static readonly int IsGrounded = Animator.StringToHash("IsGrounded");
    private static readonly int JumpTrigger = Animator.StringToHash("Jump");
    private static readonly int AttackTrigger = Animator.StringToHash("Attack");
    private static readonly int Speed = Animator.StringToHash("Speed");

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

        if (anim.runtimeAnimatorController == null)
        {
            Debug.LogError("No Animator Controller assigned!", this);
        }
    }

    void Update()
    {
        float speed = Input.GetAxis("Horizontal");
        anim.SetFloat(Speed, Mathf.Abs(speed));
        anim.SetBool(IsRunning, Mathf.Abs(speed) > 0.1f);

        if (Input.GetButtonDown("Jump"))
        {
            anim.SetTrigger(JumpTrigger);
        }

        if (Input.GetButtonDown("Fire1"))
        {
            anim.SetTrigger(AttackTrigger);
        }
    }
}

Using Animator.StringToHash converts the string to an integer hash at startup, so subsequent calls use the hash instead of doing a string comparison every frame. More importantly, it documents your parameter names in one place. If a parameter name changes, you only update it in the hash declaration.

Step 2: Disable Has Exit Time on gameplay-critical transitions.

In the Animator window, click on the transition arrow between states. In the Inspector, uncheck Has Exit Time for transitions that should be responsive (idle-to-attack, idle-to-jump, any-state-to-hurt). Set the Transition Duration to 0 or a very small value for snappy animations:

using UnityEngine;

public class AnimatorDebug : MonoBehaviour
{
    private Animator anim;

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

    void Update()
    {
        // Log the current Animator state for debugging
        AnimatorStateInfo state = anim.GetCurrentAnimatorStateInfo(0);
        AnimatorTransitionInfo transition = anim.GetAnimatorTransitionInfo(0);

        if (anim.IsInTransition(0))
        {
            Debug.Log($"In transition, progress: {transition.normalizedTime:F2}");
        }
        else
        {
            Debug.Log($"State: {state.shortNameHash}, time: {state.normalizedTime:F2}");
        }
    }

    // Call this from a UI button or debug key to force-test a trigger
    public void TestTrigger(string triggerName)
    {
        // Reset first in case a previous trigger is stuck
        anim.ResetTrigger(triggerName);
        anim.SetTrigger(triggerName);
        Debug.Log($"Trigger '{triggerName}' fired. Animator enabled: {anim.enabled}");
    }
}

The ResetTrigger call before SetTrigger is important. Triggers can get "stuck" if they were set but never consumed (because the transition conditions were not met at the time). A stuck trigger can cause unexpected transitions later, or prevent the same trigger from being set again since it is already in the set state.

Step 3: Confirm the Animator Controller is assigned, the Animator is enabled, and animation clips are not empty.

using UnityEngine;

public class AnimatorValidator : MonoBehaviour
{
    void Start()
    {
        Animator anim = GetComponent<Animator>();

        if (anim == null)
        {
            Debug.LogError("No Animator component found.", this);
            return;
        }

        if (!anim.enabled)
        {
            Debug.LogError("Animator is disabled. Enable it in the Inspector.", this);
        }

        if (anim.runtimeAnimatorController == null)
        {
            Debug.LogError("No Animator Controller assigned to the Animator.", this);
            return;
        }

        // List all parameters for verification
        foreach (AnimatorControllerParameter param in anim.parameters)
        {
            Debug.Log($"Parameter: '{param.name}' Type: {param.type}");
        }

        // List all clips to check for empty or missing animations
        AnimationClip[] clips = anim.runtimeAnimatorController.animationClips;
        foreach (AnimationClip clip in clips)
        {
            if (clip == null)
            {
                Debug.LogWarning("Found a null animation clip in the controller!");
            }
            else
            {
                Debug.Log($"Clip: '{clip.name}' Length: {clip.length}s Loop: {clip.isLooping}");
            }
        }
    }
}

Run this validator script on your animated object. It will print every parameter name and type (so you can cross-check against your code), every animation clip (so you can spot null or zero-length clips), and whether the Animator is properly configured.

Why This Works

Parameter hash matching eliminates the most common source of failures: typos. By centralizing parameter names as static hash fields, you get compile-time safety (the string exists in one place) and runtime performance (hash lookups are faster than string comparisons). If a parameter does not exist in the Animator Controller, Unity logs a warning the first time you try to set it — check the Console.

Disabling Has Exit Time tells the Animator that the transition should be purely condition-driven. With Has Exit Time on, the Animator insists on playing the current clip to the specified point before transitioning, even if the condition has been true for seconds. For gameplay actions that need to feel responsive, this delay is unacceptable. For cinematic or ambient animations (like a character finishing a sitting-down animation before standing up), Has Exit Time is appropriate.

Controller validation catches the "everything looks right but nothing works" scenario. A missing controller reference, a disabled component, or a null clip in a state are all silent failures — the Animator does not throw exceptions, it simply does not animate. Explicit validation at startup surfaces these issues immediately.

"The Animator does not throw errors. It just silently refuses. If your animation is not playing, the answer is in the Animator window at runtime, not in your code."

Related Issues

If your animation plays but references a component that turns out to be null at runtime, the problem is likely a missing component on the animated object — see NullReferenceException on GetComponent. If animations work in the editor but break after building prefabs, the issue may be with how prefab overrides affect Animator Controller references — check our guide on prefab changes not saving.

Use Animator.StringToHash once. Typos in parameter names waste hours.