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 
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?