Skip to main content

Stage 3: The Grid Board

Course progressStage 3 of 10
~45 min
Build

a clickable grid of courtyard tiles

Learn

how a 2D collider plus a mouse click lets the player pick a spot

Ship

tiles that light up when you click them

The big idea

Slimes are crawling across your courtyard. To stop them, the player needs to place towers — but a player can't place anything until the game knows where the click landed. That's what this stage builds: a grid of tiles the player can click.

A click only reaches a GameObject if that object has a collider — an invisible shape that says "I can be hit." For 2D art we use a Collider2D, usually a BoxCollider2D sized to the tile. When the mouse goes down over a collider, Unity calls a special method on that object's script named OnMouseDown. No collider means no OnMouseDown, ever — this trips up almost everyone, so we'll prove it works before we make it pretty.

We also want a tile to respond to a click by changing color. To change a tile's color from code, the script first needs a reference to its own Sprite Renderer. We get that with GetComponent, which fetches a component already on the same GameObject. And we'll track whether a tile is taken with a bool — a value that's only ever true or false.

New words
Collider2D
An invisible 2D shape on a GameObject that lets it be clicked or hit. A BoxCollider2D is a rectangle.
OnMouseDown
A method Unity automatically calls on a GameObject's script when the mouse is pressed over its collider.
GetComponent
Fetches a component that's already attached to the same GameObject, so your script can talk to it (like its Sprite Renderer).
bool
A value that is only ever true or false. We use one to remember whether a tile already has a tower.
Before you start

Finish Stage 2: Waves — you should have a spawner sending slimes across the courtyard. This stage adds the board underneath them.

Build it

Step 1 — Build one tile

A tile is a sprite the player can click, so it needs a Sprite Renderer (for the art), a BoxCollider2D (so clicks land on it), and the TileSlot script (the behavior).

Build this GameObject

Tile

2D Sprite
Open recipe
Sprite
a plain floor/courtyard tile from your sliced tilemap
Position
(-4, 2, 0)
Order in Layer
1 (above the background, below slimes)
Components to add
  • Sprite Renderer
  • BoxCollider2D
  • TileSlot.cs (added in Step 2)

Add the BoxCollider2D with Add Component → Box Collider 2D. Unity auto-sizes it to the sprite. This collider is what makes the tile clickable — without it, no click is ever registered.

Step 2 — Make the tile react to clicks

Select the Tile, Add Component → New Script, name it TileSlot, and build it in three passes. Press Play and click the tile after each pass.

Pass 1 — prove the click lands. Before any color logic, confirm Unity is even calling your method:

using UnityEngine;

public class TileSlot : MonoBehaviour
{
void OnMouseDown()
{
Debug.Log("clicked " + name);
}
}

Press Play and click the tile in the Game view. The Console prints clicked Tile. If nothing prints, the tile is missing its BoxCollider2D, or your Scene has no Main Camera (clicks are cast from the camera into the Scene). Both are required for OnMouseDown to fire. Stop.

Pass 2 — light it up. Now we change the tile's color on click. First we grab the Sprite Renderer in Start, then tint it:

using UnityEngine;

public class TileSlot : MonoBehaviour
{
public Color highlightColor = Color.yellow;
private SpriteRenderer sr;

void Start()
{
sr = GetComponent<SpriteRenderer>();
}

void OnMouseDown()
{
sr.color = highlightColor;
}
}

Press Play and click the tile. It turns yellow. GetComponent<SpriteRenderer>() reaches the renderer already on this tile and stores it in sr so we can change its color. highlightColor is public, so you can pick a different highlight in the Inspector. Stop.

Pass 3 — remember if it's taken. A real board shouldn't let you re-use a spot. We add a bool and guard against clicking a taken tile:

Script anatomy

The full script, line by line

The finished Stage 3 tile: it highlights on click, and a taken tile politely refuses instead of re-highlighting.

using UnityEngine;

