◀ 4.55.15.2 ▶

5.1 — The Axe: Item Pickups & Inventory Bools

Coins and potions changed numbers. The axe changes what you are: it flips a boolean — that half you have written three times already — and then it replaces the entire machine that draws your character. That second half is one line long, and nothing you have built so far prepares you for it.

In this lesson you will be able to answer“how do I add an item to my inventory?”Guided build
Break It Down~10 minGuided build

The player picks up an axe and becomes an axe-wielder — "how do I add an item to my inventory?" Every pickup so far changed a number. This one changes the machine that draws the player.

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

  1. Give the axe a shape the engine can report onA collider on the AxeItem prefab root with Is Trigger ticked — the coin's recipe from 2.1. Today the prefab has no collider at all, which makes it a picture you can walk straight through.Read: the Collider2D and Is Trigger rows in the quick reference, and the coin you gave a shape to in 2.1
  2. Put one where a player can reach itAn instance of the prefab standing in the Overworld. There are zero in any scene, so the mechanic is invisible until you place one.Read: the prefab-versus-instance distinction from 2.1, and the clone you spawned in 2.4
  3. Confirm the thing you touched was the axeelse if (other.CompareTag("Axe")) inside the OnTriggerEnter2D(Collider2D other) you already have — a third branch of an idea you have written twice.Read: the tag check you wrote in 2.1, and the other.CompareTag("Coin") row in the quick reference for the exact spelling
  4. Remember that you have itOne field in the class body — a bool that starts false and is set to true inside the branch. One item, one yes/no.Read: the coin count field you declared in the class body in 2.1, and the bool hasAxe = false; row in the quick reference for the type
  5. write this partRead: the Animator component page in the Unity scripting reference — the property whose type is RuntimeAnimatorController — and the unused axe controller already sitting in Assets/Artwork/Animations/Top_Down/PlayerAnimator/Axe/
  6. Consume the pickupDestroy(other.gameObject) — the axe leaves the world the moment you touch it, exactly like the coin. The object is consumed, the capability is not.Read: the Destroy(Object) row in the quick reference, and the destroy line in your own coin branch

Only one part of that list has no precedent in this course, and it is the reason this lesson exists. Every pickup so far changed a value — a coin count, health — and a bool is that same shape in different clothes: a value that can only hold 0 or 1 is still a value. The axe changes what the player is, and that is not done by changing a value at all. It is done by replacing the machine that decides which animation plays — which is why "remember the axe" and "become the axe-wielder" are separate parts.

The test: could you look this part up on its own, right now, in one search? "Pick up an axe" cannot be looked up. "Change the controller an Animator is playing" can — and the search term is a property name you have to read rather than guess. That is why the gap above names where to look and not what to type.

Your turn

The axe flips a boolean that nothing in your game reads yet. Name one thing that will have to read it, and say what you expect that line of code to look like when something finally does.

In the Editor ~20 min
  1. Project window → Assets/Prefabs/AxeItem: double-click it to open the prefab asset itself, not a copy standing in a scene, and read the Inspector. A Transform, a Sprite Renderer, and the tag Axe already set. Now notice what every other pickup in this game has and this one does not.
  2. Inspector → Add Component → Box Collider 2D, on the prefab's root — the object carrying the tag. Then Scene view → drag the collider's handles until the green outline covers the axe you can actually see. The sprite is wide and short, so a Box fits it more easily than the coin's Circle did in 2.1.
  3. Inspector → Box Collider 2D → Is Trigger ☑ ON. A trigger reports an overlap and pushes nothing; leave it off and the axe is a small solid rock that shoves the player aside, and the callback you are about to write never runs at all.
  4. Project window → drag one AxeItem into the Overworld in the Hierarchy, then Scene view → place it on a route a tester will walk without dying first. The prefab ships with zero instances in any scene, so nothing is a pickup until you put one there — and the path toward a cave mouth is the sensible spot, because 5.2 is where that door starts caring.
  5. Hierarchy → Player → Inspector → Animator → Controller: it is playing Player_Shovel.controller — the state machine 1.6 taught you to drive. Then Project window → Assets/Artwork/Animations/Top_Down/PlayerAnimator/: two complete controllers, an axe and a shovel, and only one of them referenced by anything in this project.
  6. Once the walkthrough is done — Project window → Assets/Prefabs/Player, Inspector → Player Controller → Axe Controller, and drag Axe/Player_Axe.controller into the slot. Wire it on the prefab, not on one Player in a scene, so every Player you place inherits it.
  7. Game view → press Play, walk into the axe, and swing. The character should be an axe-wielder the moment you touch it. Then walk through a cave mouth and come back, and check that they still are — SceneSwitch.cs is what puts the controller back.
