9.2 Blend Trees & State Machines

Phase 9 · Animation · Study time: 20–35 h

Blending smoothly between animations (walk to run) and driving transitions with animation state machines.

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#.

1. Why You Rarely Play Just One Clip

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.

Frame N (Idle, standing still): character upright, arms relaxed Frame N+1 (Animator.Play("Run") called): character SNAPS to Run frame 0, mid-stride, one leg forward No frames in between. The pose jumps instantly. This sudden jump is usually called "popping."

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.

2. Cross-Fading Between Two Clips

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:

Blend of Walk -> Run over 0.25 seconds (CrossFade) time (s) 0.00 0.05 0.10 0.15 0.20 0.25 Walk weight 1.00 0.80 0.60 0.40 0.20 0.00 Run weight 0.00 0.20 0.40 0.60 0.80 1.00 t=0.00 Walk [##########] 1.0 Run [..........] 0.0 t=0.10 Walk [######....] 0.6 Run [####......] 0.4 t=0.25 Walk [..........] 0.0 Run [##########] 1.0 final_pose = Walk_pose * Walk_weight + Run_pose * Run_weight

"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).

Tip You can call 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.

3. 1D Blend Trees: From Idle to Walk to Run

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:

At 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.

1D Blend Tree, parameter = Speed Speed: 0.0 2.0 6.0 | | | Motion: Idle (blend) Walk (blend) Run Example: Speed = 3.5, which sits between Walk(2.0) and Run(6.0) t = (3.5 - 2.0) / (6.0 - 2.0) = 0.375 Walk weight = 1 - t = 0.625 Run weight = t = 0.375 Idle weight = 0.000 (out of range for this segment, ignored)

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.

Common mistake Setting 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.

4. 2D Blend Trees: Blending by Movement Direction

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:

MoveZ (forward, +1) ^ | RunForward | MoveX -1 RunLeft ----- Idle(0,0) ----- RunRight MoveX +1 | RunBack | v MoveZ (backward, -1) If diagonal clips are authored too, e.g. RunForwardRight at (0.7, 0.7), Unity finds the motions closest to the current (MoveX, MoveZ) point and blends between them, weighting each by how close it is to that 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.

Tip Keep 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.

5. The Animator State Machine: States, Transitions, and Parameters

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.

State Machine (Animator window) [Entry] --> [Locomotion] (a 1D Blend Tree driven by "Speed") [Locomotion] --Jump (trigger)--> [Jump] [Jump] --Grounded == true, Exit Time 0.9--> [Locomotion] [Any State] --Health <= 0--> [Death]

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.

6. Transition Duration and Interruption

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:

These two settings interact in a way that trips up almost everyone the first time:

State "Attack" is a looping animation, length normalized 0..1 Transition Attack -> Locomotion has Has Exit Time = true, Exit Time = 1.0 Player releases the attack button at normalizedTime = 0.1 the transition's condition becomes true right away, but Unity WAITS it only fires once normalizedTime reaches 1.0 -> almost a full extra loop feels like the input "did nothing" for up to 0.9 of the clip's length

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.

Interrupting a Transition Mid-Blend

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).

Transition A -> B starts at t=0.00, duration 0.30s t=0.00 A weight=1.00 B weight=0.00 t=0.10 A weight=0.67 B weight=0.33 new condition true: target becomes C t=0.10 A weight=0.67 C weight=0.33 B is dropped, C continues from here t=0.30 A weight=0.00 C weight=1.00

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.

7. Layers and Avatar Masks: Aiming While Running

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:

Layer 1: Base weight 1.0 mask: full body plays Locomotion state machine -> legs walk/run Layer 2: UpperBodyAim weight 0..1 mask: spine + arms + head only plays Aim clip -> only affects the masked bones Final pose = Base layer pose, then UpperBodyAim's pose overrides just the masked bones on top, scaled by the layer's weight

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.

Common mistake Forgetting to assign an Avatar Mask to an additive/override layer at all. With no mask, the layer's 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.

8. Root Motion vs In-Place Animation

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.

In-place clip (no root motion): the clip only moves bones relative to the character's origin. transform.position never changes from playing the clip alone. if your code doesn't move the object, the character runs in place and its feet visibly slide against the ground. Root motion clip: the clip's hip/root bone itself travels forward as part of the animation data. Unity exposes that per-frame travel as animator.deltaPosition / animator.deltaRotation, and (if enabled) applies it to transform.position/rotation automatically each frame.

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.

9. Driving and Reading the Animator from C#

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.

Writing Parameters

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.

Reading State Back

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.

10. The Common Bug: Transitions That Never Fire, or That Snap

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.

A Typo in the Parameter Name

// 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.

Has Exit Time Left On By Accident

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.

Zero Transition Duration

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.

Comparing Floats for Exact Equality

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.

A Trigger Set on a Disabled Object

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.

11. Putting It Together: A Full Locomotion Controller

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:

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.

12. Glossary

13. Exercises

Exercise 1 A 1D Blend Tree uses parameter 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.
Show answer

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).

Exercise 2 An Animator Controller has a 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");
}
Show answer

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.

Exercise 3 Write a 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.)
Show answer
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.

← Back to all chapters