◀ 1.51.62.1 ▶

1.6 — Animation States: IsWalking, WalkDir, Attack

Your character moves, but it does not look like it: it slides around in the same idle pose whichever way you push. A script never plays an animation itself — it asks the Animator by setting parameters, and the Animator picks the clip.

In this lesson you will be able to answer“how do I play a walking animation?”Full walkthrough
Break It Down~10 minFull walkthrough

Play a walking animation for a character that already moves in four directions, with a different clip for each one and a picture that faces the way it walks.

First, before any code: cut that into parts small enough that you could look each one up on its own.

  1. Get a handle on the Animator and the Sprite RendererTwo fields, filled once, before the first frame. Both components live on the child Sprite, so only GetComponentInChildren<T>() finds them.Read: the Animator and flipX rows in the quick reference.
  2. Work out whether the player is moving at allOne true/false value built from the two input fields 1.4 gave you: inputX != 0f || inputY != 0f. The controller turns IsWalking into idle or walking; you never name a clip.Read: the SetBool("IsWalking", b) row in the quick reference, and the Parameters tab.
  3. Work out which direction the player is facingCompare the inputs by size rather than sign — the Mathf.Abs(float f) row — because a hard left push is -1 and would lose to a gentle nudge upward. The winner sets WalkDir: 0 up, 1 sideways, 2 down.Read: the row of math helpers in the quick reference.
  4. Turn the picture around to match the directionThe sideways clip is drawn facing right, so the sprite is mirrored rather than redrawn: flipX true for a left push, false for a right one. The up and down clips face the camera.Read: the flipX row in the quick reference.

One part finds the machine, one asks a yes/no question, one asks a which-one question, one turns the picture around. Animation looks like a whole new subject; it is four decisions.

Does the part name an API you could search for? "Play the walking animation" cannot be looked up; SetBool("IsWalking", b), SetInteger("WalkDir", 1) and flipX can.

Your turn

The walkthrough fires the attack with SetTrigger(string name), and a swing is not one of the four parts. Why not — and what is different about an action that ends by itself?

Two methods are written in the walkthrough and neither runs until something calls it. Where do those calls belong, and why would calling them before the input is read animate the wrong frame?

In the Editor~10 min
  1. Hierarchy → select the child Sprite under Player — from 1.2, that is where the Animator lives, not on the Player.
  2. Window → Animation → Animator. Click the Parameters tab, top-left. Three parameters are configured: IsWalking (Bool), WalkDir (Int) and Attack (Trigger). Write down their exact spellings, capitals included.
  3. Animator → click the arrows between the up, side and down states and read their conditions in the Inspector: they compare WalkDir to 0, 1 and 2. Which number means which direction?
  4. Animator → click the arrow into any of the Shovel_Swing states. What two conditions must both be true before the attack plays?
  5. Inspector → with the Sprite selected, read the Animator component: which controller asset is assigned, and which kind of parameter — Bool, Int, Float or Trigger — is missing from it? Then Game view → press Play and click the mouse to swing.
In the Code~20 min

The calls that do the talking between your script and the controller:

Unity APISignatureBehaviour
animator.SetBool()void SetBool(string name, bool value)Sets a boolean parameter, for persistent states — idle versus walking, where true and false are both meaningful.
animator.SetInteger()void SetInteger(string name, int value)Sets a whole-number parameter, for discrete choices such as a direction (0, 1, 2), where each value is a different clip.
animator.SetFloat()void SetFloat(string name, float value)Sets a decimal parameter, for smooth values such as speed. The fourth member of the family — this controller has no Float parameter, so you will not call it today.
animator.SetTrigger()void SetTrigger(string name)Fires a momentary trigger, which resets itself once a transition consumes it, for one-shot actions such as attacks. No value is passed.
spriteRenderer.flipXbool (read/write)Mirrors the sprite horizontally, letting one sideways walk clip serve both left and right.
Mathf.Abs()float Mathf.Abs(float f)Returns a number's absolute value, stripping the sign — for comparing size regardless of direction.
GetComponentInChildren<T>()T GetComponentInChildren<T>()Finds a component on this GameObject or on any of its children — where the Sprite Renderer and the Animator turned out to live.

