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:
| Method | Parameter type | Behaviour | Use for |
|---|---|---|---|
SetBool(name, value) | Bool | Stays true/false until changed | Sustained states: IsMoving, IsGrounded |
SetFloat(name, value) | Float | Continuous value | Blend tree inputs: Speed |
SetInteger(name, value) | Int | Integer state | Direction facing, weapon index |
SetTrigger(name) | Trigger | Set until a transition consumes it, then reset by the controller | One-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
ResetTriggeronce the moment has passed. (Corrected 2026-10-25: this gotcha previously said unconsumed triggers “accumulate”; a trigger is a single flag, not a counter.) Invokematches 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 = 0fstopsUpdatedelta time but not all code. Coroutines usingWaitForSecondsRealtimeare unaffected; those usingWaitForSecondsare paused.- Never call
SetTriggerevery frame for a sustained state — useSetBoolinstead. 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 TriggerHit, and transitions Idle ↔ Move onIsMovingand Any State → Hit onHit - a script that stores those names as
private const stringfields, setsIsMovingfrom the movement axes each frame, and callsSetTriggeron H - P toggles pause through a
GameStateenum andTime.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
Dietrigger and aTriggerGameOvermethod that waits for the death clip withInvokebefore loading a game-over scene, and check that calling it twice changes nothing the second time
Self-test
- Which parameter type suits
IsMoving, and which suitsHit? Why? - What does storing parameter names in
private const stringfields protect against, and what does it not protect against? - What goes wrong if
Time.timeScaleis still0when a new scene loads? - Why does
TriggerGameOverstart withif (currentState != GameState.Playing) return;? - You rename
LoadGameOverScenebut forget to update theInvokecall. What happens? - While
Time.timeScaleis0, does a coroutine waiting onWaitForSecondscontinue? What aboutWaitForSecondsRealtime?
Answers
IsMovingis a Bool because it is a sustained state that stays true until changed.Hitis a Trigger because it is a one-shot event that the controller resets once a transition uses it.- 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.
- The new scene starts frozen, because physics,
Updatedelta time and animations stay paused. - It guards against invalid transitions, so game over cannot be triggered twice or while the game is already won, lost or paused.
- Nothing is called and there is no compile error, because
Invokematches the method by its string name. WaitForSecondsis paused;WaitForSecondsRealtimeis unaffected.
Related
- unity-animator-controller — the editor-side animation state machine this scripting layer drives
- monobehaviour-lifecycle —
Updatedrives per-frame animation updates;Startcaches the Animator - unity-getcomponent — fallback
GetComponent<Animator>()pattern - unity-gamemanager-pattern —
SceneManager.LoadSceneand game state management - unity-scene-management — scene list, restart, loading screens and the full Title → Game → Game Over/Win flow
- csharp-control-flow —
enumand state guardifchecks