Stage 3: The Grid Board
a clickable grid of courtyard tiles
how a 2D collider plus a mouse click lets the player pick a spot
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.
- 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.
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 GameObjectTile
2D SpriteOpen recipe
Tile
2D Sprite- Sprite
- a plain floor/courtyard tile from your sliced tilemap
- Position
- (-4, 2, 0)
- Order in Layer
- 1 (above the background, below slimes)
- 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:
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;
}
}
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.
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.
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.
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 GameObjectBoard
Empty GameObjectOpen recipe
Board
Empty GameObject- 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
Try this
Three short experiments. Predict before you run, then test your guess.
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?
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?
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 Tilein the Console. - After Pass 2, clicking a tile turns it your highlight color.
- A tile with
occupiedset totrueprintsThis tile is takenand does not change color. - You have a grid (about 5×3) of tiles nested under a
Boardparent, 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.OnMouseDownonly 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
Startmethod or theGetComponentline, sosris empty. Confirmsr = GetComponent<SpriteRenderer>();runs inStart. - 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.
srwas never assigned because the tile has no Sprite Renderer, orStartdidn't run. Make sure the tile is a 2D Sprite with a Sprite Renderer and the script is attached.