How can I get a list of Tiles (and subtiles) overlapping an Area2D?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By DapperDestral

Ok, so I’m trying to get the player to detect if it’s overlapping specific tile types.

Normally you do it like this:

Area2D-chan getting a list of overlapping tiles, then telling Player-senpai what they are.

Unfortunately there seems to be no direct way to do that in Godot. You can’t get a list of tiles that overlap an Area2D, unless I missed something.

However, there are two stupid ways to do this;

  • Separate each tile type (spikes, water, lava, icecream) into its own tilemap and test for that (dumb extra busywork, but it works).

  • Separate each tile type into its own Area2D and add CollisionShape2Ds for each region they cover (ew, why would you do that?) and test for those.

I guess what I’m asking is if there’s any non-dumb alternatives that I overlooked?

did you solve the problem?

ramazan | 2022-01-19 09:39

1 Like
:bust_in_silhouette: Reply From: russmatney

Excellent diagram, it really explains the path - I wanted this behavior as well, and ended up writing code to get a list of tiles overlapping an Area2D. Right now this only covers rectangles, but it could be extended to other shapes.

class_name TileDetector
extends Area2D

func _on_TileDetector_body_entered(body):
  if body is TileMap:
    if body.get_parent().has_method("add_active_body"):
      body.get_parent().add_active_body(self)

# bug/strange behavior:
# area2ds fire body_exited when tilemap set_cell is called
# https://github.com/godotengine/godot/issues/61220
# func _on_TileDetector_body_exited(body:Node):
#   if body is TileMap:
#     if body.get_parent().has_method("remove_active_body"):
#       body.get_parent().remove_active_body(self)

# Returns an array of vector2s (the overlapping tilemap cell coords)
func overlapping_cells(tm: TileMap) -> Array:
  # TODO may need to support more shapes here
  var extents = $CollisionShape2D.shape.extents
  var first_cell = tm.world_to_map(global_position - extents)
  var last_cell = tm.world_to_map(global_position + extents)
  var cells = []
  for x in range(first_cell.x, last_cell.x + 1):
    for y in range(first_cell.y, last_cell.y + 1):
      cells.append(Vector2(x, y))
  return cells

I then hold a reference to the area2d on the tilemap, and call this from the tilemap’s _process method… not ideal, but works for my use-case - maybe there’s some better way to do this?

1 Like