My question here is the basically the title. I have a rigidbody, that will detect if it collides with something, and if it collides with the tilemap it will perform a specific operation on the tile it collides with. The problem is that all the rigidbody collision functions just return the tilemap node itself. Is there anyway to change the position of or delete a tile in a tilemap when a rigidbody collides with it? It might also be possible to add a script to the tilemap that checks if the rigidbody collides with each tile, but that seems more complicated.

if you want to detect what tile in a TileMap a RigidBody2D collides with, you can use the get_colliding_bodies() method in your RigidBody2D script. This returns a list of all RigidBody2D nodes that are currently colliding with the body.

Once you have this list, you can iterate over it and check if the collision is with a TileMap node by using the is_in_group() method and passing in the "tile_map" group.

Next, you can get the position of the collision point in tile coordinates by using the map_to_world() method on the TileMap node and then the world_to_map() method to convert from world to tile coordinates.

Finally, you can change the tile at the colliding position by using the set_cell() method on the TileMap node.

Gdscript;

     func _on_RigidBody2D_body_entered(body):
    if body.is_in_group("tile_map"):
        # Get the TileMap node
        tile_map = body.get_node("/root/TileMap")
        # Get the collision point in world coordinates
        collision_point = body.get_collision_point(0)
        # Convert collision point to tile coordinates
        tile_coordinates = tile_map.world_to_map(collision_point)
        # Change the tile
        tile_map.set_cell(tile_coordinates.x, tile_coordinates.y, 0) 

Ofcourse you have to go to the tiles and in ready() write;

func ready():
add_to_group("tile_map")

alternatively you can use "get_collision_mask" function of the TileMap node. This function returns a collision mask that can be used to find the specific tile that the rigidbody collided with.

this is gonna be attached to the rigidbody:

func _on_Area2D_body_entered(body):
    if body.get_parent().get_parent().get_name() == "TileMap":
        tile_map = body.get_parent().get_parent()
        mask = tile_map.get_collision_mask(body.get_position())
        # Check if the mask corresponds to a valid tile index
        if mask > 0:
            # Get the tile coordinates in the tile map
            tile_x, tile_y = tile_map.world_to_map(body.get_position())
            # Change the tile or perform other actions
            # ...

Note that in this example, the assumption is made that the TileMap node is a direct parent of the Area2D node attached to the rigidbody. You may need to modify the code to match the specific hierarchy of your scene.

Hope this helps

    funnylittle get_collision_point isn't a function on body. Is there another variable I should call it on? (I connected a signal manually using the connect() function and there is an error that says that get_collision_point isn't a function on body.) Just so you know for a little extra context, this is a particle that destroys tiles in a tilemap if it touches one. It's a rigidbody scene that gets instanced multiple times and this is the script attached to it.

    9 months later

    I'm trying to do something very similar (although in Godot 4) and can't figure this out. Did you ever find a solution for this? Would love to be able to identify which exact tile my RigidBody2D bullet collides with! Thanks in advance 🙂

    3 months later

    In case someone else stumbles upon this looking for answers, here's how I finally managed it! First off, make sure the RigidBody2D's contact_monitor to true and max_contacts_reported to some good number. I used 16 in my case. Yes, I pulled that out of my butt, can't motivate it at all.

    Now, we'll be doing all the checking inside the _integrate_forces method, and since it's possible to collide with several tiles at ones and with Contact Monitoring on we'll not only see the things we collide with, but everything we touch, so we have to do some loopdidoos and have some way of checking that the tile cell we find is something we're interested in. For me, my atlas has 2 tiles, so this is very simple. And when the RigidBody2D collides with a particular type of tile cell, it sets it to the other. So a very simple case. Anyway, here's how I did it:

    func _integrate_forces(state: PhysicsDirectBodyState2D) -> void:
            if state.get_contact_count() > 0:
                    for i in state.get_contact_count():
                            if state.get_contact_collider_object(i) is TileMap:
                                    var tilemap: TileMap =\
                                                    state.get_contact_collider_object(i) as TileMap
                                    # even though the method name includes "local position"
                                    # it appears it is a global position?? (this breaks if
                                    # we try to convert it to global first)
                                    var tile_local_pos: Vector2 =\
                                                    tilemap.to_local(state.get_contact_local_position(i))
                                    # get the tile cell coordinates (or indices I guess you can think
                                    # of them?)
                                    var tile_pos: Vector2i =\
                                                    tilemap.local_to_map(tile_local_pos)
                                    # sometimes we'll get coordinates that is outside the tilemap,
                                    # so here's an ugly solution for that..
                                    if tile_pos.x < 0 or tile_pos.x > 15 or\
                                                    tile_pos.y < 0 or tile_pos.y > 15:
                                                            return
    
                                    # we'll need the source id to change the cell
                                    var tile_source_id =\
                                                    tilemap.get_cell_source_id(0, tile_pos)
                                    var tile_atlas_coord =\
                                                    tilemap.get_cell_atlas_coords(0, tile_pos)
    
                                    if tile_atlas_coord != friendly_atlas_coord:
                                            tilemap.set_cell(
                                                    0,
                                                    tile_pos,
                                                    tile_source_id,
                                                    friendly_atlas_coord)

    Hopefully it can help someone else figure out a solution for them! Oh.. And I just realized you can skip the top-level if-statement, this wasn't a loop at first which is why that check is there.. :3

    a month later

    A bit late, but for anyone wondering the hidden ancient secret of detecting a collision with a specific tile and not the tilemap, setup the _body_shape_entered signal and

    func _on_event_area_2d_body_shape_entered(body_rid, body, body_shape_index, local_shape_index):
        var coords = tilemap.get_coords_for_body_rid(body_rid)
        #now do whatever u want with it lol
        #In layer 2, I'm changing the "sprite" of the tile on coords (which is the one I've collided with) for the one I have on 10, 2 on my tileset/atlas of source id 1
        tilemap.set_cell(2, coords, 1, Vector2i(10, 2))
      8 months later