Free Tutorial

Godot 4 Tilemap Tutorial
Build Beautiful 2D Levels Fast

SScriptSnap/March 2026/14 min read

Every great 2D game starts with a map. Whether you're building a sprawling RPG overworld, a tight Metroidvania corridor, or a cozy farming sim, Godot 4's TileMap and TileSet system is your workhorse for godot 2d level design. The system received a massive overhaul in Godot 4 — the old TileMap node and autotile workflow from 3.x are gone, replaced by a unified, layer-based architecture that's faster to use and far more powerful.

In this godot 4 tilemap tutorial, you'll learn how to set up a TileSet from a sprite sheet, configure terrain rules (the new autotile), work with multiple layers, add physics and collision, and manipulate tilemaps programmatically with GDScript. By the end you'll have a production-ready tilemap workflow you can drop into any project. Let's get building.

Step by Step
1

Understanding TileMap vs TileSet in Godot 4

Before touching any editor panels, let's clarify the two core concepts. A TileSet is a resource that defines your tiles — their textures, physics shapes, animation frames, terrain bits, and custom data. Think of it as the palette of Lego bricks you can place. A TileMap is the node that actually places those bricks on a grid in your scene. One TileSet can be shared across multiple TileMap nodes, which is ideal for reusing tilesets across scenes.

In Godot 4 the old separate TileMap modes (single tile, atlas, autotile) collapsed into a single “atlas source” concept. Every tile lives inside a TileSetAtlasSource, which references a texture and divides it into a grid of tiles. You can still have multiple atlas sources in one TileSet — useful when mixing sprite sheets from different artists or tile sizes.

Crucially, TileMap now supports multiple layers natively. Instead of stacking multiple TileMap nodes (the Godot 3 workaround), you add layers directly inside a single TileMap. Each layer can use different Z-indexing, different physics layers, and different tile sets. This dramatically simplifies your scene tree and gives you finer control over draw order.

2

Setting Up Your First TileSet

Create a new scene and add a TileMap node. In the Inspector, click the Tile Set property and choose New TileSet. Click the TileSet resource to open the TileSet editor at the bottom of the screen.

Next, drag your tileset sprite sheet into the atlas panel. Godot will ask you to auto-detect the tile size — confirm it or set it manually (common sizes are 16×16 or 32×32). The editor will slice your image into a grid of tiles. If some regions of the sheet are empty, right-click to clear them and keep your palette clean.

Each tile in the atlas can be selected and configured individually. The panels on the right let you assign physics layers, navigation layers, custom data layers, and terrain sets. We'll cover each of these in the sections that follow.

If you want to save and reuse the TileSet across scenes, right-click the resource in the Inspector and choose Save As… to store it as a .tres file. Then reference that file in any TileMap across your project. This is the recommended approach for any project with more than one map scene — it keeps your godot tileset data in a single source of truth.

3

Terrain Rules — The New Godot Autotile

If you've used Godot 3, you'll remember the bitmask-based autotile system. Godot 4 replaces that with Terrain Sets and Terrain Peering Bits — a much more intuitive and flexible approach. Think of a terrain as a “material” like grass, dirt, or water. Godot automatically picks the right tile variant depending on what neighbours a cell has.

To set one up: in the TileSet editor, open Terrain Sets in the Inspector. Add a terrain set (choose Match Corners and Sides for a typical 47-tile blob tileset, or Match Sides for simpler top-down tilesets). Then add individual terrains — e.g. Grass and Dirt.

Switch to the Terrains paint tab, select a terrain, and paint the peering bits onto each tile in your atlas. The center bit marks “this tile IS this terrain” and the edge/corner bits mark which neighbours the tile expects. Once painted, switch to the TileMap editor and use the Terrains painting mode. Now when you draw, Godot automatically picks the correct border, corner, and inner tiles for smooth transitions. This is the modern equivalent of godot autotile, and it's drastically more powerful.

A common workflow is to pair two terrains — for example, a “Ground” terrain and a “Wall” terrain. When painting walls, Godot auto-selects the right edge variant based on adjacent ground tiles, giving you perfect cliff faces, coastlines, or dungeon walls with zero manual fiddling.

