Quick answer: The most common cause is that the TileSet's physics layer has no collision shapes painted onto the tiles. In Godot 4, you must first add a physics layer to the TileSet resource, then use the TileSet editor to paint collision polygons onto individual tiles.

Here is how to fix Godot TileMap collision not working properly. You have painted a beautiful tile-based level. Your character drops in and falls straight through the floor. The TileMap is there, the tiles are visible, but the physics engine treats them like they do not exist. TileMap collision in Godot 4 requires a multi-step setup that is easy to get partially wrong, and a partial setup produces zero collision rather than partial collision. Here is how to get every piece in place.

The Symptom

Your character, a CharacterBody2D or RigidBody2D, passes through TileMap tiles as if they are not solid. The tiles render correctly on screen. There are no errors in the Output panel. The character’s collision with other StaticBody2D or other non-TileMap objects may work fine, making the TileMap the only thing that seems broken.

You might also encounter a subtler variant: some tiles have collision and others do not, even though they look identical. Or collision works on one layer of a multi-layer TileMap but not another. In all cases, the root cause is in the TileSet configuration, not the TileMap node itself.

Running a quick debug test confirms the problem. If you add a temporary StaticBody2D with a CollisionShape2D at the same position as a tile, the character collides with it. The physics engine works — the TileMap just is not participating in it.

Physics Layer Not Set on TileSet

In Godot 4, collision shapes on tiles are organized under physics layers. A TileSet can have zero or more physics layers, and each one has its own collision layer and mask bitmask. If your TileSet has no physics layers defined, there is nowhere for collision shapes to live, and no tiles in the map will have any physical presence.

This is the single most common cause of TileMap collision not working. Godot 4 changed how TileSet physics are configured compared to Godot 3, and many tutorials either skip this step or assume you already know about it.

# Diagnostic: check if the TileSet has physics layers
extends Node2D

@onready var tile_map: TileMapLayer = $TileMapLayer

func _ready():
  var tile_set = tile_map.tile_set
  if tile_set == null:
    push_error("TileMapLayer has no TileSet assigned!")
    return

  var physics_layers = tile_set.get_physics_layers_count()
  print("TileSet physics layers: ", physics_layers)

  if physics_layers == 0:
    push_warning("No physics layers! Tiles will have no collision.")
  else:
    for i in physics_layers:
      print("  Layer %d - collision_layer: %d, collision_mask: %d" % [
        i,
        tile_set.get_physics_layer_collision_layer(i),
        tile_set.get_physics_layer_collision_mask(i)
      ])

To fix this, select your TileSet resource in the Inspector. Scroll down to the Physics Layers section and click Add Element. A new physics layer appears with default collision layer and mask values of 1. This alone does not create collision shapes — it just creates the container for them. You still need to paint the shapes onto tiles, which is the next step.

Collision Shapes Not Painted on Tiles

Having a physics layer on the TileSet is necessary but not sufficient. You must also paint collision polygons onto individual tiles using the TileSet editor. Each tile can have its own collision shape per physics layer, and by default, tiles have no collision shape even when a physics layer exists.

Open the TileSet editor by double-clicking the TileSet resource or clicking TileSet at the bottom of the editor while the TileMap is selected. Switch to the Select mode and click on a tile. In the right panel, expand the Physics section. You will see one entry per physics layer. Click on a physics layer to enter collision polygon editing mode, and draw the shape that should be solid for that tile.

# Programmatically add collision to a tile (useful for procedural tilesets)
extends Node

func setup_tile_collision(tile_set: TileSet, source_id: int, atlas_coords: Vector2i):
  var source = tile_set.get_source(source_id) as TileSetAtlasSource
  if source == null:
    return

  var tile_data = source.get_tile_data(atlas_coords, 0)

  # Create a full-tile collision rectangle
  var tile_size = tile_set.tile_size
  var half = Vector2(tile_size) / 2.0
  var polygon = PackedVector2Array([
    Vector2(-half.x, -half.y),
    Vector2(half.x, -half.y),
    Vector2(half.x, half.y),
    Vector2(-half.x, half.y)
  ])

  # Set collision polygon on physics layer 0
  tile_data.set_collision_polygons_count(0, 1)
  tile_data.set_collision_polygon_points(0, 0, polygon)

For a quicker workflow on large tilesets, use the Paint mode in the TileSet editor. Select the physics layer property you want to paint, then click or drag across multiple tiles to apply a default full-tile collision rectangle. You can then go back and customize individual tiles that need non-rectangular shapes like slopes or platforms.

Wrong Collision Layer and Mask

Even with physics layers and collision shapes properly configured, the TileMap tiles will not interact with your character if the collision layer and mask bits do not align. The TileSet’s physics layer has its own collision layer and mask, separate from the TileMap node’s properties.

