Skip to content

Articles

Godot 4 tilemap collision: the TileMapLayer setup

Add collision to a TileMapLayer in Godot 4.3+: a TileSet physics layer, per-tile collision polygons, a CharacterBody2D mask that matches, and one-way platforms.

How to Add Tilemap Collisions in Godot 4

A tile either collides or it does not, and the switch is not on the TileMapLayer. It is in the TileSet: one physics layer plus a collision polygon on each tile. Two more things then have to match — the layer's collision bits and the body's mask — or the map looks solid and the player walks straight through it.

This is the current-node version of the tilemap collisions video. The video was recorded before TileMapLayer existed: Godot 4.3 split TileMap into one TileMapLayer per layer, and every step below uses the 4.3+ node. The editor wording is from 4.8 beta 2 and the APIs used here exist unchanged since 4.3.

1. Three pieces, not one switch

Piece Where it lives What it decides
Physics layer TileSet resource → Physics Layers which collision bits the tiles occupy
Collision polygon TileData for one tile the shape that tile contributes
Body mask your CharacterBody2D which bits the body scans for

All three have to agree. A physics layer with no polygons collides with nothing. Polygons on a tile nobody painted do nothing. Correct polygons on layer 1 are invisible to a body whose mask does not include layer 1.

2. Create the TileSet and add a physics layer

  1. Add a TileMapLayer node to the scene.
  2. In the Inspector, set Tile Set → New TileSet, then set Tile Size to your tile size.
  3. Open the TileSet panel at the bottom of the editor and drag the tilesheet onto it. Answer Yes to create tiles automatically.
  4. Select the TileMapLayer again, unfold Tile Set → Physics Layers, and click Add Element. Godot creates layer 0 with collision layer 1.
  5. Clear that layer's Collision Mask. A static floor never needs to detect anything; the mask matters only when the tilemap itself has to query bodies.

Writing the same layer from code is three calls:

var tileset := TileSet.new()
tileset.tile_size = Vector2i(32, 32)
tileset.add_physics_layer()
tileset.set_physics_layer_collision_layer(0, 1)  # tiles occupy layer 1
tileset.set_physics_layer_collision_mask(0, 0)   # tiles detect nothing

3. Give the solid tiles a polygon

  1. In the TileSet panel, switch to the Select tool.
  2. Select the tiles that should be solid. Drag a rectangle, or hold Shift and click to add and remove tiles from the selection.
  3. In the middle column, unfold Physics Layer 0 and use the three-dots menu above the polygon editor: Reset to default tile shape, or press F.

That gives each selected tile a rectangle covering the full cell. Polygon points are tile-local and centered, so a full 32×32 cell is (-16, -16) to (16, 16), not (0, 0) to (32, 32).

Select a single tile and drag points to fit a shape that is not a full square — a slope, a fence, the top of a table. Use the same three-dots menu to add or remove points.

Godot's debug view of the painted TileMapLayer: every solid cell shows its collision polygon in blue, and the one-way plank shows a thin polygon along its top edge.

The blue overlay above is Godot's own collision debug draw, not art added to the level. Every solid cell carries a full-cell polygon; the plank tile carries a thin polygon on its top edge only.

4. One-way platforms

To make a tile a platform the player can jump through from below:

  1. Select the platform tiles in the TileSet panel.
  2. In Physics Layer 0, expand Polygon 0.
  3. Enable One Way Collision.

A one-way polygon lets a body pass from one side and blocks it from the other. Keep the polygon along the surface the player should stand on — a thin strip at the top of the tile — rather than the full cell, and test the direction in a running scene: the editor's debug overlay draws the shape, not which side it blocks, and the point order is not a reliable way to reason about it.

5. Paint the level and check the layer

Paint the tiles onto the TileMapLayer in the 2D view. Two node properties decide whether the map pushes back at all:

  • Collision Enabled (collision_enabled) is on by default. Turning it off gives you a decorative layer with identical tiles and no physics.
  • Collision Visibility Mode forces the debug shapes on, which is the fastest way to see whether a painted tile actually has a polygon.

The demo level for this article: a 24×24 CharacterBody2D standing on a grass tile row, a one-way plank platform above and to the right, and a brick wall at the right edge.

The level above is three painted rows: grass floor, a brick wall four cells tall, and a plank platform. The steps in this article are what make the floor solid.

