How can I get the collision layer of a colliding tile in a TileMapLayer?

Godot Version

4.7.2

Question

Basically I want to add a “magnet” mechanic to the player in my game, where the player can stick to metal walls. I’m building the level collisions all in a TileMapLayer, so at first I figured I’d make a custom data layer that tells whether a given tile is “metal” or not, but I realized that wouldn’t really work because I could have certain tiles that are not metal on every side.

That’s when I thought of making a collision layer specifically for metal surfaces: tiles allow multiple physics layers to be set up so I could have a layer for regular surfaces and other for metal surfaces, and then set up the tiles so that only the metal sides of a tile have metal collisions.

Makes sense, right? Now, uh… how do I actually check the collision layer of a colliding tile? I’m using move_and_collide() for my player’s collision checking, which returns a KinematicCollision2D object. Normally you’d use get_collider() on this object and then retrieve its collision_layer property, but because the node returned by this function is the TileMapLayer, it doesn’t have a collision_layer!

I’m pretty sure there’s something missing. It’s either right in my face or something not really obvious. Either way, I’d appreciate any help :slight_smile:

Alright, I think I found a solution.

Basically set the player body’s collision mask to that of metal surfaces, check for a collision and then revert the mask back to the original. If you had a collision, by no doubts it was a metal surface. I don’t even need to worry about which specific tile the player has touched with this approach, in fact I don’t need to worry if the metal the player is touching is from a tile at all.

extends Node

# The collision layer we'll use for metal surfaces
@export_flags_2d_physics var metal_collision_layer := 1 << 3

# The body we'll be using to check for metal collision
@export var body : CharacterBody2D

func _physics_process(_delta: float) -> void:
	# Save the body's original collision mask so we can reset it later
	var body_original_collision_mask := body.collision_mask
	
	# Set the collision mask to the metal collision layer
	body.collision_mask = metal_collision_layer
	
	# Get the collision info from the body with the modified property
	var collision := body.move_and_collide(
		Vector2.ZERO, 	# We want to check around the position the body
						# is currently at, so no motion
						
		true, 	# It's just a test, don't move the body
		
		1, 		# Add a nice margin here so the code can detect the collision properly
		
		true 	# The body overlapping with the collision is totally expected. 
				# Set this to true.
	)
	
	# Reset the body's collision mask after we're done
	body.collision_mask = body_original_collision_mask
	
	# If we had a collision, we ARE touching a metal surface!
	if collision:
		print("Touching metal!")

It works nice and should solve the problem for me, but I wonder if someone else has a better idea?