| Parameter | Description |
|---|---|
| job | The job to complete before the NavMesh world changes in any way. |
Tells the NavMesh world to halt any changes until the specified job is completed.
When jobs process NavWorld operations, it is essential that the NavMesh data doesn't change. Every time you schedule a job that contains NavWorld operations, call this method to pass the job's JobHandle to the NavWorld. Otherwise, in the Editor, Unity logs an error and forces the job to complete before it allows the NavMesh data to change.
Additional resources: IJob, IJobFor
using Unity.Collections; using Unity.Jobs; using UnityEngine; using Unity.AI.Navigation.LowLevel; public class NavWorldDependencyExample : MonoBehaviour { public Transform target; struct MapPositionJob : IJob { public NavWorld world; public Vector3 position; public NativeArray<NavLocation> result; public void Execute() { result[0] = world.MapLocation(position, Vector3.one, 0); } } void Update() { using NavWorld world = NavWorld.GetDefaultWorld(); NativeArray<NavLocation> result = new NativeArray<NavLocation>(1, Allocator.TempJob); JobHandle handle = new MapPositionJob { world = world, position = target.position, result = result }.Schedule(); // Prevent the NavMesh from being modified while the job runs. Call this from the main // thread only; AddDependency throws when it is called from inside a job. world.AddDependency(handle); // Complete the job before the world and the result buffer go out of scope. handle.Complete(); result.Dispose(); } }
using Unity.Collections; using Unity.Jobs; using UnityEngine; using Unity.AI.Navigation.LowLevel; public class FindPathJobExample : MonoBehaviour { // The node pool limits how many nodes a search can visit, and therefore also the // largest path it can return. const int k_MaxNodesToVisit = 1024; public Transform target; NavWorld m_World; NavQueryBuffer m_Buffer; NativeArray<int> m_PathSize; JobHandle m_SearchJob; bool m_JobScheduled; // Runs the search, but does not retrieve the path. The number of nodes in the result // is only known once the search has finished, so it is reported back to the caller. struct SearchPathJob : IJob { [ReadOnly] public NavWorld world; public NavQueryBuffer buffer; public NavLocation start; public NavLocation end; // Element 0 receives the number of nodes the path is made of. public NativeArray<int> pathSize; public void Execute() { pathSize[0] = 0; NavQueryStatus status = world.BeginFindPath(buffer, start, end); while ((status & NavQueryStatus.InProgress) != 0) status = world.ContinueFindPath(buffer, 64, out int _); if ((status & NavQueryStatus.Success) == 0) return; status = world.EndFindPath(buffer, out int pathSizeFound); if ((status & NavQueryStatus.Success) == 0) return; pathSize[0] = pathSizeFound; } } // Copies the path that the search left in the query buffer. Keeping this separate means // the destination array can be allocated to the exact size the search reported, so no // node is ever dropped. struct ReadPathJob : IJob { [ReadOnly] public NavWorld world; public NavQueryBuffer buffer; public NativeArray<NavNode> path; public void Execute() { world.GetResultFromFindPath(buffer, path); } } void OnEnable() { m_World = NavWorld.GetDefaultWorld(); m_Buffer = new NavQueryBuffer(m_World, Allocator.Persistent, k_MaxNodesToVisit); m_PathSize = new NativeArray<int>(1, Allocator.Persistent); } void Update() { NavLocation start = m_World.MapLocation(transform.position, 5f *Vector3.one, 0); NavLocation end = m_World.MapLocation(target.position, 5f * Vector3.one, 0); if (!m_World.IsValid(start) || !m_World.IsValid(end)) return; m_SearchJob = new SearchPathJob { world = m_World, buffer = m_Buffer, start = start, end = end, pathSize = m_PathSize }.Schedule(); // Register the job so that nothing modifies the NavMesh while // the search runs. Call this from the main thread only, because // AddDependency throws when called from inside a job. m_World.AddDependency(m_SearchJob); m_JobScheduled = true; } void LateUpdate() { if (!m_JobScheduled) return; // Complete the search only where the result is needed, so that it overlaps with the // rest of the frame instead of blocking the main thread right after scheduling it. m_SearchJob.Complete(); m_JobScheduled = false; int pathSize = m_PathSize[0]; if (pathSize == 0) return; // EndFindPath reported how many nodes the path contains, so this array fits it exactly. NativeArray<NavNode> path = new NativeArray<NavNode>(pathSize, Allocator.TempJob); // EndFindPath leaves the result in the query buffer, so a later job can still copy it // out. Both jobs use the same buffer, so this one must never run at the same time as // the search. Retrieving the path is only a copy, which is why completing it right // away costs little compared to the search itself. JobHandle readJob = new ReadPathJob { world = m_World, buffer = m_Buffer, path = path }.Schedule(); m_World.AddDependency(readJob); readJob.Complete(); // The path is a corridor of nodes, not a list of waypoints. Draw the gate that each // pair of consecutive nodes shares to see the corridor the agent can move through. for (int i = 0; i < path.Length - 1; i++) { if (m_World.GetPortalPoints(path[i], path[i + 1], out Vector3 left, out Vector3 right)) Debug.DrawLine(left, right, Color.magenta); } path.Dispose(); } void OnDisable() { // Never dispose a container, or the world, while a job that uses it is still running. m_SearchJob.Complete(); m_PathSize.Dispose(); m_Buffer.Dispose(); m_World.Dispose(); } }