Hi all!
For my game, I have a TileMapLayer
which is composed of Scene sourced tiles. I wanted to take advantage of Godot’s pathfinding in order to allow agents to make their way across any map I created that contains colliders. The following is based on Godot 4.4.1.
I wanted to assist anybody else who might have a similar problem; so here is my annotated code that generates a NavigationRegion2D
based on a given TileMapLayer
. I hope it is helpful!
The tree looks like:
- Map
- TileMapLayer
The following methods are defined in my Map
class which derives from Node2D
:
public override void _Ready()
{
// We call this deferred to allow the physics engine to do anything required before we begin creating the navigation polygon
CallDeferred(nameof(GenerateNavigationRegion));
}
private void GenerateNavigationRegion()
{
// Grab the tile map layer, this should contain all colliders you want to consider in the polygon during baking
var tileMapLayer = GetNode<TileMapLayer>("TileMapLayer");
// Calculate the world space rect, covering the tile map area
var usedRect = tileMapLayer.GetUsedRect();
// NOTE: The line below assumes equal dimensions on tile sizes!
var tileSize = tileMapLayer.TileSet.TileSize.X;
var usedWorldSpace = new Rect2(
usedRect.Position.X * tileSize,
usedRect.Position.Y * tileSize,
usedRect.Size.X * tileSize,
usedRect.Size.Y * tileSize);
// Create the polygon and add an outline which represents the world space rect we previously computed
var navigationPolygon = new NavigationPolygon();
navigationPolygon.AddOutline(new Vector2[]
{
new(usedWorldSpace.Position.X, usedWorldSpace.Position.Y),
new(usedWorldSpace.Position.X, usedWorldSpace.End.Y),
new(usedWorldSpace.End.X, usedWorldSpace.End.Y),
new(usedWorldSpace.End.X, usedWorldSpace.Position.Y)
});
// Set the agent radius, this is used to "cull" the edges of the polygon during baking
// Ultimately, it is to prevent agents from getting stuck on walls.
navigationPolygon.AgentRadius = 14;
// Here we use RootNodeChildren, as we plan to scan the Tile Map Layer's children when baking the polygon
navigationPolygon.SourceGeometryMode = NavigationPolygon.SourceGeometryModeEnum.RootNodeChildren;
// Create a new NavigationRegion2D node and add it as a child
var navigationRegion2d = new NavigationRegion2D { Name = "NavigationRegion2D" };
AddChild(navigationRegion2d);
// This object is a container for the geometry data which will be used to bake the polygon. It is empty for now...
var navigationMeshSourceGeometryData2d = new NavigationMeshSourceGeometryData2D();
// This function will populate the contents of the source geometry data we just created.
NavigationServer2D.ParseSourceGeometryData(navigationPolygon, navigationMeshSourceGeometryData2d, tileMapLayer, Callable.From(() =>
{
// This part of the code is hit when the parse is complete, now we use the parsed geometry data to bake the polygon
NavigationServer2D.BakeFromSourceGeometryData(navigationPolygon, navigationMeshSourceGeometryData2d, Callable.From(() =>
{
// Now the polygon is baked, we assign the navigation region's polygon to the newly baked polygon. All done!
navigationRegion2d.NavigationPolygon = navigationPolygon;
}));
}));
}
Tips for debugging:
- You can enable visualization of navigation polygons under Debug > Visible Navigation
- You can enable visualization of paths that agents are using on the
NavigationAgent2D
node: Debug > Enabled.
Thanks to the Godot devs for making such a powerful engine!