The rule is the same as for any physics object: the character’s collision mask must include at least one bit that the TileSet physics layer’s collision layer is set to. If your character’s mask scans layer 1 but the TileSet physics layer is on layer 3, they will never collide.

# Debug collision layers between player and tilemap
extends CharacterBody2D

func _ready():
  print("Player collision_layer: ", collision_layer)
  print("Player collision_mask: ", collision_mask)

  # Check which layers the player's mask includes
  for i in range(1, 33):
    if get_collision_mask_value(i):
      print("  Player scans for layer: ", i)

# Ensure the player and tileset agree on layers
func _fix_layer_mismatch():
  # Set player to scan layer 1 where our tiles live
  set_collision_mask_value(1, true)
  print("Player mask updated to include layer 1")

The most reliable approach is to establish a layer naming convention early in your project. For example: layer 1 for world/terrain, layer 2 for player, layer 3 for enemies, layer 4 for projectiles. Then set your TileSet physics layer to collision layer 1, and ensure your player’s mask includes layer 1.

One-Way Collision Issues

One-way collisions on TileMap tiles allow characters to jump up through a platform and land on top of it. When this is not working, the character either passes through in both directions or collides from both directions, defeating the purpose of one-way platforms.

One-way collision is a per-polygon property set in the TileSet editor. Select a tile, open its collision polygon, and enable the One Way checkbox. The direction is determined by the polygon’s winding order and normal — by default, the solid side faces upward for a horizontal platform.

# Set up one-way collision on a tile programmatically
extends Node

func configure_one_way_tile(tile_data: TileData, physics_layer: int):
  # Enable one-way collision on the first polygon
  tile_data.set_collision_polygon_one_way(physics_layer, 0, true)

  # Set a margin for fast-moving bodies
  # Higher values prevent tunneling through the platform
  tile_data.set_collision_polygon_one_way_margin(physics_layer, 0, 4.0)
  print("One-way collision enabled with margin 4.0")

# For CharacterBody2D, ensure floor snap is configured
extends CharacterBody2D

func _ready():
  # Floor snap helps maintain contact with one-way platforms
  floor_snap_length = 8.0
  print("Floor snap length: ", floor_snap_length)

A common pitfall is setting the one-way margin too low. If your character moves fast enough to travel more than the margin distance in a single physics frame, they can tunnel through the one-way platform. Increase the margin to match or exceed your character’s maximum vertical speed divided by the physics tick rate.

TileMap vs TileMapLayer Migration in 4.3+

Godot 4.3 deprecated the TileMap node in favor of TileMapLayer. The old TileMap node combined multiple layers into a single node, while TileMapLayer represents a single layer as its own node. This change affects collision because each TileMapLayer has its own physics properties and its own collision behavior.

If you upgraded a project from Godot 4.2 or earlier to 4.3+, your existing TileMap nodes still work but are considered legacy. Collision issues can appear if the internal layer-to-node conversion does not preserve all physics layer settings, or if scripts reference the old TileMap API for collision queries.

# Migration: convert TileMap layer references to TileMapLayer
# Old Godot 4.2 code (deprecated):
# var cell = tile_map.get_cell_source_id(0, coords)  # layer index 0

# New Godot 4.3+ code with TileMapLayer:
extends Node2D

@onready var ground_layer: TileMapLayer = $GroundLayer
@onready var platform_layer: TileMapLayer = $PlatformLayer

func _ready():
  # Each TileMapLayer is its own node now
  var cell = ground_layer.get_cell_source_id(Vector2i(5, 10))
  print("Ground tile source at (5,10): ", cell)

  # Collision properties are per-TileMapLayer
  print("Ground layer collision_layer: ", ground_layer.collision_layer)
  print("Ground layer collision_mask: ", ground_layer.collision_mask)

  # You can disable collision on decorative layers
  platform_layer.collision_enabled = true
  print("Platform collision enabled: ", platform_layer.collision_enabled)

To migrate, right-click the old TileMap node in the scene tree and select Convert to TileMapLayer nodes. This creates one TileMapLayer child per layer in the original TileMap. After conversion, verify that each TileMapLayer’s collision settings are correct and update any script references from the old TileMap API to the new TileMapLayer API. The TileSet resource itself does not need to change — it is shared across all layers.

"I spent three hours debugging why my tiles had no collision. Turns out I had added the physics layer to the TileSet but never actually painted collision shapes onto the tiles. The TileSet editor has two separate steps and skipping the second one gives you zero feedback."

Related Issues

If your collision shapes work but the character jitters when walking on tiles, see our guide on CharacterBody2D jittering and wall sliding. For characters snapping back to the ground after jumping off tiles, ground snapping issues covers floor_snap_length tuning. If your TileMap renders correctly but tiles appear on the wrong layer, CanvasLayer rendering order explains the draw order system.

Two steps: add the physics layer, then paint the shapes. Miss either one and you get zero collision.