Socratic: Separation of Concerns

  1. Why does your script set abstract parameters like "IsWalking" rather than telling the engine "play frame 4 of the walk sprite sheet"?
  2. If an artist replaced a 4-frame walk animation with an 8-frame one, how much C# would change? What does your answer say about this split?
  3. What is the difference between animator.SetBool(string name, bool value) and animator.SetTrigger(string name)? Which resets itself, and why does that matter for an attack?
In the CodeFull walkthrough

The question this answers“how do I play a walking animation?”

You are editing Assets/Scripts/PlayerController.cs. The animation half of the script you already have — add it to the file from 1.5.

Step 1Declare the two components at class scope

A script can only talk to a component it holds a reference to.

// PlayerController.cs — class body, below the speed knob and the two input fields.
    private Animator animator;
    private SpriteRenderer spriteRenderer;
  1. 2Will hold the Animator. No value yet — Start() fills it in.
  2. 3The same for the picture, needed for one line later: flipX.Both fields start as null, and every NullReferenceException here comes from a field declared but never assigned.

Step 2Find them once, in Start()

Add this below the fields; your file has no Start() yet.

// PlayerController.cs — Start() does not exist in your file yet. Add it below the fields.
    void Start()
    {
        animator = GetComponentInChildren<Animator>();
        spriteRenderer = GetComponentInChildren<SpriteRenderer>();
    }
  1. 2Runs once, before the first frame — the right place to look a component up.
  2. 4Searches the Player and every object beneath it for an Animator.The line that catches everyone. The Animator sits on the child Sprite, not the Player: GetComponent<T>() looks only at the Player, finds nothing, and hands you null.
  3. 5The same shape, for the same reason: the picture is on the child Sprite too.

Step 3Write UpdateAnimation() and set the walking flag

Your own method, called once per frame. One question first: is the player moving at all?

