TileMapLayer: Applying custom collision to a tile during runtime doesn't work

Godot Version

4.4

Question

My game requires me to update individual cells/tiles collisions if the map data gives the tile a different collision than the default defined in tileset, and for that I am using _tile_data_runtime_update.

What I have works visually (I can see the collision shape is applied correctly when running game in debug) but the player can’t collide with it. I have collision layer and masks enabled and they’re both on layer 1. Player has layer 1 and 4 enabled. Am I forgetting about something? Am I doing something wrong? I looked for a solution everywhere, but can’t find anything.

game_scene.gd:

for i in tiles.size():
		var tile_data = tiles[i]
		var x = i % Constants.map_width
		var y = i / Constants.map_width
		var cell_pos = Vector2i(x, y)
		
		var source_id = get_or_create_tile_source(tile_data["id"])
		var collision_id = int(tile_data["properties"][2])
		var transform = get_tile_transform(tile_data["properties"])
		
		# Set cell with transform
		tilemap.set_cell(
			cell_pos,
			source_id,
			Vector2i(0, 0),  # Atlas coordinates (assuming tiles are at (0,0) in their source)
			transform  # Apply the transform
		)
		
		var tile_source = tilemap.tile_set.get_source(source_id)
		if tile_source is TileSetAtlasSource:
			# Get DEFAULT collision ID from source
			var base_data = tile_source.get_tile_data(Vector2i(0, 0), 0)
			var stored_id = base_data.get_custom_data("collision_id")
			
			# Only override if different
			if stored_id != collision_id:
				set_specific_cell_collision(cell_pos, collision_id)
				
func set_specific_cell_collision(cell_pos: Vector2i, collision_id: int):
	# Store collision override for specific cell
	tilemap.custom_collisions[cell_pos] = collision_id

TileMapLayer.gd (Attached to TileMapLayer Node):

extends TileMapLayer

var parent_game: Node

# Stores cell-specific collision overrides {Vector2i: collision_id}
var custom_collisions := {}

func _use_tile_data_runtime_update(coords: Vector2i) -> bool:
	return custom_collisions.has(coords)

func _tile_data_runtime_update(coords: Vector2i, tile_data: TileData) -> void:
	var collision_id = custom_collisions.get(coords, -1)
	
	# Clear existing collision polygons for PHYSICS LAYER 0 (editor's Layer 1)
	tile_data.set_collision_polygons_count(0, 0)
	
	# Apply new collision shape from the library
	if collision_id in parent_game.collision_tiles:
		var shape = parent_game.collision_tiles[collision_id]
		if shape is Array:  # Multiple polygons
			tile_data.set_collision_polygons_count(0, shape.size())
			for i in range(shape.size()):
				tile_data.set_collision_polygon_points(0, i, shape[i])
		else:  # Single polygon
			tile_data.set_collision_polygons_count(0, 1)
			tile_data.set_collision_polygon_points(0, 0, shape)

Or maybe it’s a new bug?

Looks like a bug. In here godot/scene/2d/tile_map_layer.cpp at 4.4 · godotengine/godot · GitHub the cell is never added to the dirty list as far as I can tell godot/scene/2d/tile_map_layer.cpp at 4.4 · godotengine/godot · GitHub

It seems to work fine in Godot 4.5 though. I’d recommend you to search if an issue has been opened (also check for closed ones) here GitHub · Where software is built and open one if not.

Is godot 4.5 out and available for use already? I wasn’t aware of that

Sorry, it’s not released yet. I tested it with a custom build. There’s a dev build available Dev snapshot: Godot 4.5 dev 1 – Godot Engine I’m not sure if the problem is fixed in that one.