In the Code ~30 min
APIWhat it gives youWhat it costs
[SerializeField] private bool hasAxe = false; A class-body field that survives every frame, shows up in the Inspector so you can watch it flip, and holds exactly two answers — the declaration form from 1.3 that has held your coin count since 2.1. There is nothing in between the two answers, and the starting value is a design decision rather than a formality: false makes the player earn the capability.
other.CompareTag("Axe") Asks the engine whether the collider you just touched carries that tag. The AxeItem prefab has carried Axe since the day you opened it, so there is nothing to create. Exact spelling against a tag that has to exist in the project, and it is asked of whichever object owns the collider — move it to a child and the answer is false, with nothing in the Console to explain it.
animator.SetBool("IsWalking", bool)
animator.SetInteger("WalkDir", int)
animator.SetTrigger("Attack")
The three calls from 1.6 that make a controller play something. They choose between the states of one controller, so none of them can reach a clip that is not already in that machine — which is why a new weapon set is not a parameter, and never can be.
the Animator's own controller property The property that holds which controller the Animator is playing right now. Read it and you know which weapon set is live; assign to it and the whole state machine — states, transitions, clips — is replaced in one line. Its type is RuntimeAnimatorController, the same type as the Inspector slot. Find its name on the Animator component page of the scripting reference rather than guessing it — and do that before you write the line the walkthrough leaves you. It replaces everything, and it saves nothing: a Player rebuilt from the prefab comes back with the controller the prefab was wired with.
Architectural specification for PlayerController.cs:
  • The pickup. You gave a collider to an object that has never had one, so you cannot see what the engine sees. How did you check the shape in the Scene view rather than guessing — and what does the player feel if the box is twice the size of the art?
  • The placement. Where did you put your axe, and how would a tester find it without being told?
  • The scope. Where does the field live, and what breaks if you move it inside the branch that sets it? Use the word "frame" in your answer.
  • The swap. Name the property you assigned to, its type, and the page you read it on rather than guessing it.
  • The slot. What did the Inspector slot have to hold before the swap could work at all — and what does the player see, and hear, if it holds nothing?
  • The animation code. The three parameters from 1.6 exist on both controllers with the same names and types. How much of your 1.6 code had to change when the axe arrived — and what does that tell you about what a controller owns?

Socratic: Right-sizing the data

  1. Why is a bool the right abstraction for "I have an axe" but the wrong one for "how many coins do I have"?
  2. At roughly how many items does a collection of booleans become the wrong design? What breaks first — readability, or the number of branches you have to write?
  3. Where should this boolean live — on the axe, on the player, or somewhere global? Use the reasoning you applied in 2.1, then check it against what you actually do below.
In the CodeGuided build

The question this answers“how do I add an item to my inventory?”

You are editing Assets/Scripts/PlayerController.cs. Two new fields and one new branch inside the pickup callback you already have. The shape is the coin from 2.1 with a different tag; the last line of step 2 is the part nothing before it prepares you for.

This build leaves 1 line for you to write. Everything around it is given, so you always know the shape of what you are producing.

Step 1Declare the state, and the controller you will become

Two lines in the class body, next to your coin count and your health. One of them is what this lesson is about; the other holds an asset that is not wired to anything in the project yet.

// PlayerController.cs — class body, above Start(), with your other fields.
    [SerializeField] private bool hasAxe = false;

    [SerializeField] private RuntimeAnimatorController axeController;
  1. 2The capability. A bool holds true or false and nothing else, which is the honest shape of "I have an axe": one item, one yes/no.Get the starting value the right way round. false means you do not have the axe until you find it — and because your character already looks like an axe-wielder, starting it at true makes the pickup appear to do nothing at all.
  2. 4A slot in the Inspector for the controller asset that draws the axe. Leave it empty for now — the editor block above wires it from the Project window.A slot rather than a typed-in path. The controller is an asset sitting in Assets/Artwork/Animations/Top_Down/PlayerAnimator/Axe/, and a field wired in the Inspector is a reference to that asset: rename it, move it, and the reference survives. A string path typed into your code would break the moment anybody tidied the folder.

Step 2Add the axe to the callback that already handles pickups

One more branch inside the method that has been handling every pickup since 2.1. The coin check stays exactly where it is; the axe is one more question asked of the same event.