6. Make the body collide

The tiles sit on physics layer 1. The body has to scan layer 1 and be something else itself:

  1. Add a CharacterBody2D with a CollisionShape2D sized to the character.
  2. Set Collision Layer to 2 and Collision Mask to 1.

The mask is the one that matters. A body with mask 0 falls through a fully collided floor and logs no error — there is nothing to collide with as far as that body is concerned. If several kinds of object share the ground, give each its own layer bit and have the player's mask include all of them.

7. Move it

extends CharacterBody2D

@export var speed := 220.0
@export var jump_velocity := -600.0

var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")


func _physics_process(delta: float) -> void:
	if not is_on_floor():
		velocity.y += gravity * delta
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = jump_velocity
	velocity.x = Input.get_axis("ui_left", "ui_right") * speed
	move_and_slide()

is_on_floor() is true only after move_and_slide() has resolved a contact, so gravity needs the is_on_floor() guard. Without it the body accumulates downward velocity while standing still, and the first frame it leaves the ground it drops like a stone.

8. Jump up through a one-way platform, drop back down

Rising through a one-way polygon works on its own: the body passes, then lands on it on the way down. To drop through on purpose, switch off the layer bit for a moment:

func drop_through() -> void:
	set_collision_mask_value(1, false)
	await get_tree().create_timer(0.15).timeout
	set_collision_mask_value(1, true)

set_collision_mask_value(1, false) clears bit 1, which is the bit the tiles occupy. The timer has to be long enough for the body to clear the polygon — 0.1–0.2 s at normal gravity. Two warnings: do not leave the bit off if the player can cancel the drop mid-air, and do not disable the whole mask if other layers (enemies, triggers) share it.

9. Read the tile under the player

Collision resolves movement; it does not tell you what the player is standing on. Ask the layer directly:

func surface_below(layer: TileMapLayer) -> StringName:
	var cell := layer.local_to_map(layer.to_local(global_position) + Vector2(0, 16))
	var data := layer.get_cell_tile_data(cell)
	if data == null:
		return &""
	return data.get_custom_data("surface")

local_to_map() takes layer-local coordinates, so convert the global position first with to_local(). get_cell_tile_data() returns the tile's TileData — the same object that holds the collision polygon — which is where a custom data layer such as surface lives. The layer has to exist first: add it under Tile Set → Custom Data Layers → Add Element, and set the value per tile in the TileSet panel. On a TileSet without a layer named surface, the call logs TileSet has no layer with name: surface and returns null.

10. When tiles are not solid

Symptom Cause Fix
Player falls through everything body mask does not include the tile layer set mask bit 1 (or the bit the layer uses)
Player falls through one tile type only that tile has no polygon select it in the TileSet panel, press F
Level is solid but the debug shapes are missing collision_enabled off, or collision debug hidden re-enable the property, turn on visible collision shapes
One-way platform blocks from below One Way Collision off, or the polygon covers the full cell enable it, and keep the polygon on the standing surface
Player sticks to walls gravity applied while on the floor, or a mask that ignores walls guard gravity with is_on_floor(); check layers
Nothing collides after an editor restart the TileSet was edited but not saved save the TileSet resource and re-run

11. What this unlocks

The layer is now a physics surface. The next step most 2D games take is putting objects on it: snapping furniture, props, or buildings to TileMap cells converts the same cells into placement targets, and the top-down game guide puts movement, camera, and collision in the order that avoids rewrites.

If you would rather not write placement, preview, and commit rules yourself, Grid Placement is the paid addon that builds on this layer. Nothing on this page needs it.

How this was checked

Every claim above was exercised on Godot 4.8 beta 2 with a headless test scene, not typed from memory:

  • the body falls and lands on the grass tile's polygon at the expected height;
  • a brick wall stops horizontal movement at the cell edge;
  • a one-way plank lets the body rise through it, then holds it on the way down;
  • collision_enabled = false drops the same body through the same floor;
  • local_to_map() plus get_cell_source_id() return the painted cell.

The TileSet, polygons, and one-way flag in that test are built with the same calls the editor makes, so a failure in the editor steps shows up in the test.

Download the demo project (6 KB) and run the same scene yourself; the zip contains tests/test.tscn, its expected output, and the two screenshots above come from that project.