Navigation Tilemap runtime baking

Godot Version

4.2.1 Stable


I’m trying to wrap my head around what I need to do to get dynamic updates to work with a NavigationRegion. I have a simple strategy game with buildings that I place down. I’m using a tilemap for the layers, and originally, I went with the navigation layer which did provide the basic functions to allow me to use the agent and pathfinding. It was great, until I tried to find a way to update the pathing when I put down structures. Avoidance sort of works, but is not really what I need here. I eventually found articles pointing me away from the tilemap navigation and towards the NavigationServer2D and NavigationRegion2D.

I got that mostly figured out and is working well, including using the tilemap physics layer to create holes/obstacles in the region. I found out how to use NavigationMeshSourceGeometryData2D and rebake the region from the geometry data to update it with any cut-outs I do via add_obstruction_outline. However, when I rebake and add those obstructions, my old data is lost. I would like to retain the original tilemap baked polygons and then cut into those only as needed when I place buildings.

I see how I could iterate through the tiles and create a full bake of everything each time, but that seemed inefficient when compared to a much more simple polygon that had already been produced during the initial tilemap bake.

Can anyone assist me with figuring out how to have a tilemap and buildings that cut out navigation meshes work together in the best way? I’d be grateful.

1 Like

Well, since you asked for “the best way” and not the most pretty or easy …

The best way is to partition your game world into axis aligned and same-sized chunks with Rect2i. Have each chunk be a region with its own source geometry.

Use the Godot 4.3 beta4 when it shows up or build from master and use the new NavigationPolygon bake rect and border size. It is PR Add NavigationPolygon `border_size` property for tile baking by smix8 · Pull Request #87961 · godotengine/godot · GitHub that added this. Set them so that each chunk bakes navigation mesh just for its Rect while also considering enough source geometry from its neighbors so that the baked navigation mesh edges can match the neighbor chunks edges in order to be merged.

Disable the edge connection margin on the navigation map, it has a super-high cost and if you setup everything correct it will not be needed at all to merge your navigation mesh edges.

For each chunk only add the relevant source geometry for the chunk and partial geometry from its neighbors. E.g. you can grow a Rect2 by the required size and check if overlaps with any of your tiles before you add them.

Keep a copy of the chunk source geometry for the static geometry that never changes. Everytime you drop dynamic stuff on the chunk duplicate this resources, add your changes to it and bake.

This altogether is the most performant way you can do runtime changes at the moment, apart from not using a TileMap as the source geometry in the first place.

if you want to base the next bake on a more optimized navigation mesh that was already baked you can iterate over the baked navigation mesh polygons and add those simpler polygons to the source geometry.

A NavigationPolygon is a mesh with indices for convex polygons. So get the polygon_count and loop over all the polygons by using get_polygon(). The array that this function returns are the indices that make the polygon. Use the indices on the vertices array to get the Vector2 positions.

If you want to optimize even further instead of adding each polygon only add the outlines of the entire navigation mesh. There is no build-in function that does this. For this you need to map the navigation mesh edges to find the outline edges, aka those with no pair. Sort them to a line with the correct winding order and add them to the source geometry. The downside of doing this is that you might have no consistency over how the mesh will look when rebaked because all the internal edge information is missing.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.