Want to level up your GDScript skills? Our quick-start courses cover signals, state machines, and more — with copy-paste examples you can use today.

Browse courses →
4

Working with Multiple TileMap Layers

Real-world 2D games rarely use a single tile layer. You typically need at least a background layer, a main collision layer, and a foreground overlay. In Godot 4, you add layers directly in the TileMap node's Inspector under Layers.

Each layer has its own name, enabled toggle, modulate (color tint), Y-sort toggle, and Z-index. A common setup looks like this:

  • Layer 0 — Background: Ground tiles, grass, water, sand. Z-index 0.
  • Layer 1 — Terrain: Walls, cliffs, collision tiles. Z-index 1. Physics enabled.
  • Layer 2 — Decoration: Flowers, rocks, signposts. Z-index 2. No collision.
  • Layer 3 — Foreground: Tree canopies, bridge overhangs. Z-index 10 (drawn above the player).

When painting, select the target layer in the TileMap editor toolbar before placing tiles. Layers are independent grids — two layers can have different tiles in the same cell, which is exactly how you create depth in a top-down RPG or a Metroidvania with parallax.

Tip: if your project uses state machine patterns for character controllers, you can query specific tilemap layers to decide whether the player is on a ladder, in water, or on solid ground — all from the same TileMap node.

📧

Want more Godot tricks?

Get our free GDScript Cheat Sheet with 20+ copy-paste snippets for signals, exports, state machines & more.

GDSJoin 500+ Godot devs · No spam · Unsubscribe anytime
5

Physics and Collision Tiles

Most 2D games need solid tiles the player can't walk or fall through. Godot 4 handles this through Physics Layers on the TileSet. Open your TileSet resource, go to the Inspector, and under Physics Layers click Add Element. Set the collision layer and mask just like you would on a StaticBody2D.

Then, in the TileSet atlas editor, switch to the Physics paint tab. Select a physics layer, then click on each tile that should be solid. Godot gives you a polygon editor — for simple square tiles, the default full-tile rectangle works fine. For slopes, half-tiles, or one-way platforms, drag the vertices to shape a custom collision polygon.

One-way collision is especially useful for platforms: enable the “One Way” flag on the physics polygon to let characters jump up through a tile but stand on top of it. This single checkbox saves you from needing separate StaticBody2D nodes scattered across the scene.

For advanced use cases like procedural generation, you can assign different physics layers to different tile types — for instance, layer 1 for solid walls, layer 2 for hazards, layer 3 for water. Then your character's CharacterBody2D can query which type of tile it's touching and react accordingly.

6

Programmatic Tilemap Manipulation with GDScript

While the editor is great for hand-crafted levels, many games need to place or read tiles at runtime. Godot 4's TileMap API is straightforward once you understand the coordinate system. Here's the core of godot tilemap gdscript:

Placing a Tile

