---
title: "Navmesh pathfinding in C#"
url: https://recursiveai.org/onepath/cookbook/navmesh-queries/
description: "Navmesh pathfinding in C#: build a navigation mesh from a tile map, find a smooth path around obstacles, raycast, snap clicks to walkable ground, and save the mesh. Runnable code, with OnePath."
version: "1.1.0"
---

# Navmesh pathfinding in C#

Build a navigation mesh once with `NavMesh.Build2D` and query it for paths, raycasts and the nearest walkable point. Paths on a navmesh are smooth corner-to-corner lines that keep the agent’s radius clear of walls. The program below builds a mesh from a 30 × 20 tile map with a block in the middle.

```shell
dotnet new console -o NavMesh && cd NavMesh
dotnet add package RecursiveAI.OnePath --version 1.1.0
```

Put this in `Program.cs` and run `dotnet run`:

```csharp
using RecursiveAI.OneMath;
using RecursiveAI.OnePath;

// A 30 x 20 tile map, one unit per cell, with a block in the middle.
var map = Grid.FromText("""
    ..............................
    ..............................
    ..............................
    ..............................
    ..............................
    ..............................
    ...........########...........
    ...........########...........
    ...........########...........
    ...........########...........
    ...........########...........
    ...........########...........
    ..............................
    ..............................
    ..............................
    ..............................
    ..............................
    ..............................
    ..............................
    ..............................
    """);
NavMesh mesh = NavMesh.Build2D(map);

PathResult route = mesh.FindPath(new Vector3D(2, 0, 9), new Vector3D(28, 0, 9));
Console.WriteLine($"path: length {route.Length:F4}, {route.Points.Count} points");
foreach (var p in route.Points) Console.WriteLine($"  {p.Position}");

// Line of sight along the mesh: does a straight walk hit the block?
bool hit = mesh.Raycast(new Vector3D(2, 0, 9), new Vector3D(28, 0, 9), out NavMeshHit info);
Console.WriteLine($"raycast hit: {hit}, at {info.Position}");

// Snap a click to the nearest walkable point.
if (mesh.TrySamplePosition(new Vector3D(15, 0, 9), 5, out Vector3D snapped))
    Console.WriteLine($"nearest walkable to (15, 0, 9): {snapped}");

// Bake once, ship the bytes, load at run time (1.1.0).
byte[] baked = mesh.Save();
NavMesh loaded = NavMesh.Load(baked);
PathResult again = loaded.FindPath(new Vector3D(2, 0, 9), new Vector3D(28, 0, 9));
Console.WriteLine($"saved {baked.Length} bytes; loaded mesh gives the same length: {again.Length == route.Length}");
```

```text
path: length 27.4165, 4 points
  (2, 0, 9)
  (10.79296875, 0, 12.599609375)
  (19.20703125, 0, 12.599609375)
  (28, 0, 9)
raycast hit: True, at (10.400390625, 0, 9)
nearest walkable to (15, 0, 9): (15, 0, 5.400390625)
saved 793 bytes; loaded mesh gives the same length: True
```

## What happened

- **`NavMesh.Build2D(grid)`** treats every non-wall cell as walkable and uses `Grid.Space` for cell size and origin (one unit per cell here). The 2D builder is exact: it shrinks the walkable area by the agent radius from `NavSettings` (0.6 for the default `Humanoid`), which is why the path keeps clear of the block’s corners.
- **`FindPath`** returns the string-pulled path: the start, one point past each corner it has to round, and the goal.
- **`Raycast`** walks the mesh surface in a straight line and reports where it leaves the walkable area, here at the eroded edge of the block.
- **`TrySamplePosition`** returns the nearest walkable point within the distance you give; a click inside the block snaps to its edge.
- **`Save` and `Load`** (new in 1.1.0) bake a mesh once and ship the bytes. The loaded mesh gives the same bits from every query, and every later 1.x release reads them.

## Limits

- **The agent radius is built in.** The mesh is shrunk by the radius in `NavSettings` when it is built, so agents of very different sizes need a mesh each.
- **`Build2D` takes tile maps.** For level geometry, use the 3D builder below.

The full query layer, with filters, area costs, random points, distance to walls and sliced queries, is `NavMeshQuery`: see [navmesh queries](https://recursiveai.org/onepath/docs/navmesh-queries/). For level geometry, `NavMesh.Build` (triangles) and `NavMesh.BuildGltf` run the 3D builder into the same format.
