+1 vote

So I am working on a project and I need to know when the player has hit a collision shape in a specific tile. I have been looking at the Godot documentation all day to try and find a solution, but I have not found one yet.

in Engine by (13 points)

2 Answers

0 votes

I think you are using a tilemap?
For a single tile (a scene you create yourself), it's simple to have collisions.
However, in Tilemap, the tiles aren't scenes you created. They are from what is called the "Tileset".
But that's okay. Still, create a scene for a tile and save it. Then attach a script to the Tilemap, iterating every tiles, and instance the tile scene you just created, set its position to where the iterated tile is. After that, free the tilemap (Remove it from the scene). The "tiles" in the Tilemap here are just representation of your tiles, so the idea is to iterate these "fake" tiles and instance the "real" tile that is the tile you saved as a scene.
This way, you can customize your tiles by designing your own tile scenes (including the adding of CollisionShape)

Edit: Remember by saying iterating I mean using a for loop in the script attached to TileMap. The tile scene you created is only one, every tile instanced from the TileMap is an instance of the scene you created.
To mkae a difference in properties between different tiles(Like having collisions or not), you can try to create a variable "id" for control. "id" there to represents each tile.
Further info for how to use "id" to control your tiles: each id can be used to represent a "fake" tile from the Tileset. When instancing, pass the id to the instanced scene("real" tile) and init it according to what id is passed. Creating a function init()or init_by_id(id) if you want, though using _ready() is fine.
Back to the question again. Your need to know which specific tile is being collided, and if you want, for more, what events you want to trigger (Just like triggers), can be done in the designing the tile scene.

by (756 points)
edited by

I don't think this is good advice. The whole point of a TileMap is that you don't have to create a million nodes to build a simple level. If you just want to add CollisionShapes to certain tiles, that can be easily done with TileMaps. No need to spawn a lot of instances.

Yes definitely we have to take each tile as a node, but it doesn't mean we have to create them manualy. It's through iterating. We only need to design one scene and using a for loop. It's pretty straight forward and this way you can custumize the tiles including the adding of things like triggers and anything you want when the game becomes more complex. Also, this is simple to do, with only a few lnes of code and one scene. It won't slow down the game, too.
The point of a TileMap is that it helps to arrange the tiles in order and that we don't have to create lots of nodes manually. It also helps when we only need simple tiles that are just sprites, or just walls with collisions, so that we don't have to create them through the adding of nodes(For simple tiles that only differ in appearance).

+3 votes

As you've not been very specific with your question, I'll assume you already have set up some TileMap and added a CollisionShape2D to at least one tile. Furthermore I'll assume that your player character is a KinematicBody2D.

Movement using move_and_collide

var tilemap = get_node("<RelativePathToTileMap>")

var collision = move_and_collide(velocity * delta)
if collision:
    var cell = tilemap.world_to_map(collision.position - collision.normal)
    var tile_id = tilemap.get_cellv(cell)

Movement using move_and_slide

var tilemap = get_node("<RelativePathToTileMap>")

velocity = move_and_slide(velocity, Vector2.UP)
for i in range(get_slide_count()):
    var collision = get_slide_collision(i)
    var cell = tilemap.world_to_map(collision.position - collision.normal)
    var tile_id = tilemap.get_cellv(cell)
by (10,197 points)

I can't get this approach to always work. Using

var cell = tilemap.world_to_map(collision.position - collision.normal)
var tile_id = tilemap.get_cellv(cell)

I will get tile_id of -1 if the collision happens on the right side of the tile, and the proper tile_id if it happens on the left side.

I created a tiny Godot project that illustrates what I am seeing:

https://github.com/city41/GodotTileMapCollisionTest

If anyone can help me understand what I'm doing wrong, I'd be grateful!

When a collision between a KinematicBody2D and TileMap occur, I want to grab the tile id properly for all collisions.

Seems like the reported collision.position is the rightmost point on the tile the KinematicBody2D collided with, in your example: (128, 65). However, if you use those coordinates for a cell-lookup with world_to_map, this will result in cell (2, 1) not (1, 1) because cell 0 includes pixels 0 to 63 and cell 1 only includes pixels 64 to 127, so pixel 128 is already considered part of the third cell. Luckily the fix is simple: just subtract one pixel from the collision.position before the lookup:

var cell = tilemap.world_to_map(collision.position - collision.normal - Vector2(1, 0))
var tile_id = tilemap.get_cellv(cell)

Btw: If you click the three dots in the lower right corner of an answer, you can choose "Ask related question". This will increase your chances of getting an answer, as it appears as an unanswered, recently asked question on this site whereas asking in a comment will usually get lost when the original author of the answer (in this case: me) isn't active anymore or chose to be not informed of new comments via e-mail.

Thanks, I didn't know about "Ask related question". I did re-ask this question here:

https://godotengine.org/qa/82264/detect-the-tile-when-kinematicbody2d-collides-with-tilemap

Wouldn't subtracting 1 cause it to detect the wrong tile if the collision occurs on the left side of a tile?

EDIT: Ah I see. Yeah, I think you are right, subtracting 1 might just do the trick. Thanks!

Seems to work as expected but I'm also getting a deallocated value (the second index) when colliding on the left side... If I subtract it by Vector2(1, 0) nothing changes, if I subtract it by Vector2(0, 1) it corrects the value on the left collision but breaks the right one. Any idea how I should proceed?

Edit:
So, I've been able to make it work by manually checking the values ​​of the coll.normal and subtracting the cell position as needed:

    var cell = tilemap.world_to_map(coll.position)
    if coll.normal.x == 1: cell -= Vector2(1, 1)
    if coll.normal.y == -1: cell -= Vector2(1, 0)
    if coll.normal.y == 1: cell -= Vector2(0, 1)

I'm sure there is a one-line version of this, but I have no idea how to do it, anyway, ty!

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.

Categories