// PlayerController.cs — below Update(), a method of your own.
    void UpdateAnimation()
    {
        bool isMoving = inputX != 0f || inputY != 0f;
        animator.SetBool("IsWalking", isMoving);
  1. 2A method you write. Nothing calls it automatically — step 7 adds the call.Animation in its own method leaves Update() as three ideas: read the input, move, update the visuals.
  2. 4A bool from 1.4's two fields: horizontal is not zero or vertical is not zero.
  3. 5Hands the Animator a named parameter and a true/false value; the controller picks the clip. You never name a clip in code.The name must match the Animator window character for character: "IsWalking", capital I, capital W.

Step 4Choose the direction by size

Three directional states, one integer parameter: WalkDir 0 up, 1 sideways, 2 down. Diagonals are the interesting case.

        if (Mathf.Abs(inputX) >= Mathf.Abs(inputY))
        {
            animator.SetInteger("WalkDir", 1);
        }
        else if (inputY > 0f)
        {
            animator.SetInteger("WalkDir", 0);
        }
        else
        {
            animator.SetInteger("WalkDir", 2);
        }
  1. 1Compares the sizes: Mathf.Abs(float f) throws the sign away, so -1 counts as 1. If horizontal wins, the sideways clip plays.
  2. 3SetInteger(string name, int value) sets an Int parameter; 1 means "walking sideways".
  3. 5Only reached when horizontal lost. > 0f asks whether vertical is positive; up is +1.
  4. 70 means walking up in this controller.
  5. 9Everything left over: not sideways and not up, so down.
  6. 112 means walking down — the values the transition arrows compare against.
  7. 12Closes the downward branch and the whole chain.Standing still: both inputs are 0, so WalkDir ends up as 1 anyway — harmless, because the Idle state never looks at it.

Step 5Mirror the sprite for left and right

The sideways clip is drawn facing right, so Unity mirrors the picture instead of playing a second clip — the Sprite Renderer's job.

            if (inputX < 0f)
            {
                spriteRenderer.flipX = true;
            }
            else
            {
                spriteRenderer.flipX = false;
            }
  1. 1The new line, inside the sideways branch: is the player pushing left? inputX goes negative on the left key.
  2. 3true mirrors the picture left-to-right, so the character faces left.
  3. 5Everything the question did not catch: pushing right.
  4. 7false leaves the clip as drawn — facing right.

Step 6Fire the attack with a trigger

An attack is a one-shot, not a state the player stays in; the Trigger parameter clears itself once a transition consumes it.

// PlayerController.cs — a second method of your own, below UpdateAnimation().
    void CheckAttack()
    {
        if (Input.GetMouseButtonDown(0))
        {
            animator.SetTrigger("Attack");
        }
    }
  1. 2Attacking is not walking, so it gets its own method — and nothing calls it yet either.
  2. 4The Input class from 1.4, asking about the mouse. Button 0 is left, and this is true on the single frame it goes down.Without the Down, Input.GetMouseButton(0) is true every frame the button is held — the swing would restart sixty times a second.
  3. 6Fires the trigger. It takes only a name: a trigger has no state to set.

Step 7Call both helpers from Update()

Both methods are written and neither one runs yet: a method of your own does nothing until something calls it.

// PlayerController.cs — inside Update(), after transform.Translate(movement);
        UpdateAnimation();
        CheckAttack();
  1. 2Runs the animation logic every frame. The parentheses are empty because inputX and inputY are fields, already there to read.Where it goes matters. The flags are built from those fields, so a call placed above the input read animates last frame's input.
  2. 3The same, inside Update() — Start() runs once, and the player can click at any moment.
Check your work: the finished filebuild it as you read — then open this to check every line

Compare it against your own file rather than pasting it in: both components assigned in Start() with GetComponentInChildren<T>(), both helpers actually called from Update(), and the three parameter names spelled as they are in the Animator window.

// ------------------------------------------------------------------
//  REFERENCE FILE — Assets/Scripts/PlayerController.cs
//  Written to be read, not pasted. Build it with the walkthrough
//  above, one step at a time, then open this and compare.
//  If it is sitting in your project unchanged, that is the thing
//  to go and do now — the walkthrough is still above you.
// ------------------------------------------------------------------
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;

    private float inputX;
    private float inputY;

    private Animator animator;
    private SpriteRenderer spriteRenderer;

    void Start()
    {
        animator = GetComponentInChildren<Animator>();
        spriteRenderer = GetComponentInChildren<SpriteRenderer>();
    }

    void Update()
    {
        inputX = Input.GetAxis("Horizontal");
        inputY = Input.GetAxis("Vertical");

        Vector2 direction = new Vector2(inputX, inputY);
        direction = direction.normalized;

        Vector2 movement = direction * moveSpeed * Time.deltaTime;

        transform.Translate(movement);

        UpdateAnimation();
        CheckAttack();
    }

    void UpdateAnimation()
    {
        bool isMoving = inputX != 0f || inputY != 0f;
        animator.SetBool("IsWalking", isMoving);

        if (Mathf.Abs(inputX) >= Mathf.Abs(inputY))
        {
            animator.SetInteger("WalkDir", 1);

            if (inputX < 0f)
            {
                spriteRenderer.flipX = true;
            }
            else
            {
                spriteRenderer.flipX = false;
            }
        }
        else if (inputY > 0f)
        {
            animator.SetInteger("WalkDir", 0);
        }
        else
        {
            animator.SetInteger("WalkDir", 2);
        }
    }

    void CheckAttack()
    {
        if (Input.GetMouseButtonDown(0))
        {
            animator.SetTrigger("Attack");
        }
    }
}
Bug Hunt~25 min

Walk in all four directions and click to swing. Once that works, break one piece at a time.

Bug Hunt: Diagnosing Animation Faults

  1. Misspell "IsWalking" as "isWalking" (lowercase i). What does the Console report? Why is a misspelled parameter a warning rather than a crash?
  2. Delete the UpdateAnimation(); call from Update() and press Play. No errors appear and the character still moves — so what does it look like, and what does that prove?
  3. Comment out both spriteRenderer.flipX lines. Walk left: which way does the sprite face, and can any input make it face the other way?
  4. Change GetComponentInChildren<Animator>() to GetComponent<Animator>(). What error appears, and which line does Unity point at — the one in Start() or the one in UpdateAnimation()?

Checkpoint — before moving on, you must be able to:

Reference Docs

Need a deeper explanation? These pages cover the same concepts:

Next: 2.1 — Coins: Triggers, Tags & Your First Stat — Unit 2 begins, and your character finally interacts with the world.

0/9 answered

Answers save on this device as you type. At the end of class, click Download and upload the file to the Canvas assignment.

Saved on this device only — the Dashboard tracks your progress.