Use set_cell() to place a tile on a specific layer at a grid position. You need the source ID (which atlas in the TileSet) and the atlas coordinates (the tile's position in that atlas grid).

place_tile.gd
# Reference to the TileMap node
@onready var tile_map: TileMap = $TileMap

func place_grass(grid_pos: Vector2i) -> void:
    # layer 0, source_id 0, atlas_coords (0, 0)
    tile_map.set_cell(0, grid_pos, 0, Vector2i(0, 0))

func _ready():
    # Place grass at grid cell (5, 3)
    place_grass(Vector2i(5, 3))

Reading a Tile

Query what tile exists at a position with get_cell_source_id() and get_cell_atlas_coords(). A source ID of -1 means the cell is empty.

read_tile.gd
func get_tile_info(grid_pos: Vector2i, layer: int = 0) -> void:
    var source_id := tile_map.get_cell_source_id(layer, grid_pos)

    if source_id == -1:
        print("Cell is empty")
        return

    var atlas_coords := tile_map.get_cell_atlas_coords(layer, grid_pos)
    print("Source: ", source_id, " Atlas: ", atlas_coords)

Converting Between World and Grid Coordinates

When you detect a click or collision, you get world-space coordinates. TileMap provides helper methods to convert:

world_to_grid.gd
func _unhandled_input(event: InputEvent) -> void:
    if event is InputEventMouseButton and event.pressed:
        # Convert mouse position to local space, then to grid
        var local_pos := tile_map.to_local(event.position)
        var grid_pos := tile_map.local_to_map(local_pos)
        print("Clicked grid cell: ", grid_pos)

        # Convert grid back to world position (center of tile)
        var world_pos := tile_map.map_to_local(grid_pos)
        print("World center: ", tile_map.to_global(world_pos))
7

Procedural Level Generation with TileMap

Combining tilemaps with procedural generation is one of the most powerful patterns in godot 2d level design. Here's a complete example that generates a simple dungeon by carving rooms into a wall-filled grid:

dungeon_generator.gd
extends Node2D

@onready var tile_map: TileMap = $TileMap

# Atlas coordinates for our tile types
const WALL_TILE := Vector2i(0, 0)
const FLOOR_TILE := Vector2i(1, 0)

@export var map_width: int = 40
@export var map_height: int = 30
@export var room_count: int = 6

func _ready():
    generate_dungeon()

func generate_dungeon() -> void:
    # Step 1: Fill everything with walls
    for x in range(map_width):
        for y in range(map_height):
            tile_map.set_cell(0, Vector2i(x, y), 0, WALL_TILE)

    # Step 2: Carve random rooms
    for i in range(room_count):
        var rw := randi_range(4, 8)
        var rh := randi_range(4, 8)
        var rx := randi_range(1, map_width - rw - 1)
        var ry := randi_range(1, map_height - rh - 1)
        carve_room(rx, ry, rw, rh)

func carve_room(x0: int, y0: int, w: int, h: int) -> void:
    for x in range(x0, x0 + w):
        for y in range(y0, y0 + h):
            tile_map.set_cell(0, Vector2i(x, y), 0, FLOOR_TILE)

This pattern scales to any tilemap-based game. You can extend it with hallway carving between rooms, cellular automata for cave systems, or noise-based biome placement. The key insight is that set_cell() is cheap — Godot batches draw calls for TileMap layers, so even filling thousands of tiles at startup is fast.

Ready to build more complex game systems? Our GDScript course bundle covers signals, state machines, autoloads, and more — everything you need for production-ready Godot projects.

Get the bundle →
8

Custom Data Layers and Tile Metadata

Beyond visuals and physics, Godot 4 lets you attach custom data to individual tiles. This is incredibly useful for game logic — you can tag tiles as “climbable”, store a damage value for hazard tiles, or mark spawn points directly in the tilemap.

In the TileSet Inspector, add a Custom Data Layer. Give it a name (e.g. terrain_type) and a type (String, int, float, etc.). Then paint the values onto each tile in the atlas editor.

At runtime, query tile data like this:

tile_custom_data.gd
func get_terrain_at(grid_pos: Vector2i) -> String:
    var tile_data := tile_map.get_cell_tile_data(0, grid_pos)

    if tile_data == null:
        return "empty"

    return tile_data.get_custom_data("terrain_type")

# Usage: check if player is standing on lava
func _physics_process(_delta: float) -> void:
    var player_grid := tile_map.local_to_map(
        tile_map.to_local(global_position)
    )

    if get_terrain_at(player_grid) == "lava":
        take_damage(10)

Custom data layers work hand-in-hand with the signal bus pattern. For example, you can emit a global signal when the player steps on a specific tile type, letting distant systems (UI, audio, quests) react without tight coupling.

9

Animated Tiles and Alternative Tiles

Godot 4's TileSet supports per-tile animations natively. In the atlas editor, select a tile and open the Animation panel. You can define animation frames by referencing consecutive tiles in the atlas (horizontally). Set the frame duration, and every instance of that tile in your TileMap will animate automatically — no AnimationPlayer needed. This is perfect for flowing water, torches, blinking lights, and lava tiles.

Alternative tiles are another Godot 4 feature that reduces tileset bloat. Instead of duplicating a tile for every rotation or flip variation, you create alternatives of the base tile. Each alternative can have a different flip, transpose, modulate color, or even different custom data. Right-click a tile in the atlas and choose Create Alternative Tile to set one up.

Use this for things like tree variations (same trunk, different canopy), crate states (intact vs broken), or directional one-way platforms (same collision shape, flipped horizontally). Your level designers get variety without inflating the sprite sheet.

10

Performance Tips and Best Practices

Godot 4's TileMap renderer is highly optimized, but there are still patterns that can trip you up at scale:

  • Keep tile sizes power-of-two friendly. 16×16 and 32×32 are ideal. Non-power-of-two sizes still work but may introduce sub-pixel seams on certain zoom levels.
  • Minimize layers. Each layer is a separate draw call batch. Four layers is fine; twenty will hurt performance on lower-end hardware.
  • Use set_cells_terrain_connect() instead of painting tiles one-by-one in procedural code. It auto-resolves terrain borders in bulk and is significantly faster than calling set_cell() in a loop with manual terrain lookups.
  • Avoid modifying tiles every frame. set_cell() triggers a redraw of the affected chunk. Batch your changes or debounce them to avoid unnecessary GPU work.
  • Use Y-sort on the right layer. Only enable Y-sorting on layers where entities need depth sorting (e.g. decoration layers with tall sprites). Background layers don't need it, and it adds overhead.

For very large maps (thousands of tiles), consider chunking — split the world into multiple TileMap nodes, each covering a region, and enable/disable them based on camera position. This is the same approach used in the save system tutorial for persisting per-chunk tile modifications.

11

Putting It All Together — A Complete Example

Here's a practical scene setup that combines everything we've covered. This script initializes a tilemap with a ground layer, spawns walls around the border, and demonstrates reading tile data for game logic:

level_manager.gd
extends Node2D

@onready var tile_map: TileMap = $TileMap

const SOURCE_ID: int = 0
const GROUND := Vector2i(0, 0)
const WALL := Vector2i(1, 0)
const WATER := Vector2i(2, 0)

const WIDTH := 20
const HEIGHT := 15

func _ready():
    build_level()

func build_level() -> void:
    for x in range(WIDTH):
        for y in range(HEIGHT):
            var pos := Vector2i(x, y)
            # Border = wall, interior = ground
            if x == 0 or x == WIDTH - 1 or y == 0 or y == HEIGHT - 1:
                tile_map.set_cell(1, pos, SOURCE_ID, WALL)
            else:
                tile_map.set_cell(0, pos, SOURCE_ID, GROUND)

func is_walkable(grid_pos: Vector2i) -> bool:
    # Check if collision layer (1) has a tile
    var wall_id := tile_map.get_cell_source_id(1, grid_pos)
    return wall_id == -1 # No wall tile = walkable

This pattern — layered tiles with a walkability check — is the foundation of grid-based movement systems in games like Pokémon or Fire Emblem. Pair it with an inventory system and a dialogue system and you have the skeleton of a complete RPG.

Wrap Up

You now have a complete workflow for Godot 4 tilemaps — from setting up a TileSet and painting terrain rules, to layering tiles for depth, adding collision, and manipulating tiles programmatically with GDScript. The key takeaways:

  • TileSet is the reusable palette; TileMap places tiles on a grid.
  • Terrain Sets replace the old autotile system with a more flexible, painter-friendly workflow.
  • Multiple layers give you depth, foreground/background separation, and per-layer physics.
  • Custom data lets tiles carry game-logic metadata that scripts can query at runtime.
  • set_cell() and get_cell_tile_data() are your main tools for runtime tilemap manipulation.

Drop these patterns into your next project and watch how quickly you can build polished, functional 2D levels. Happy building!

Free download
+ weekly tips

Get the Free GDScript Cheat Sheet + Weekly Godot Tips

Join indie devs getting our GDScript Cheat Sheet with 20+ copy-paste snippets for signals, exports, state machines, and more — plus weekly Godot tips and architecture tricks straight to your inbox.

20+ GDScript snippets
Weekly Godot tips
Zero spam, cancel anytime
>
GDSJoin 500+ Godot devs. Unsubscribe in one click.