// PlayerController.cs — below Update(), the pickup callback you already have.
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Coin"))
        {
            coins = coins + 1;
            Destroy(other.gameObject);
        }
        else if (other.CompareTag("Axe"))
        {
            hasAxe = true;
            animator.____ = ____;
            Destroy(other.gameObject);
        }
    }
  1. 9The new question. The prefab has carried the tag Axe since the day you opened it, so there is nothing to create and nothing to spell-check — but it is still other.CompareTag("Axe") and not ==, for the reason from 2.1.else if rather than a second if, so each object is asked exactly one question. No collider is a coin and an axe at once, so it makes no difference to the game — it says what you mean.
  2. 11Sets the capability. No type in front of it: the field already exists in the class body, so this assigns to it — the declare-versus-assign rule from 1.3.
  3. Your turn12The last line of the pickup, and the whole of "becoming an axe-wielder". The line above says that you have the axe; this one puts it in your hands by handing the Animator a different controller to play. Keep the receiver exactly as written — animator is the field you cached in 1.6. The right-hand side is the slot you declared in step 1.You want the property on animator that holds which controller it is playing right now — the same type as the slot you declared, so both sides of this assignment are the same kind of thing. The Animator component page in the Unity scripting reference lists it among its properties.Nothing else in this file changes when you pick up the axe: your movement, your input, your health and the coin branch above are all what they were. That is the surprise — the player does not transition into being an axe-wielder, the machine that draws the player is replaced. And because it is replaced rather than saved, it is gone the moment a fresh Player is built from the prefab — which is why SceneSwitch.cs saves the controller before it loads a scene and puts it back in Start().
  4. 13The axe leaves the world the moment you pick it up — the same line as the coin and the potion. The object is consumed, the capability is not.
Check your work: the finished filetry every gap first — then open this to compare

Compare against your own file rather than copying it. Two differences are worth naming, because neither is a mistake. The shape: this file returns early out of each branch and the walkthrough chains else if — both ask every object exactly one question. The helper: here the swap lives in a method called PickUpAxe(), and the walkthrough writes the same two lines inline, so that "becoming an axe-wielder" has a name you can point at. What is NOT a difference is the gap itself: whichever way you wrote it, the line that changes the player is the assignment to the Animator's controller property, and that name is the one you had to go and read.

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

// Your controller at the end of 5.1: movement and health from 2.2, animation
// from 1.6, the coin pickup from 2.1 — and the axe.
public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 5f;
    [SerializeField] private int health = 3;
    [SerializeField] private int coins;

    // The capability, and the controller that draws it. The starting value is
    // the design: false, until the player earns it.
    [SerializeField] private bool hasAxe = false;
    [SerializeField] private RuntimeAnimatorController axeController;

    private Rigidbody2D body;
    private Animator animator;
    private SpriteRenderer spriteRenderer;

    private float inputX;
    private float inputY;

    void Start()
    {
        // Looked up once, never inside Update() — the rules from 1.6 and 2.2.
        body = GetComponent<Rigidbody2D>();
        animator = GetComponentInChildren<Animator>();
        spriteRenderer = GetComponentInChildren<SpriteRenderer>();
    }

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

        Vector2 direction = new Vector2(inputX, inputY);

        // No Time.deltaTime: velocity is per second, not per frame.
        body.linearVelocity = direction * moveSpeed;

        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);
            spriteRenderer.flipX = inputX < 0f;
        }
        else if (inputY > 0f)
        {
            animator.SetInteger("WalkDir", 0);
        }
        else
        {
            animator.SetInteger("WalkDir", 2);
        }
    }

    void CheckAttack()
    {
        if (Input.GetMouseButtonDown(0))
        {
            animator.SetTrigger("Attack");
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Spikes"))
        {
            TakeDamage(1);
        }
    }

    // Every pickup in the game arrives here. The axe is one more question.
    // Your potion branch from 2.3 lives in this same callback, under its own
    // tag — it is left out here only because the tag is the one you invented.
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Coin"))
        {
            coins = coins + 1;
            Destroy(other.gameObject);
            return;
        }

        if (other.CompareTag("Axe"))
        {
            PickUpAxe();
            Destroy(other.gameObject);
        }
    }

    // The whole of "becoming an axe-wielder", and the only line in the game
    // that changes what the player IS. Setting a parameter picks a clip inside
    // one controller; this replaces the whole controller — every state, every
    // transition, every clip, in one assignment.
    private void PickUpAxe()
    {
        hasAxe = true;
        animator.runtimeAnimatorController = axeController;
    }

    private void TakeDamage(int amount)
    {
        if (health <= 0)
        {
            return;
        }

        health = health - amount;
        Debug.Log("Health: " + health);

        if (health <= 0)
        {
            Debug.Log("Player died.");
        }
    }
}

// The Inspector half of this lesson is the editor block at the top of the page:
// a trigger collider on Assets/Prefabs/AxeItem, one axe instance standing in the
// Overworld, and the Axe Controller slot on Assets/Prefabs/Player.

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

Reference Docs

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

Next: 5.2 — The Door: Opening It with Events, where that permission finally gets spent.

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