public class TileSlot : MonoBehaviour
{
public Color highlightColor = Color.yellow;
private SpriteRenderer sr;
public bool occupied = false;

void Start()
{
sr = GetComponent<SpriteRenderer>();
}

void OnMouseDown()
{
if (occupied)
{
Debug.Log("This tile is taken");
return;
}

sr.color = highlightColor;
}
}
  1. Lines 5–7What each tile remembers

    highlightColor is the click color (public, so it's editable). sr is a private handle to the Sprite Renderer. occupied is the bool that tracks whether this spot already holds a tower.

  2. Lines 9–12Cache the renderer once

    GetComponent in Start runs a single time and stores the result. We avoid calling GetComponent every click, which would be wasteful.

  3. Lines 16–20The guard clause

    If occupied is true, we log a message and return early — the method stops before the highlight line. This is a guard clause: handle the bad case first, then fall through to the normal case.

  4. Line 22The normal case

    If we got past the guard, the tile is free, so we highlight it. Stage 5 is where placing a tower will flip occupied to true.

Press Play. Click a free tile — it highlights. To test the guard, set occupied to true in the Inspector before pressing Play, then click: the Console prints This tile is taken and the color stays put.

Step 3 — Lay out the grid

One tile isn't a board. We make a parent to keep the Hierarchy tidy, then duplicate the tile into a grid. A common board is 5 columns by 3 rows.

Build this GameObject

Board

Empty GameObject
Open recipe
Position
(0, 0, 0)

An empty parent. Drag your Tile onto it in the Hierarchy so all tiles nest under Board — it keeps a 15-tile grid from cluttering the list.

With the Tile selected, press Ctrl/Cmd+D to duplicate it and nudge each copy one unit over (tiles are 1 unit wide at our scale). Fill a row of 5 across, then duplicate the whole row up and down to make 3 rows. Aim for the grid to roughly line up under the lanes your slimes walk on. Every copy keeps the BoxCollider2D and TileSlot script, so every tile is clickable from the moment it exists.

Understand it

The collider is the quiet hero here. A sprite is just pixels Unity draws; it has no idea where the mouse is. The BoxCollider2D is what turns a picture into something the player can interact with, and Unity's built-in mouse handling does the hard part — converting a screen click into "which collider did this hit?" — so your script only has to answer "what happens when I get clicked?"

Caching the Sprite Renderer in Start with GetComponent is a habit worth keeping. GetComponent does a small search, so calling it once and storing the result is faster and cleaner than calling it on every click. And the guard clause in OnMouseDown — check the bad case, return, then handle the good case — keeps the method flat and readable instead of wrapping the real logic in a big if. You'll reuse that shape constantly.

Try this

Learning beat

Try this

Three short experiments. Predict before you run, then test your guess.

Predict first

Before you press Play, remove the BoxCollider2D from one tile (set the others aside). Decide: will clicking that tile still print or highlight? Run it and click. What does that tell you about colliders?

Compare

Change highlightColor on a couple of tiles to different colors in the Inspector, then click each. Compare a board where every tile highlights the same vs. tiles that hint at different uses. Which reads more clearly to a player choosing where to build?

Connect

Each tile now knows whether it's occupied. In Stage 5 the player will place a tower on a tile and flip that bool to true. Where in this script would the "place a tower here" code naturally go?

Test your stage

  • In Pass 1, clicking a tile printed clicked Tile in the Console.
  • After Pass 2, clicking a tile turns it your highlight color.
  • A tile with occupied set to true prints This tile is taken and does not change color.
  • You have a grid (about 5×3) of tiles nested under a Board parent, and every tile responds to clicks.
  • Design check. Click a few tiles and look at the board. Is it obvious which tiles you've picked and which are still open? A player should be able to read the board at a glance.

If it breaks

  • Clicking a tile does nothing — no Console message at all. This is the number-one issue: the tile is missing its BoxCollider2D. OnMouseDown only fires on a GameObject that has a collider. Add a Box Collider 2D and try again.
  • Still nothing, even with a collider. Check that your Scene has a Main Camera and that it's tagged MainCamera. Mouse clicks are cast from the Main Camera, so without one, no click reaches any collider.
  • The Console prints but the color won't change. You're likely missing the Start method or the GetComponent line, so sr is empty. Confirm sr = GetComponent<SpriteRenderer>(); runs in Start.
  • Every tile highlights when I click just one. You probably wrote a shared/static field by accident, or your tiles aren't separate GameObjects. Each tile should be its own object with its own TileSlot — duplicating with Ctrl/Cmd+D does this correctly.
  • A red error about "NullReferenceException" on click. sr was never assigned because the tile has no Sprite Renderer, or Start didn't run. Make sure the tile is a 2D Sprite with a Sprite Renderer and the script is attached.