Build a navigation mesh from a tile map with OnePath, then find a path around an obstacle, cast a ray along the mesh, snap a point to the nearest walkable spot, and save the mesh to bytes to load at run time.
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}");
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 usesGrid.Spacefor cell size and origin (one unit per cell here). The 2D builder is exact: it shrinks the walkable area by the agent radius fromNavSettings(0.6 for the defaultHumanoid), which is why the path keeps clear of the block’s corners.FindPathreturns the string-pulled path: the start, one point past each corner it has to round, and the goal.Raycastwalks the mesh surface in a straight line and reports where it leaves the walkable area, here at the eroded edge of the block.TrySamplePositionreturns the nearest walkable point within the distance you give; a click inside the block snaps to its edge.SaveandLoad(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.
The full query layer, with filters, area costs, random points, distance to walls and sliced queries, is NavMeshQuery: see navmesh queries. For level geometry, NavMesh.Build (triangles) and NavMesh.BuildGltf run the 3D builder into the same format.