Generate a NavigationRegion2D for a given TileMapLayer

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!

1 Like