Learning how to make a puzzle game in Godot starts with an empty editor and a grid sketched on paper. Store the board as data first, draw it with a TileMapLayer second, then add input and match logic on top.
Quick answer: Create a Godot project with a board scene, keep every piece in a dictionary keyed by cell, and treat the tiles on screen as a picture of that data. Convert clicks and taps to cell coordinates, find runs of matching pieces, clear them, refill, and repeat until the board is stable. Export once the loop works.
This walkthrough builds a small Match-3 style board as part of our wider guide to indie puzzle game development. The same data structure also suits sliding-block and merge puzzles.
Setting up the project and scenes
Godot Engine is a cross-platform 2D and 3D game engine with a unified interface, and its official editor binaries and export templates are published on the Godot website, as the engine repository README explains. Download the editor for your operating system and create a new Godot project. Install the export templates now only if you want to test on a device soon; they are not needed to build the board.

Godot builds everything from nodes grouped into scenes, so give the game a few small scenes rather than one big one:
- Level scene: a Node2D root, which matches the approach in a Godot forum thread on puzzle setup where a level is simply a new scene with a Node2D at its root.
- Board script: attach a game script to that root. The same thread suggests a script that places things into the scene, which is where your grid data will live.
- Board display: add a TileMapLayer as a child of the root. This is the Godot 4 node for drawing a grid of tiles.
- Interface: build menus and score panels as a separate UI scene. The forum example uses a split container anchored to full rect for this.
If you are weighing another engine, how to make a puzzle game in unity covers the same task there. Confirm node and class names against the class reference, which is also accessible from the Godot editor.
Planning the mechanic on paper
Decide on the grid, the piece types and one legal move before you write code. A matching rule that fits in a sentence is easier to implement than a vague idea.

- Draw the grid and mark a single swap or slide.
- Write down what counts as a match and what happens to the cells it empties.
- List what the player can select, because that decides your input code.

If you are unsure which rule is fun, test it cheaply first. Our guide on how to prototype a puzzle game covers that stage.
Storing the board as data
The board state belongs in a script, not in the tiles. A dictionary keyed by Vector2i cell, with the piece kind as the value, makes empty cells easy to represent because a missing key means an empty cell. It also avoids the index arithmetic that nested arrays need at the edges.

Filling the grid
Attach this to the level root. The sizes and tile-source values are exported variables, so you set them in the Inspector.
extends Node2D
@export var grid_size: Vector2i
@export var kind_count: int
@export var min_run: int
@export var tile_source: int
@export var atlas_row: int
@onready var layer: TileMapLayer = $TileMapLayer
var cells: Dictionary = {}
func fill_board():
for y in grid_size.y:
for x in grid_size.x:
var pos := Vector2i(x, y)
cells[pos] = randi() % kind_count
layer.set_cell(pos, tile_source, Vector2i(cells[pos], atlas_row))
Random filling can create matches before the player moves. After filling, run the match check below and re-roll any cell it flags until the board starts clean.
Drawing with TileMapLayer
Each kind maps to one tile in your tileset, and every change to cells is followed by a matching change on the layer. Never read gameplay state back from the layer. A paid course on Godot puzzle grids teaches the same split, with separate tile-scene and grid-setup lessons before any game rules.
Reading mouse and touch input
Mouse clicks and screen touches arrive as different event classes, InputEventMouseButton and InputEventScreenTouch. Handle both in one function that converts the event position to a cell, so a desktop build and a phone build share all later logic.
signal cell_picked(cell)
func _unhandled_input(event):
if not (event is InputEventMouseButton or event is InputEventScreenTouch):
return
if not event.pressed:
return
var cell: Vector2i = layer.local_to_map(make_input_local(event).position)
if cells.has(cell):
cell_picked.emit(cell)
This assumes the TileMapLayer sits at the root node’s origin. If you offset it, convert through the layer’s own transform instead. Emitting a signal keeps the input code ignorant of the rules. The forum example works the same way: a button script triggers a signal on the game script, and the game script decides what happens.
Finding and clearing matches
Detecting runs
A match is a run of identical pieces at least as long as min_run. Scan from each cell to the right and downward, then collect every cell in a qualifying run.
func run_from(start: Vector2i, step: Vector2i):
var run: Array = [start]
var next := start + step
while cells.has(next) and cells[next] == cells[start]:
run.append(next)
next += step
return run
func find_matches():
var found := {}
for pos in cells:
for step in [Vector2i.RIGHT, Vector2i.DOWN]:
var run: Array = run_from(pos, step)
if run.size() >= min_run:
for p in run:
found[p] = true
return found.keys()
Collecting results in a dictionary removes duplicates when a run is found from several starting cells, and it handles crossing runs correctly because shared cells are stored once.
Clearing and refilling
Clear in this order so data and display never disagree:
- Erase each matched key from cells and call erase_cell on the layer.
- Walk each column from the bottom up and move pieces down into empty cells, updating both data and tiles.
- Fill the empty cells at the top with new random kinds.
- Run find_matches again and repeat until it returns nothing. This loop produces chain reactions.
Disable input while this loop runs, and add a short tween between steps if you want the player to see the cascade. Sliding and merging puzzles reuse the same dictionary. The Zenva course lists tile-sliding mechanics and merging tiles with math-based logic, and runs 3h 45m at intermediate level.
Fixing a misbehaving board
Work from the simplest check upward, and stop as soon as the data and the drawing agree.
- Print the dictionary. If cells looks right but the screen does not, the fault is in your set_cell calls or tileset mapping, not in the rules.
- Test one direction. Call run_from on a cell you know is part of a run. Wrong lengths point to the step vector or the equality test.
- Log the picked cell. If clicks land on the wrong cell, the cause is the coordinate conversion, usually an offset layer or a scaled viewport.
- Stop tuning. When a full cascade ends with a stable, correct board, move on to export. Further polish is better judged on a real device.
Exporting to desktop and mobile
Godot exports with one click to Linux, macOS, Windows, Android, iOS and web-based platforms, with console support also listed, according to the official engine README. Install the matching export templates from the Godot website, then add a preset for each target in the Export dialog. Each platform has its own requirements, so follow what the dialog reports for your target.
Test desktop first, because it is the fastest loop. Then test on a real phone, since touch input and screen size are the likeliest sources of surprises. A tap that misses its cell on a handset usually traces back to the conversion step in the input function.
Frequently asked questions
Is Godot a good engine for a small puzzle game?
Yes. Godot is free and open source under the MIT license, with no royalties, and it is supported by the not-for-profit Godot Foundation. A course vendor also describes it as free, lightweight and beginner-friendly, which suits a board game built from tiles and a single script.
Where can I check the exact names of TileMapLayer and input event classes?
The class reference is accessible from inside the Godot editor, so you can look up TileMapLayer, InputEventMouseButton and InputEventScreenTouch while you code.
Are there official examples I can study?
Yes. The Godot team maintains official demo projects in their own GitHub repository, and they are also available through the Asset Store.
Can the same grid code support a sliding-block puzzle?
Yes. The dictionary of cells stays the same. Only the move rule changes, from swapping pieces and finding runs to shifting tiles along a row or column and merging equal neighbours.
