How to detect which block type the player touches?

Godot Version

4.2.2

Question

I’m making a platformer game with different block types. The player is a CharacterBody2D, and all the blocks are in a TileMap.

How can I detect collision with a specific block type, e.g. spikes? I read that I need collision layers / masks / an Area2D, but what do I have to do exactly?

So, one of the things you could do is to place an Area2D node over the spikes, give it a group, let’s say ‘spikes’, and then connect the _on_area_entered(area: Area2D) signal to your CharacterBody2D script, under the Inspector > Nodes > Signals tab. Your code will look something like:

var player_hp: int = 100

func _on_area_entered(area: Node2D) -> void:
     if area.is_in_group("spikes"):
          player_hp = 0
     else:
          return

This will also open up the code to allow you to put in enemy collision, as it is basically the same process.

Hopefully that helps!

EDIT: This isn’t necessarily the best method to do this, as you’ll have to manually connect every spike instance. However, if you created the spikes as their own scene and moved the code under its own script, you could simply instantiate the spikes in your world and that would prevent you from needing to connect multiple different spikes.

Thanks! The spikes are in my TileMap, though, what do I have to do? Create a TileMap for each block type?

I would suggest creating a AnimatedSprite2D and using the SpriteSheet option to select it out of your tilemap. I don’t know of a way to use the Tilemap feature for scene objects, but that doesn’t mean it doesn’t exist.

EDIT: picture included of spritesheet button

A separate AnimatedSprite2D for each spike, you mean?

So, that’s where I’m saying to create the spikes in their own scene. It will prevent you from needing to manually connect/create each individual spike, and makes it so that every spike you place (using the chain-link ‘instantiate’ in the SceneTree) will inherit the properties of the original one you create.

Is there a way to create the spikes using code?
Here is what I have now:
Godot

If I’m understanding correctly, yes! Although, instead of a node2D, you’ll want to make them an Area2D with collision and a sprite as children, like so:
image

Then, you’ll want to connect the signal from here:
image

to your new script you will create in Area2D; let’s call this script spikes.gd. This will only do one thing, but in my example I have set the Player to be in a group ‘Player’. You’ll need to do the same, and this eliminates the need for the player having the script in my aforementioned post.

~~Spikes.gd~~
#Don't type out this function, make sure you connect the signal in the above screenshot
func _on_body_entered(body: Node2D) -> void:
	if body.is_in_group("Player"):
		body.player_hp = 0 #'body' here will call the 'player_hp' variable in 'Player.gd'
	else:
		return

Now, in your world scene, whenever you add an instance (Ctrl+Shift+A if using Windows, with the main node selected) of the spikes, it will always check if the player enters it without you needing to manually set everything up each time.

EDIT: Helpful hint when placing multiple instances is to enable the grid and smart snap options in your world:


this can help make sure you’re placing them in line with one another.

Thank you. I used an AnimatableSprite2D because I want the spikes to be able to move. Are Area2Ds moveable?

I would like to create the spike instances in the code, like so:

add_spike_to_position(50, 100)
add_spike_to_position(100, 100)
add_spike_to_position(150, 100)
add_spike_to_position(200, 100)
…

How can I do this efficiently? My levels will contain thousands of blocks.

To be perfectly honest, I haven’t created complex enough levels to worry about that, however, this Reddit post on r/Godot seems to have good information in the comments to do exactly what you’re looking for.

EDIT: it involves creating a separate TileMap for just the spikes, and on _ready(), replace them with the scene version. There is a video tutorial linked in the comments there as well.

Although, instead of a node2D, you’ll want to make them an Area2D with collision and a sprite as children, like so:

I tried that, but the player (CharacterBody2D) falls right through the block:
img2

img

My apologies on clarification; that should have been just the spikes that are area2D nodes. For your normal blocks, using collision in your TileMap will work fine.

But, to note, that above referenced Reddit article states for your case you’d want to make a new TileMap node and only select the spike(s) from it, instead of manually creating tiles on the whole thing. This way, in your _ready() function, you can replace all of the painted spikes through code.

There is an easier way to do this using physics layers! Please give me a moment and I’ll make an explanation with pictures

(Preface: There is more than one solution to this problem, each with pros and cons. I recommend exploring to see what approach works best for your project)

I am assuming that your TileMap node already has one Physics Layer added for collision with your player. You will want to add another Physics Layer for your spikes, and set it’s Layer to be something different than the regular collision layer:

Within the TileSet editor for your TileMap, take the tile for your spikes and paint a collider for them on Physics Layer 1:

Now we need to make the player react to spikes. Add an Area2D node to your player to use as a hurtbox and add a CollisionShape2D to it:
image
Note: You can change the shape of the hurtbox to be different than the player’s physics collision.

Next, set the Hurtbox’s collision layer and mask bit to 2:
image

Go to the Hurtbox’s signals and connect body_entered to your player script, like this:
image
Within your player script, write code for the function called during spike collision:

# At the end of your player script...
func _on_hurtbox_body_entered(body):
	print("Collided with spikes!")
	# Spike behavior should go here.

At this point, the player will have a hurtbox that reacts to colliding with spikes. The player will not be able to stand on spikes and will fall through, so you may want to add a collider to them on Physics Layer 0 if that behavior is not desired. Good luck!

1 Like

I haven’t messed with the Physics Layers enough, but I need to!

Alternatively, instead of adding another collider on physics layer 0, you can set the following variable/function; I’ve found it to be useful for many different situations, including freezing the player during dialogue.

var can_control: bool = true

func _physics_process(delta: float) -> void:
     if can_control == false: return

as long as that’s before the player movement code, you can do something like

if player_hp == 0:
     can_control = false

and it will freeze the player (at least from my testing) in place on the spikes/void so you can do a “you died!” screen, or something similar.

Thank you! But will I need a physics layer for each block type? Since Godot is limited to 32 layers – what if there are more than 32 types of blocks?

Anyway, it’s probably better to use individual sprites for each block, as they should be able to move too. TileMap tiles can’t move, can they?

I am realizing now that the solution I proposed is not very extendable, since it would quickly spiral out of control if you needed many more unique block interactions.

If blocks should be able to move individually from each other then I do believe something other than a TileMap would be better!

1 Like