Godot Version
4.6.2
Question
I’m creating a platformer and using TileMap Layers for the stage collision. I want to implement custom tile data so that when the player character collides with certain tiles, I can cause different types of behaviors to happen (alter movement speed, damage, etc.)
Most tutorials on the matter still use Tilemaps (which I thought were deprecated) and use the body_shape_entered signal to get the tilemap and tilemap RID in order to locate the specific cell and then check it’s tile data. I thought I was doing this right, but the body_rid seems to be getting the position of the origin point of the tilemap layer, and not the coordinate of the cell on the tilemap layer being collided with.
Not sure what I’m doing wrong or even if the tilemap layer can be used for this method. Or maybe I should rely on another way of getting the RID? Any help would be appreciated.
func _on_body_shape_entered(body_rid: RID, body: Node2D, _body_shape_index: int, _local_shape_index: int) -> void:
if body is TileMapLayer:
var tile_cord = body.get_coords_for_body_rid(body_rid)
TileMapLayer has some functions to help with this. local_to_map() converts a body’s Vector2 position to the tile coordinate Vector2i, map_to_local() converts to tile cell coordinates back to body positions. get_cell_data() can retrieve the custom data associated with each tile/cell. The help file at https://docs.godotengine.org/en/4.6/classes/class_tilemaplayer.html has some notes about making sure your body positions are either local to the tilemaplayer origin or they are global coordinates.
In order for get_coords_for_body_rid() to fetch you the exact tile, you need to set TileMapLayer’s physics_quadrant_size property to 1
Otherwise, multiple tile shapes will be packed inside a quadrant body and the coordinate returned by get_coords_for_body_rid() will be the quadrant origin which will be the same for all cells in the quadrant. This is done for optimization purposes.
However, with a bit of workaround, you can still fetch the exact tile even if quadrant size is set to a value larger than 1, assuming that at least one point in tile’s collision polygon is inside cell boundaries.
To do so get the shape rid from physics server using PhysicsServer::body_get_shape() and then pass that rid to PhysicsServer::shape_get_data() to retrieve the collision polygon points array. Transform one of those points to map space using TileMapLayer::local_to_map(), and there’s your cell coordinate.
Thank you both!
I tried reducing the physics cell size to 1 and leaving it at the default using the PhysicsServer2D method.
func _on_body_shape_entered(body_rid: RID, body: Node2D, _body_shape_index: int, _local_shape_index: int) -> void:
if body is TileMapLayer:
var rid = PhysicsServer2D.body_get_shape(body_rid, _body_shape_index)
var pos = PhysicsServer2D.shape_get_data(rid)[0]
var coord = body.local_to_map(pos)
The above code gets me a coord, but it’s a little off (like up and to the left away from the collision point). When I use the above and reduce the Physics Quadrant to 1 it returns (0, 0)
Using this code, gets me the correct cell, but because it’s not the collision point, it returns the tile where the hitbox is and not the tile coord it’s colliding with:
func _on_body_shape_entered(body_rid: RID, body: Node2D, _body_shape_index: int, _local_shape_index: int) -> void:
if body is TileMapLayer:
var pos = body.to_local(_hitbox.global_position)
var coord = body.local_to_map(pos)
Well it’s a cell coordinate. Areas don’t have “collision points”.
And yes, local_to_map(), as the name suggests, converts local coordinate in tilemap space to the cell coordinate. It will work by itself if the tilemap node itself has identity transforms (no translation, rotation or scale). Otherwise you need to first transform the coordinate into local space.
I think I need to figure out a different method to find the point where the tile is locally then. I thought the RID was the point where the body shapes interacted which is then ultimately converted into the coord but that’s either not the case, it’s too inaccurate for my needs or I’m converting it incorrectly.
Not sure I understand what exactly you need. Your original question was about how to get cell coordinates of the cell that entered an area, triggering body_shape_entered signal. We answered that. The coordinate attained this way will represent a cell, not an actual position. If that’s not what you need, describe more precisely what you’re trying to do and show your colliders.
Hopefully this makes sense.
What I’m trying to do is get the custom tile data for whatever the player has collided with using this Area2D.
The tile data would probably be an int or bool from certain tiles that the player object reads and then changes state depending on the tile data. I can figure this part out once I figure out how to get the correct tile.
My thought process was to get the tile coord from the collision point between the Area2D and the Tilemap Layer’s collision shape using a body_shape_entered signal, then get the tile data from the coord.
Assuming the player hits the red marked tile, that’s the coord I want to get, but so far I have only been able to get the coords of the tiles marked green. The green marked tile is using the hitbox’s global position, the other green marked tiles (approximately) are the ones I’m getting when using the RID method.
Forget areas, rids etc… Describe what you’re trying to do from the player perspective. What does the tile you want to get represents in the gameplay context?