Help with Tile-based navigation & room generation

Godot Version

4.4

Question

I’m making a top-down dungeon-crawler sort of game, and I’m trying to set up every room with a navigation layer for enemy AI.

My current solution has the proper tiles in the TileSet painted with Navgation layer 0, then I use the TileMapLayer runtime_update to remove the navigation polygon from any tiles that overlap with a collision object:

func _use_tile_data_runtime_update(coords: Vector2i) -> bool:
	return true

func _tile_data_runtime_update(coords: Vector2i, tile_data: TileData) -> void:
	# Automatically set every collision tile as non-navigable (set collision polygon to null)
	var navpoly = tile_data.get_navigation_polygon(0)
	if _is_used_by_obstacle(coords):
		tile_data.set_navigation_polygon(0, null)

_is_used_by_obstacle() works fine, and is true for any coords that overlap collision objects. The issue is when I generate multiple rooms with this script, there’s nothing to bring back the navigation layer once I clear the polygon. This usually results in my levels looking like this:

Where despite the second room having no collision objects, it still has gaps in the navigation mesh that were made by the previous room’s fences. I imagine I need an else in _tile_data_runtime_update to restore the original NavigationPolygon of the TileData if it’s not overlapping with a collision object, but so far I’ve been unable to make that work.

I feel like this has to be a pretty common issue but I’m not sure the best way to go about it, and haven’t been able to make even a hacky solution work - any tips would be really appreciated :folded_hands:

It looks like a shared resource issue. You want to make sure that each level has its own NavigationPolygon. You can’t reuse them for every level unless you recalculate them at runtime. If you want to go that route:

signal baking_complete


func _tile_data_runtime_update(coords: Vector2i, tile_data: TileData) -> void:
	navpoly.bake_navigation_polygon()
	await baking_complete
	# Automatically set every collision tile as non-navigable (set collision polygon to null)
	var navpoly = tile_data.get_navigation_polygon(0)
	if _is_used_by_obstacle(coords):
		tile_data.set_navigation_polygon(0, null)


func await_bake() -> void:
	 while is_baking == true:
		continue
	baking_complete.emit()