You already know how to make an Animator play a single clip, like Idle or Run. That's fine as long as the character never changes what it's doing. The moment the player pushes forward while the character is standing still, you hit a wall: how do you go from the Idle pose to the Run pose without it looking like a jump cut in a badly edited video? The answer, in almost every game you've ever played, is that the engine never actually "switches" — it blends. This chapter is about how Unity's Animator does that blending, and how you drive it from C#.
If you switch clips with animator.Play("Run"), Unity throws away whatever pose is currently showing and jumps straight to frame 0 of Run on the very next frame. There's no in-between. The legs teleport from one shape to another in a single frame.
The fix nearly every game uses is blending: instead of switching instantly, you mix two (or more) poses together using a blend weight (a number from 0 to 1 saying how much of a clip's pose counts toward the final result). A weight of 1.0 means "100% this clip." A weight of 0.0 means "0% this clip, ignore it completely." A weight of 0.5 means "average this clip's pose and another clip's pose 50/50, bone by bone."
Unity's Animator gives you three tools that are all really the same idea — blend by weight — applied in different situations:
This chapter builds all three, then shows the state machine that decides which state (a clip or a blend tree) is active, and how to drive the whole thing from your own C# scripts.
The simplest blend is a cross-fade: tell the Animator "start playing this state, and blend into it over N seconds." Unity lowers the old state's weight and raises the new one's, every frame, until the new state reaches a weight of 1.0 and fully owns the pose.
using UnityEngine;
public class SimpleCrossFade : MonoBehaviour
{
public Animator animator;
void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
// Blend from whatever is playing right now into "Run"
// over 0.25 seconds.
animator.CrossFade("Run", 0.25f);
}
}
}
There's no console output for this — it's a visual effect you'd see in the Game view. But you can trace exactly what weight Unity is using each frame during that 0.25 second blend:
"Pose" means the position and rotation of every bone in the character's rig (from the skinned mesh chapter). Each frame, Unity samples both clips at the current time, multiplies every bone's transform by that clip's weight, and adds the two results together. That's why a half-and-half blend between a standing pose and a running pose looks like a smooth, physically plausible in-between body — not two characters overlapping like a ghost.
CrossFade(stateName, duration) takes the name of the state to blend into and how many seconds the blend should take. There's a related method, CrossFadeInFixedTime, which measures the transition duration in real seconds no matter how long the target clip is (handy when timing must be exact, like syncing to a sound effect).
CrossFade again before the first blend finishes. Unity doesn't force you to wait — it starts blending from whatever mix of poses is currently on screen into the new target. That's what makes combos and quick direction changes feel responsive instead of sluggish.Cross-fading is great for one-off, code-triggered transitions. But locomotion — walking that gradually speeds up into running — needs something that blends continuously as a number changes, not just once between two named clips. That's what a blend tree is for.
A Blend Tree is a special kind of state in the Animator: instead of holding one clip, it holds several clips and blends between them based on one or more parameters (named values stored on the Animator, covered fully in section 5). A 1D Blend Tree uses exactly one float parameter — typically called Speed — and a list of motions, each tagged with a threshold: the parameter value at which that motion should play at full weight.
You build a 1D Blend Tree in the Animator window (right-click a state, Create State, From New Blend Tree, then open it), not in code. Inside it you'd set:
1DSpeedIdle at threshold 0, Walk at threshold 2, Run at threshold 6At runtime, Unity looks at the current value of Speed, finds the two motions whose thresholds it falls between, and blends only those two. Everything else gets weight 0.
The math is linear interpolation (from the math/vectors chapter): find how far Speed is between the two neighboring thresholds as a fraction from 0 to 1, and use that fraction as the weight split. As Speed keeps rising past 2.0, Walk's weight keeps falling and Run's keeps rising, with no seam — no frame where one clip abruptly replaces another.
Your job in C# is just to keep Speed up to date, usually smoothed so it doesn't jump instantly when the player taps a key:
using UnityEngine;
public class Locomotion1D : MonoBehaviour
{
public Animator animator;
public float maxSpeed = 6f;
public float acceleration = 10f;
private float currentSpeed;
void Update()
{
float input = Mathf.Clamp01(Mathf.Abs(Input.GetAxis("Vertical")));
float targetSpeed = input * maxSpeed;
currentSpeed = Mathf.MoveTowards(currentSpeed, targetSpeed, acceleration * Time.deltaTime);
animator.SetFloat("Speed", currentSpeed);
}
}
Worked trace: say the player holds "forward" fully (Input.GetAxis("Vertical") == 1), so targetSpeed = 6. Starting from currentSpeed = 0, with acceleration = 10 and a frame time of about 0.016s (60 FPS), currentSpeed rises roughly 0.16 per frame: 0 -> 0.16 -> 0.32 -> ... until it reaches 6 about 0.6 seconds later. While that's happening, the blend tree is smoothly sliding from Idle through Walk and into Run, exactly the way the diagram above describes, frame by frame.
Speed directly from raw input (animator.SetFloat("Speed", input * maxSpeed)) with no smoothing. The character's legs will visibly snap speed the instant a key is pressed or released, because the blend tree is being fed a value that jumps instantly instead of ramping. Always smooth the parameter in code (Mathf.MoveTowards, Mathf.SmoothDamp, or Animator.SetFloat's own dampTime overload from section 9) before feeding it to the Animator.A 2D Blend Tree works the same way but uses two float parameters at once — usually MoveX (strafe, left/right) and MoveZ (forward/back) — so you can blend between clips laid out over a 2D plane instead of a single line. This is what lets a character strafe-run diagonally with a blend of RunForward and RunRight, instead of only ever running straight ahead.
In the Animator window you'd pick a 2D blend type and place each motion at a 2D point:
Unity offers a few different 2D blend types, and picking the right one matters:
For a normal 8-direction movement set (forward, back, left, right, and the four diagonals), Freeform Directional is the usual choice. Driving it from C# means keeping MoveX and MoveZ updated with the character's local movement direction:
using UnityEngine;
public class Locomotion2D : MonoBehaviour
{
public Animator animator;
public float smoothTime = 0.1f;
private Vector2 currentBlend;
private Vector2 blendVelocity;
void Update()
{
float h = Input.GetAxisRaw("Horizontal"); // strafe: -1 left, +1 right
float v = Input.GetAxisRaw("Vertical"); // -1 back, +1 forward
Vector2 targetBlend = new Vector2(h, v);
currentBlend = Vector2.SmoothDamp(currentBlend, targetBlend, ref blendVelocity, smoothTime);
animator.SetFloat("MoveX", currentBlend.x);
animator.SetFloat("MoveZ", currentBlend.y);
}
}
Worked trace: the player holds forward and right together, so h = 1, v = 1, and targetBlend = (1, 1). Vector2.SmoothDamp eases currentBlend toward that point over roughly smoothTime seconds instead of snapping there. Once currentBlend settles near (1, 1), the 2D blend tree finds the motions closest to that direction — likely RunForward and RunRight, or a dedicated RunForwardRight clip if you authored one — and blends them so the character visibly runs diagonally, feet matching the direction of travel.
MoveX/MoveZ relative to the character's own facing, not the world. If the parameters are world-space but the blend tree's clips are authored as "run forward relative to the character," turning the character will make the blend pick the wrong clip for the direction it looks like you're moving in.Blend trees decide how to blend within one node. The state machine decides which node is active at all — is the character in Locomotion, or Jump, or Attack? You build it visually in the Animator window as a graph of boxes and arrows.
Float, Int, Bool, or Trigger. Transitions read them to decide when to fire; blend trees read the Float ones to decide how to blend.Trigger deserves special attention: unlike Bool, which stays whatever you last set it to, a Trigger is "armed" by SetTrigger and then automatically cleared the moment a transition that uses it actually fires. It behaves like a one-shot event, which is exactly what you want for "jump now" — you don't want the character to keep re-jumping every frame just because a bool stayed true.
You can inspect what parameters exist on an Animator entirely from code, which is handy for debugging a controller you didn't build yourself:
using UnityEngine;
public class ListAnimatorParameters : MonoBehaviour
{
public Animator animator;
void Start()
{
foreach (AnimatorControllerParameter p in animator.parameters)
{
Debug.Log(p.name + " : " + p.type);
}
}
}
Console output for a typical locomotion controller:
Speed : Float
Grounded : Bool
Jump : Trigger
Note that the parameter names are plain strings, and they're case-sensitive. That single fact causes more silent bugs than almost anything else in this chapter, covered in section 10.
Every transition arrow in the graph has its own blend settings, separate from the CrossFade call you write in code. The two settings that matter most for a beginner:
0, it's an instant snap, not a blend, even though it's technically still a "transition."These two settings interact in a way that trips up almost everyone the first time:
For fast, responsive actions (jump, dodge, attack cancels) you usually want Has Exit Time off, so the transition fires the instant the condition is true. For animations that should always finish playing once started (a big finisher move, a death animation), you keep it on.
Sometimes a transition is already blending when a new, more urgent condition becomes true — the player was blending from Walk into Attack, and now wants to Jump instead. The Interruption Source setting on a transition controls whether that's allowed, and if so, which state's own outgoing transitions are allowed to interrupt it (None, Current State, Next State, or both).
With interruption allowed, the blend doesn't restart from scratch — it keeps whatever mix of the old state is already showing and redirects the incoming half toward the new target. Without it, the character has to finish blending fully into B before a transition out of B can even be considered, which feels laggy for anything that needs to cancel quickly.
Everything so far blends whole-body poses. But often you want two things happening at once on different parts of the body: legs running from the locomotion state machine, while the upper body independently aims at a target. That's what Layers are for.
The Animator window has a Layers panel. Each layer is its own independent state machine, and its result is blended on top of the layers below it:
Locomotion/Jump/etc. state machine.Aim state), blend mode set to Override, and an Avatar Mask assigned that only includes the spine, arms, and head bones.An Avatar Mask is an asset (right-click, Create, Avatar Mask) with a checkbox per bone group. Unmask the legs on the aim layer's mask, and whatever that layer plays simply can't touch leg bones at all — no matter what clip is in it, the legs stay under the base layer's control.
using UnityEngine;
public class AimLayerController : MonoBehaviour
{
public Animator animator;
private int aimLayerIndex;
void Start()
{
aimLayerIndex = animator.GetLayerIndex("UpperBodyAim");
}
void Update()
{
bool isAiming = Input.GetMouseButton(1);
float targetWeight = isAiming ? 1f : 0f;
float current = animator.GetLayerWeight(aimLayerIndex);
animator.SetLayerWeight(aimLayerIndex, Mathf.MoveTowards(current, targetWeight, 5f * Time.deltaTime));
}
}
Worked trace: while the player holds the right mouse button, targetWeight is 1, and SetLayerWeight ramps the aim layer's weight up from 0 to 1 over about 0.2 seconds (1 / 5). During that ramp, the character's legs keep running exactly as the base layer says, while the upper body gradually rotates into the aim pose — not a snap, a proper cross-fade of just the masked bones.
Override blend mode replaces the entire body, not just the upper half — so as soon as its weight rises above 0, the legs freeze into the aim clip's pose and stop running.There are two very different ways an animation clip can represent movement, and mixing them up is a classic source of "my character glides" or "my character's feet slide on the ground" bugs.
Toggle this with the Apply Root Motion checkbox on the Animator component, or the same flag in code, animator.applyRootMotion. When it's on, Unity calls a special method, OnAnimatorMove, once per frame right after evaluating the animation, and moving the object there instead of in Update keeps movement perfectly matched to the feet:
using UnityEngine;
[RequireComponent(typeof(Animator))]
public class RootMotionRelay : MonoBehaviour
{
private Animator animator;
void Awake()
{
animator = GetComponent<Animator>();
animator.applyRootMotion = true;
}
// Unity calls this automatically, once per frame, only while
// Apply Root Motion is enabled.
void OnAnimatorMove()
{
transform.position += animator.deltaPosition;
transform.rotation *= animator.deltaRotation;
}
}
Root motion looks great for grounded, foot-perfectly-matched movement, but it's harder to control precisely (turning radius and speed are baked into the clip, not a parameter you tune), and it doesn't work well with physics-driven movement. Most action games use in-place clips for locomotion and move the character with code (a CharacterController or Rigidbody, from the physics chapters) driven by the same Speed/MoveX/MoveZ values feeding the blend tree, so the visual animation speed and the actual movement speed always agree. Root motion tends to be reserved for animation-critical moments like a scripted attack lunge or a climb, where exact foot placement matters more than precise player control.
Everything above happens because code sets parameters and the Animator reacts. This section is the reference for both directions: writing parameters in, and reading state back out.
using UnityEngine;
public class AnimatorParameterDemo : MonoBehaviour
{
public Animator animator;
public bool isGrounded;
void Update()
{
// Float: drives 1D/2D blend trees directly
float speed = new Vector3(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical")).magnitude;
animator.SetFloat("Speed", speed);
// Float with built-in smoothing: eases toward the value over
// dampTime seconds instead of snapping to it in one frame
animator.SetFloat("Speed", speed, 0.1f, Time.deltaTime);
// Bool: stays true or false until you change it again
animator.SetBool("Grounded", isGrounded);
// Int: useful for combo steps, weapon type, etc.
animator.SetInteger("ComboIndex", 2);
// Trigger: fires once; Unity clears it automatically once a
// transition that consumes it actually fires
if (Input.GetButtonDown("Jump"))
{
animator.SetTrigger("Jump");
}
// ResetTrigger: cancels a trigger that has not fired yet
if (Input.GetKeyDown(KeyCode.Escape))
{
animator.ResetTrigger("Jump");
}
}
}
Note the two-argument and four-argument overloads of SetFloat both exist on purpose. The plain one snaps the parameter to the exact value immediately, which is fine if you're already smoothing the value yourself (like Locomotion1D in section 3 does with Mathf.MoveTowards). The dampTime overload lets the Animator do that smoothing for you, which is convenient but gives you less control over exactly how the smoothing behaves.
using UnityEngine;
public class AnimatorStateReader : MonoBehaviour
{
public Animator animator;
void Update()
{
AnimatorStateInfo state = animator.GetCurrentAnimatorStateInfo(0);
if (state.IsName("Jump") && state.normalizedTime >= 1f)
{
Debug.Log("Jump clip finished playing");
}
if (animator.IsInTransition(0))
{
AnimatorTransitionInfo t = animator.GetAnimatorTransitionInfo(0);
Debug.Log("Blending, progress = " + t.normalizedTime);
}
}
}
GetCurrentAnimatorStateInfo(0) asks about layer 0 (the base layer; pass 1 for the second layer, and so on). IsName checks the state's name (or its full path, like "Base Layer.Jump", if names collide across sub-state machines). normalizedTime is the same 0-to-1-per-loop value from section 6, except it keeps counting past 1.0 for looping clips (1.2 means "20% into the second loop") instead of wrapping back to 0, which is exactly why >= 1f is the right check for "has this non-looping clip finished." Once the Jump state's clip has played through fully, the console prints once; while any transition is actively blending, it prints the blend progress every frame.
Almost every beginner hits one of these. They're worth memorizing because the Animator gives you no error message for any of them — the transition just quietly doesn't do what you expected.
// The Animator Controller has a Trigger parameter named exactly "Jump".
animator.SetTrigger("jump"); // wrong case -> silently does nothing
// Fix: match the exact, case-sensitive name shown in the Animator window
animator.SetTrigger("Jump");
Parameter names are plain strings matched by exact spelling and case. "Jump" and "jump" are two different parameters as far as the Animator is concerned. Recent versions of Unity log a console warning for a missing parameter name, but older versions do nothing at all — the trigger is just lost. A common fix is to store the name once as a const string or cache its hash with Animator.StringToHash, so a typo becomes a compile error instead of a silent bug.
Covered in section 6, but worth repeating because it's the single most common "my transition never fires (quickly)" report: a looping state with Has Exit Time checked and Exit Time near 1.0 makes the transition wait for the clip to nearly finish its current loop before it's even allowed to check its conditions. If a state is meant to be interruptible immediately (movement, most actions), uncheck Has Exit Time.
A Transition Duration of 0 makes the "transition" an instant snap rather than a blend — the same popping problem from section 1, just hidden inside the state machine instead of an explicit Play call. If a change looks like a jump cut even though you're using the state machine and not calling Play directly, check the duration on the transition arrow, not your code.
A transition condition like Speed Equals 0 can fail to ever fire, because a smoothed float (from Mathf.MoveTowards, SmoothDamp, or the dampTime overload of SetFloat) almost never lands on exactly 0.0 — it approaches it and stops a tiny fraction away. Use Less/Greater conditions with a small threshold (Speed Less 0.05) instead of Equals for any float that's being smoothed.
Calling SetTrigger on an Animator whose GameObject is currently inactive does nothing — there's no error, the call is simply a no-op. If you're queuing up an action (like "jump the instant we land") on a character that might be disabled for a frame (object pooling, a cutscene toggling visibility), check gameObject.activeInHierarchy before relying on a trigger surviving that gap.
Here's how the pieces from this chapter combine in one script driving a full Animator Controller: a 1D blend tree for locomotion, a trigger for jumping, and an aim layer.
The Animator Controller itself (built in the Animator window, not in code) would have:
Speed (Float), Grounded (Bool), Jump (Trigger)Locomotion (1D Blend Tree on Speed: Idle@0, Walk@2, Run@6), Jump (single clip)Locomotion -> Jump on the Jump trigger, Has Exit Time off, duration 0.1s. Jump -> Locomotion on Grounded == true, Has Exit Time on at 0.85, duration 0.15s.UpperBodyAim masked to spine/arms/head.using UnityEngine;
[RequireComponent(typeof(Animator))]
public class PlayerAnimatorDriver : MonoBehaviour
{
private Animator animator;
private int aimLayer;
public float speed;
public bool isGrounded = true;
void Awake()
{
animator = GetComponent<Animator>();
aimLayer = animator.GetLayerIndex("UpperBodyAim");
}
void Update()
{
// 1. Locomotion: drive the 1D blend tree
float input = Input.GetAxis("Vertical");
speed = Mathf.MoveTowards(speed, input * 6f, 10f * Time.deltaTime);
animator.SetFloat("Speed", speed);
// 2. Jump: fire a trigger, guarded by our own grounded check
if (isGrounded && Input.GetButtonDown("Jump"))
{
animator.SetTrigger("Jump");
}
animator.SetBool("Grounded", isGrounded);
// 3. Aim layer: fade in/out with the right mouse button
bool aiming = Input.GetMouseButton(1);
float targetWeight = aiming ? 1f : 0f;
float current = animator.GetLayerWeight(aimLayer);
animator.SetLayerWeight(aimLayer, Mathf.MoveTowards(current, targetWeight, 5f * Time.deltaTime));
}
}
Trace one moment: the player is standing still (speed near 0, Locomotion showing mostly Idle), holds the aim button (the UpperBodyAim layer ramps toward weight 1, so the upper body turns to aim while the legs stay idle), then presses forward and jump at nearly the same instant. speed starts ramping toward 6, sliding Locomotion's blend tree from Idle toward Walk; SetTrigger("Jump") fires the Locomotion -> Jump transition immediately, since that transition has Has Exit Time off. All three systems — the blend tree, the trigger-driven state switch, and the independent aim layer — run at the same time without stepping on each other, because each one only touches the parameters and bones it owns.
Speed.Speed with motions Idle at threshold 0, Walk at threshold 2, and Run at threshold 6. Compute the weight of every motion when Speed = 1, and again when Speed = 5. Show the interpolation fraction t you used for each.Speed = 1 falls between Idle (0) and Walk (2): t = (1 - 0) / (2 - 0) = 0.5. Idle weight = 1 - t = 0.5, Walk weight = t = 0.5, Run weight = 0 (out of range).
Speed = 5 falls between Walk (2) and Run (6): t = (5 - 2) / (6 - 2) = 0.75. Walk weight = 1 - t = 0.25, Run weight = t = 0.75, Idle weight = 0 (out of range).
Trigger parameter named exactly Attack, feeding a transition Locomotion -> AttackState. The following code runs when the player clicks, but the attack animation never plays, and the console shows no error at all. Find the bug and fix it.
void OnFire(InputValue value)
{
animator.SetTrigger("attack");
}
The parameter in the Animator Controller is named Attack (capital A), but the code calls SetTrigger("attack") with a lowercase a. Parameter names are matched as exact, case-sensitive strings, so Unity treats these as two completely different (and in this case, one nonexistent) parameters. The real Attack trigger is never set, so the transition's condition is never satisfied, and nothing plays — with no error, because passing an unrecognized name to SetTrigger is not treated as a hard failure.
void OnFire(InputValue value)
{
animator.SetTrigger("Attack"); // matches the Animator's parameter name exactly
}
A safer long-term fix is to store the name once, e.g. private static readonly int AttackHash = Animator.StringToHash("Attack");, and always call animator.SetTrigger(AttackHash) from that one constant, so a future typo becomes a single compile-time-checked spot instead of a scattered string that's easy to mistype in one of several places.
MonoBehaviour that finds the layer index of a layer named "UpperBodyAim" in Start, then each frame smoothly moves that layer's weight toward 1 while the player holds KeyCode.LeftShift, and toward 0 otherwise, so a full fade from 0 to 1 takes about 0.2 seconds. (Hint: this is the same pattern as the aim layer script in section 7, just with a different input and a slightly different fade time.)using UnityEngine;
public class ShiftLayerFader : MonoBehaviour
{
public Animator animator;
private int layerIndex;
void Start()
{
layerIndex = animator.GetLayerIndex("UpperBodyAim");
}
void Update()
{
bool held = Input.GetKey(KeyCode.LeftShift);
float targetWeight = held ? 1f : 0f;
// a full 0 -> 1 fade in 0.2s means a rate of 1 / 0.2 = 5 per second
float current = animator.GetLayerWeight(layerIndex);
animator.SetLayerWeight(layerIndex, Mathf.MoveTowards(current, targetWeight, 5f * Time.deltaTime));
}
}
The key idea is that "a full fade in X seconds" turns into a rate of 1 / X units per second for Mathf.MoveTowards's max-delta argument, multiplied by Time.deltaTime so it's frame-rate independent. Since 0.2 seconds is the target, the rate is 1 / 0.2 = 5, matching the section 7 example.
That covers blending end to end: cross-fading for one-off transitions, 1D and 2D blend trees for continuous locomotion, the state machine that decides which state is active and when, transition timing and interruption, layers and masks for independent body regions, root motion versus in-place movement, and the C# calls that drive and read all of it. The next time a character in your game needs to go from standing still to a full sprint, or aim a weapon while running, you now know exactly which Animator tool handles which half of the job.