NavigationAgent2D multiple tilemaplayers

Godot Version

4.5.1

Question

How can I get my multiple tilemaplayers to sort of “combine” so my enemy using a navigationagent2d can move properly instead of making paths that go through walls and trying to follow them

I’ve tried things like this:

extends TileMapLayer

var _obstacles: Array[TileMapLayer] = []

func _ready() -> void:
	_get_obstacle_layers()

func _get_obstacle_layers():
	# make sure the name here is the same as the group's
	var layers = get_tree().get_nodes_in_group("Obstacles")

	for layer in layers:
		if layer is not TileMapLayer: continue
		_obstacles.append(layer)
		
func _use_tile_data_runtime_update(coords: Vector2i) -> bool:
	return _is_used_by_obstacle(coords)
	
func _is_used_by_obstacle(coords: Vector2i) -> bool:
	for layer in _obstacles:
		if coords in layer.get_used_cells():
			var is_obstacle = layer.get_cell_tile_data(coords).get_collision_polygons_count(0) > 0
			if is_obstacle:
				return true
	return false
	
func _tile_data_runtime_update(coords: Vector2i, tile_data: TileData) -> void:
	if not _is_used_by_obstacle(coords):
		tile_data.set_navigation_polygon(0, null)

but it doesnt work I dont even get an error
i’ve tried following Pathfinding Guide for 2D Top-View Tiles in Godot 4.3 - casraf.dev but it doesnt seem to work in godot 4.5 or atleast I couldnt get it to.

My Scene looks a little like this, this is the tilemaplayers I need to “combine” or make pathbindable

Also the player can build on a grid (so they can place tiles on the BuildingLayer tilemaplayer node, so I need the Floor layer to update and remove tiles/add tiles in realtime to respond to that

thanks in advance for the help!

1 Like

Godot Version

4.6.1

Sorry for editing so much, it’s my first time here. I don’t understand much English, I used Google Translate so there may be errors.

I managed to do it this way, using AI and some manual corrections. There might be easier ways to do it, but this one at least works.

extends CharacterBody2D
class_name PlayerWithAStarClickToMove

@export var ground_layer: TileMapLayer
@export var obstacle_layer: TileMapLayer
@export var speed: float = 130.0
@export var allow_diagonals: bool = false

@onready var path_line: Line2D = $"../PathLine"

const TILE_SIZE: int = 16
const CENTER_OFFSET: Vector2 = Vector2(8, 8)

var astar_grid: AStarGrid2D = null

var current_path: PackedVector2Array = []
var path_index: int = 0


func _ready() -> void:
	if not ground_layer or not obstacle_layer:
		push_error("Player: Defina ground_layer e obstacle_layer no Inspector!")
		return
	
	update_astar_grid()
	snap_to_center()


func update_astar_grid() -> void:
	if not ground_layer or not ground_layer.tile_set:
		push_error("Player: ground_layer inválido!")
		return
	
	astar_grid = AStarGrid2D.new()
	
	var map_rect: Rect2i = ground_layer.get_used_rect()
	astar_grid.region = map_rect
	astar_grid.cell_size = Vector2(TILE_SIZE, TILE_SIZE)
	astar_grid.offset = CENTER_OFFSET
	
	astar_grid.default_compute_heuristic = AStarGrid2D.HEURISTIC_MANHATTAN
	astar_grid.default_estimate_heuristic = AStarGrid2D.HEURISTIC_MANHATTAN
	
	astar_grid.diagonal_mode = (
		AStarGrid2D.DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES
		if allow_diagonals
		else AStarGrid2D.DIAGONAL_MODE_NEVER
	)
	
	astar_grid.update()
	
	for x in range(map_rect.position.x, map_rect.end.x):
		for y in range(map_rect.position.y, map_rect.end.y):
			var cell := Vector2i(x, y)
			if not _is_cell_walkable(cell):
				astar_grid.set_point_solid(cell)
	
	print("A* Grid atualizado | Região: ", map_rect, " | Diagonais: ", allow_diagonals)


func _is_cell_walkable(cell: Vector2i) -> bool:
	var ground_data: TileData = ground_layer.get_cell_tile_data(cell)
	if ground_data == null:
		return false
	
	var obstacle_data: TileData = obstacle_layer.get_cell_tile_data(cell)
	if obstacle_data != null and obstacle_data.get_collision_polygons_count(0) > 0:
		return false
	
	return true


func _input(event: InputEvent) -> void:
	if event is InputEventMouseButton:
		if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
			var click_global = get_global_mouse_position()
			move_to_position(click_global)


func move_to_position(target_global: Vector2) -> void:
	if not astar_grid:
		return

	var start_cell := ground_layer.local_to_map(ground_layer.to_local(global_position))
	var target_cell := ground_layer.local_to_map(ground_layer.to_local(target_global))

	if not astar_grid.is_in_boundsv(start_cell) or not astar_grid.is_in_boundsv(target_cell):
		path_line.clear_points()
		return

	if astar_grid.is_point_solid(target_cell):
		path_line.clear_points()
		return

	var local_path := astar_grid.get_point_path(start_cell, target_cell)

	current_path.clear()
	for local_pos in local_path:
		var global_pos = ground_layer.to_global(local_pos)
		current_path.append(global_pos)

	path_index = 1
	update_path_visual()


func update_path_visual() -> void:
	if path_line == null:
		return

	path_line.clear_points()

	if current_path.is_empty():
		return

	for point in current_path:
		path_line.add_point(point)


func snap_to_center() -> void:
	var current_cell = ground_layer.local_to_map(ground_layer.to_local(global_position))
	var center_local = ground_layer.map_to_local(current_cell) + CENTER_OFFSET
	global_position = ground_layer.to_global(center_local)


func _physics_process(delta: float) -> void:
	if current_path.is_empty() or path_index >= current_path.size():
		velocity = Vector2.ZERO
		update_path_visual()
		return

	var target_point = current_path[path_index]
	var direction = (target_point - global_position).normalized()

	velocity = direction * speed

	move_and_slide()

	var distance_to_target = global_position.distance_to(target_point)
	if distance_to_target < 4.0:
		path_index += 1
		update_path_visual()

		if path_index >= current_path.size():
			current_path.clear()
			velocity = Vector2.ZERO
			path_line.clear_points()

	if path_index >= current_path.size():
		current_path.clear()
		velocity = Vector2.ZERO
		if not current_path.is_empty():
			global_position = current_path[-1]
		if path_line:
			path_line.clear_points()

This is how my nodes are organized

Ground = Navigation layers

Wall = Physics Layers