Problem with gathering custom data from tiles in a side view 2d game

Godot Version

4.3

Question

I have been trying to retrieve custom data from tiles in a tileset upon collision. The game is 2d and plays from a side view much like a 2d platformer.

Everything seemed to be going smoothly, and i came up with the following code (I’ll update it to TileMapLayer later on):

var surface_type : int
if collision_collider is TileMap:
	var tilemap = collision_collider as TileMap
	var collision_point = collision_info.get_position()
	# Convert collision point to local coordinates of the tilemap
	var local_point = tilemap.to_local(collision_point)
	# Get the tile coordinates from the local point
	var tile_coords = tilemap.local_to_map(local_point)
	# Access the custom data from the TileSet
	var data = tile_map.get_cell_tile_data(0, tile_coords)
	# Get the surface type data
	surface_type = data.get_custom_data_by_layer_id(0)
	print(surface_type)

Straight away the problem with this became apparent as if i hit a wall to the left or the ceiling i get the following error:

“Attempt to call function ‘get_custom_data_by_layer_id’ in base ‘null instance’ on a null instance.”

The issue is, i think because the TileMap indexes from top left to bottom right. Basically if i hit something below or to the right, it seems to identify the correct tile but left and top collisions are offset by one tile.

To visually illustrate what is happening i took the print out and spawned the godot icon over the tile i was trying to get the data from, like this:

var dot_scene = preload("res://dot.tscn")

var surface_type : int
if collision_collider is TileMap:
	var tilemap = collision_collider as TileMap
	var collision_point = collision_info.get_position()
	# Convert collision point to local coordinates of the tilemap
	var local_point = tilemap.to_local(collision_point)
	# Get the tile coordinates from the local point
	var tile_coords = tilemap.local_to_map(local_point)
	# Access the custom data from the TileSe
	var data = tile_map.get_cell_tile_data(0, tile_coords)
	# DEBUG Test which tile is being collided with
	var dot_instance = dot_scene.instantiate()
	var world_position = tilemap.map_to_local(tile_coords)
	dot_instance.position = world_position
	get_parent().add_child(dot_instance)

And sure enough, you can see when you hit any tiles below or to the right (well, you can’t see to the right but trust me when i say the result is correct!) the icon is spawned on the actual tile, whereas the ones to the left and above are offset by one tile.

The angular tiles do actually throw me off a bit because they all seem to be fine even though they are to the left and / or above!

I’m really struggling to work out a good way to fix this! An offset seemed like a good idea but as it is very much direction dependant that felt like a bit of a hack?

Im having the exact same issue with TileMapLayer collisions.

The raycast identifies the tiles to the left and to the north as offset by 1.

Vector2 collidorPosition = Raycast.GetCollisionPoint();
TileMapLayer tileMapLayer = (TileMapLayer)Raycast.GetCollider();
Vector2I tileCoords = tileMapLayer.LocalToMap(collidorPosition);

GD.Print("Collision Point: ", collidorPosition);
GD.Print("Tile Coordinates: ", tileCoords);

I wonder if this is a bug of some kind?

It seems extremely specific in that the diagonals, tiles that are BOTH up and left seem to return correctly but directly up or directly left are offset by 1.

OR! Hopefully I’m just missing something really silly that would fix this :smile:

So i have found a way to correct for this but i do feel like this correction shoudn’t be required!

As this only affects collisions where the tile is directly t the left or directly above, i have added an additional check for the collision normal and then i modify the collision point based on my tile size:

var surface_type : int
if collision_collider is TileMap:
	var tilemap = collision_collider as TileMap
	var tile_size = tilemap.tile_set.tile_size
	var collision_point = collision_info.get_position()
	var collision_normal = collision_info.get_normal()

	# Adjust the collision point based on the normal
	var adjusted_collision_point = collision_point
	if collision_normal == Vector2.DOWN: # Player collided from below
		adjusted_collision_point += Vector2(0, -tile_size.y)
	elif collision_normal == Vector2.RIGHT: # Player collided from the right
		adjusted_collision_point += Vector2(-tile_size.x, 0)

	# Convert collision point to local coordinates of the tilemap
	var local_point = tilemap.to_local(adjusted_collision_point)
	# Get the tile coordinates from the local point
	var tile_coords = tilemap.local_to_map(local_point)
	# Access the custom data from the TileSet
	var data = tile_map.get_cell_tile_data(0, tile_coords)
	# Get the surface type data
	surface_type = data.get_custom_data_by_layer_id(0)
	print(surface_type)

What i don’t really understand is why thos would be nececary in the first place? It seemingly works absolutely fine with tiles in all directions except directly up and directly left, even if the tile is up AND left it still works without the modifier.

Does anyone know why this might be? Or is it actually a bug?