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)TriggerFires once, resets automaticallyOne-shot events: Hit, Die, Win

Parameter name constants: Animator parameter names are plain strings. A typo in a string causes silent failure at runtime — the animation simply does not play and no error is logged. Store parameter names as private const string fields so typos become compile errors instead.

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

  • SetTrigger fires once and resets. If the Animator Controller has no transition that consumes it, the trigger accumulates and fires multiple times unexpectedly. Ensure every trigger has at least one outgoing transition.
  • 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 called every frame will queue indefinitely in the Animator.