Summary

The Animator component connects a GameObject to an Animator Controller, which defines animation clips and the transitions between them. Scripts drive the Animator by setting named parameters — booleans, floats, integers, or triggers — that the Animator Controller reads to decide which clip to play and when to transition. The script does not directly choose clips; it sets values and lets the Animator Controller respond.

That division remains the standard production-friendly workflow in current Unity material as well: code exposes state, while the controller and clips own timing, blends, and animation-side transitions. (Unity, The definitive guide to animation in Unity, see source-unity-animation-guide)


Key ideas

Parameter types:

MethodParameter typeBehaviourUse for
SetBool(name, value)BoolStays true/false until changedSustained states: IsMoving, IsGrounded
SetFloat(name, value)FloatContinuous valueBlend tree inputs: Speed
SetInteger(name, value)IntInteger stateDirection facing, weapon index
SetTrigger(name)TriggerSet until a transition consumes it, then reset by the controllerOne-shot events: Hit, Die, Win

Parameter name constants: Animator parameter names are plain strings. A typo in a string is not a compile error: at runtime the animation simply does not change, and the only sign is a Console warning that the parameter does not exist, which is easy to miss among other messages. Store parameter names as private const string fields. Each name is then typed once, and misspelling the constant’s name where it is used becomes a compile error. The string value inside the constant must still match the Animator Controller exactly, including capitals. (Corrected 2026-10-25: this paragraph previously said no error is logged.)

GameState enum: Defining a GameState enum (Playing, Won, Lost, Paused) and guarding state-changing methods against invalid transitions prevents duplicate triggers and logical contradictions (e.g. dying while already dead).

Time.timeScale: Setting Time.timeScale = 0f freezes Unity’s time — physics, Update delta, and animations all pause. Setting it back to 1f resumes everything. Always reset timeScale to 1f before loading a new scene, otherwise the new scene starts frozen.

Invoke("MethodName", delay): Schedules a private method to be called after a set number of seconds. Useful for letting a death or win animation play before transitioning to the next scene.


In practice

Caching the Animator:

[SerializeField] private Animator playerAnimator;
 
private void Start()
{
    // Fallback if not assigned in Inspector
    if (playerAnimator == null)
        playerAnimator = GetComponent<Animator>();
 
    if (playerAnimator == null)
        Debug.LogError("[AnimationController] No Animator found!");
}

Parameter name constants (prevents silent typo bugs):

private const string PARAM_IS_MOVING = "IsMoving";
private const string PARAM_SPEED     = "Speed";
private const string PARAM_HIT       = "Hit";
private const string PARAM_DIE       = "Die";
private const string PARAM_WIN       = "Win";

Driving movement animation each frame:

private void HandleMovementAnimation()
{
    if (playerAnimator == null) return;
 
    float h = Input.GetAxisRaw("Horizontal");
    float v = Input.GetAxisRaw("Vertical");
    bool isMoving = (h != 0f || v != 0f);
 
    playerAnimator.SetBool(PARAM_IS_MOVING, isMoving);
    playerAnimator.SetFloat(PARAM_SPEED, new Vector2(h, v).magnitude);
}

One-shot trigger (hit reaction):

public void PlayHitAnimation()
{
    if (playerAnimator == null) return;
    playerAnimator.SetTrigger(PARAM_HIT);  // fires once, resets automatically
}

Game state enum with state-guarded transitions:

public enum GameState { Playing, Won, Lost, Paused }
 
private GameState currentState = GameState.Playing;
 
public void TriggerGameOver()
{
    if (currentState != GameState.Playing) return;  // guard
    currentState = GameState.Lost;
    playerAnimator.SetTrigger(PARAM_DIE);
    Invoke("LoadGameOverScene", transitionDelay);   // delay for animation
}
 
private void LoadGameOverScene()
{
    SceneManager.LoadScene(gameOverSceneName);
}

Pause / unpause:

public void TogglePause()
{
    if (currentState == GameState.Playing)
    {
        currentState = GameState.Paused;
        Time.timeScale = 0f;  // freeze everything
    }
    else if (currentState == GameState.Paused)
    {
        currentState = GameState.Playing;
        Time.timeScale = 1f;  // resume
    }
}
 
public void RestartScene()
{
    Time.timeScale = 1f;  // always restore before loading
    SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}

Gotchas

  • A trigger is “a boolean parameter that is reset by the controller when consumed by a transition” (Unity Technologies, Manual: Animation Parameters). If no transition from the current state uses it, it is not cleared: it stays armed and can fire a transition later, at an unexpected moment. Make sure each trigger has a transition that consumes it, or call ResetTrigger once the moment has passed. (Corrected 2026-10-25: this gotcha previously said unconsumed triggers “accumulate”; a trigger is a single flag, not a counter.)
  • Invoke matches the method name by string. A renamed method will not cause a compile error — the invocation will silently do nothing at runtime. Test after any rename.
  • Time.timeScale = 0f stops Update delta time but not all code. Coroutines using WaitForSecondsRealtime are unaffected; those using WaitForSeconds are paused.
  • Never call SetTrigger every frame for a sustained state — use SetBool instead. A trigger set every frame is re-armed as soon as a transition consumes it, so the one-shot transition keeps firing again.

Practice

Animate a player square with two clips (for example a still pose and a wobble) and drive them from a script:

  • an Animator Controller with a Bool IsMoving, a Trigger Hit, and transitions Idle ↔ Move on IsMoving and Any State → Hit on Hit
  • a script that stores those names as private const string fields, sets IsMoving from the movement axes each frame, and calls SetTrigger on H
  • P toggles pause through a GameState enum and Time.timeScale
  • R restarts the scene

Success test:

  • the Move clip plays only while a movement key is held
  • one press of H plays the hit reaction once
  • while paused, the player and its animation stop; unpausing resumes them
  • restarting from the paused state does not load a frozen scene

Extension:

  • add a Die trigger and a TriggerGameOver method that waits for the death clip with Invoke before loading a game-over scene, and check that calling it twice changes nothing the second time

Self-test

  1. Which parameter type suits IsMoving, and which suits Hit? Why?
  2. What does storing parameter names in private const string fields protect against, and what does it not protect against?
  3. What goes wrong if Time.timeScale is still 0 when a new scene loads?
  4. Why does TriggerGameOver start with if (currentState != GameState.Playing) return;?
  5. You rename LoadGameOverScene but forget to update the Invoke call. What happens?
  6. While Time.timeScale is 0, does a coroutine waiting on WaitForSeconds continue? What about WaitForSecondsRealtime?

Answers

  1. IsMoving is a Bool because it is a sustained state that stays true until changed. Hit is a Trigger because it is a one-shot event that the controller resets once a transition uses it.
  2. Each name is typed once, and a misspelt constant name is a compile error. The string value inside the constant must still match the controller exactly.
  3. The new scene starts frozen, because physics, Update delta time and animations stay paused.
  4. It guards against invalid transitions, so game over cannot be triggered twice or while the game is already won, lost or paused.
  5. Nothing is called and there is no compile error, because Invoke matches the method by its string name.
  6. WaitForSeconds is paused; WaitForSecondsRealtime is unaffected.