Animator Controller
Animator Controller is a visual state machine that owns Animation Clips — it decides when and how to blend Idle, Walk, Run, Jump, Attack, and every other state.
Imagine...
An Animator Controller is the character’s stage director. It knows which state they are in (idle, run, jump) and, when a condition is met (jump button, speed > 0), transitions to the next clip smoothly. You declare States as clips and draw “arrows” (Transitions) with conditions — the controller fires each arrow on its own.
The concept in detail
An Animator Controller is an asset
(.controller)
with one or more Layers, each an independent state machine. The Base layer
usually holds locomotion; upper layers can override the torso (throw while running). Each
layer has a Weight (0–1) and a Blending Mode
(Override/Additive).
Inside a layer, States (orange/green boxes) are Animation Clips, and
Transitions (arrows) define when to switch. A transition has
Has Exit Time
(wait for the clip to finish) and
Transition Duration
(blend time). Parameters (Int, Float, Bool, Trigger) are the variables the
Animator reads to pick a transition.
A Sub-State Machine groups complex states into one node (e.g. all attack variants under “Combat”). Any State is special — a transition from Any State can fire from whatever the current state is, used for Death or Hit interrupts.
From Unity 2022+, the Animation Rigging package adds IK on top of the Animator: runtime hand/foot poses (VR controllers, climbing) without a dedicated clip.
A basic character state machine
Hands-on steps
Create an Animator Controller
Project → Create → Animator Controller. Double-click to open the Animator window. Drop Animation Clips in to create States.
Add Parameters
Animator window → Parameters tab (+) → add Float "Speed", Bool "IsGrounded", Trigger "Jump". These are the variables C# will set.
Create a Transition with a condition
Right-click a State → Make Transition → click the target. Select the Transition → Inspector → Conditions → add (Speed > 0.1).
Assign it to a GameObject
Add Component → Animator → drop the Controller into "Controller". Make sure the Avatar is correct (Humanoid setup).
Drive it from script
animator.SetFloat("Speed", velocity.magnitude) in Update(). Cache hashes with Animator.StringToHash() instead of strings.
Interactive simulator
Click a state to jump there, or use the control buttons to fire conditional transitions.
Code example
BasicDrive Animator parameters from a PlayerController — basic locomotion with Speed and a Jump trigger.
using UnityEngine;
public class AnimatorDriver : MonoBehaviour
{
Animator anim;
// Cache hashes in Awake — faster than a string every frame
static readonly int SpeedHash = Animator.StringToHash("Speed");
static readonly int JumpHash = Animator.StringToHash("Jump");
static readonly int GroundHash = Animator.StringToHash("IsGrounded");
static readonly int DeathHash = Animator.StringToHash("Death");
void Awake() => anim = GetComponent<Animator>();
void Update()
{
Vector3 vel = GetComponent<Rigidbody>().velocity;
anim.SetFloat(SpeedHash, vel.magnitude, 0.1f, Time.deltaTime);
anim.SetBool(GroundHash, isGrounded);
if (Input.GetKeyDown(KeyCode.Space))
anim.SetTrigger(JumpHash); // Trigger auto-resets after 1 frame
}
public void Die()
{
anim.SetTrigger(DeathHash);
anim.SetLayerWeight(1, 0f); // Disable layer 1 on death
}
}Code example
AdvancedPlayInFixedTime, CrossFade, and reading the current state — fine-grained transitions from code.
using UnityEngine;
public class AdvancedAnimator : MonoBehaviour
{
Animator anim;
static readonly int BaseLayer = 0;
void Awake() => anim = GetComponent<Animator>();
// CrossFade: switch to a named state with a custom blend duration
public void PlayAttack(string attackName)
{
anim.CrossFadeInFixedTime(attackName, 0.15f, BaseLayer);
}
// Is the character in a given state (name or tag)
bool IsInState(string stateName)
{
return anim.GetCurrentAnimatorStateInfo(BaseLayer)
.IsName(stateName);
}
// Scale animation playback from gameplay
public void SetAttackSpeed(float speedMultiplier)
{
anim.SetFloat("AttackSpeed", speedMultiplier);
}
// Read normalized time to know how far the clip has played
void Update()
{
var info = anim.GetCurrentAnimatorStateInfo(BaseLayer);
if (info.normalizedTime >= 0.9f && info.IsTag("Attack"))
{
Debug.Log("Attack almost done — combo window");
}
}
}📌 Quick recap
- ▸A Trigger auto-resets after 1 frame (Jump, Hit)
- ▸Animator.StringToHash() → cache in Awake, avoid string GC
- ▸Any State → interrupt from any current state
- ▸SetFloat(hash, value, damping, dt) for smoother blends
- ▸normalizedTime: 0 = start of clip, 1 = end of clip
⚠️ Common mistakes
❌ Trigger does nothing — the state never changes
Has Exit Time is true and the clip has not finished — the trigger is ignored
✅ Turn off Has Exit Time for transitions that should fire anytime
❌ Animation does not blend — it hard-cuts
Transition Duration = 0
✅ Set Transition Duration to 0.1–0.25s to blend