Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

NavMesh Baking at Runtime Example

Discussion in 'Navigation' started by kenlem1, Dec 4, 2017.

  1. kenlem1

    kenlem1

    Joined:
    Mar 20, 2014
    Posts:
    40
    Here is a minimum example of baking a NavMesh at runtime without using the abandoned NavMesh components on GitHub. Check out the navmesh documentation to see what each parameter does. There is very little chance this will generate a usable navmesh using the default settings.


    Code (CSharp):
    1.  
    2.  // This should be global in the class
    3. NavMeshDataInstance navMeshDataInstance;
    4. public LayerMask includedLayerMask;
    5.  
    6. private void BuildNavMesh(Transform xform)
    7.     {
    8.      
    9.         // delete the existing navmesh if there is one
    10.         navMeshDataInstance.Remove();
    11.      
    12.         List<NavMeshBuildSource> buildSources = new List<NavMeshBuildSource>();
    13.      
    14.         NavMeshBuilder.CollectSources(
    15.             xform,
    16.             includedLayerMask,
    17.             navMeshCollectGeometry,
    18.             0,
    19.             new List<NavMeshBuildMarkup>(),
    20.             buildSources);
    21.      
    22.         NavMeshData navData = NavMeshBuilder.BuildNavMeshData(
    23.             NavMesh.GetSettingsByID(0),
    24.             buildSources,
    25.             new Bounds(Vector3.zero, new Vector3(10000, 10000, 10000)),
    26.             Vector3.down,
    27.             Quaternion.Euler(Vector3.up));
    28.      
    29.         navMeshDataInstance = NavMesh.AddNavMeshData(navData);
    30.     }
     
    onixsword, Bip901 and Artaani like this.
  2. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Here is one with more of the details needed worked through. Will need to remove the references to OdinInspector and the Button attributes.

    Code (csharp):
    1.  
    2. using Sirenix.OdinInspector;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6. using UnityEngine;
    7. using UnityEngine.AI;
    8.  
    9. namespace AiGame
    10. {
    11.     public class NavBuilder : MonoBehaviour
    12.     {
    13.         public static NavBuilder Instance;
    14.  
    15.         [SerializeField]
    16.         private Vector3 BoundsCenter = Vector3.zero;
    17.         [SerializeField]
    18.         private Vector3 BoundsSize = new Vector3(2500, 2500, 2500);
    19.         [SerializeField]
    20.         private int SettingsId;
    21.         [SerializeField]
    22.         private LayerMask BuildMask;
    23.         [SerializeField]
    24.         private NavMeshData NavMeshData;
    25.         private NavMeshDataInstance NavMeshDataInstance;
    26.  
    27.         [Button("UpdateNavmesh")]
    28.         public void UpdateNavmeshData()
    29.         {
    30.             StartCoroutine(UpdateNavmeshDataAsync());
    31.         }
    32.  
    33.         private void Awake()
    34.         {
    35.             Instance = this;
    36.         }
    37.  
    38.         private void Start()
    39.         {
    40.             AddNavMeshData();
    41.         }
    42.  
    43.         [Button("AddNavMesh")]
    44.         private void AddNavMeshData()
    45.         {
    46.             if (NavMeshData != null)
    47.             {
    48.                 if (NavMeshDataInstance.valid)
    49.                 {
    50.                     NavMesh.RemoveNavMeshData(NavMeshDataInstance);
    51.                 }
    52.                 NavMeshDataInstance = NavMesh.AddNavMeshData(NavMeshData);
    53.             }
    54.         }
    55.  
    56.         private IEnumerator UpdateNavmeshDataAsync()
    57.         {
    58.             AsyncOperation op = NavMeshBuilder.UpdateNavMeshDataAsync(NavMeshData, GetSettings(), GetBuildSources(), GetBounds());
    59.             yield return op;
    60.  
    61.             AddNavMeshData();
    62.             Debug.Log("Navmesh update complete");
    63.         }
    64.  
    65.         [Button("Build")]
    66.         private void Build()
    67.         {
    68.             NavMeshData = NavMeshBuilder.BuildNavMeshData(GetSettings(), GetBuildSources(), GetBounds(), Vector3.zero, Quaternion.identity);
    69.             AddNavMeshData();
    70.         }
    71.  
    72.         private List<NavMeshBuildSource> GetBuildSources()
    73.         {
    74.             List<NavMeshBuildSource> sources = new List<NavMeshBuildSource>();
    75.             NavMeshBuilder.CollectSources(GetBounds(), BuildMask, NavMeshCollectGeometry.PhysicsColliders, 0, new List<NavMeshBuildMarkup>(), sources);
    76.             //Debug.LogFormat("Sources {0}", sources.Count);
    77.             return sources;
    78.         }
    79.  
    80.         private NavMeshBuildSettings GetSettings()
    81.         {
    82.             NavMeshBuildSettings settings = NavMesh.GetSettingsByID(SettingsId);
    83.             return settings;
    84.         }
    85.  
    86.         private Bounds GetBounds()
    87.         {
    88.             return new Bounds(BoundsCenter, BoundsSize);
    89.         }
    90.     }
    91. }
    92.  
    93.  
     
    MadeFromPolygons likes this.
  3. TomPo

    TomPo

    Joined:
    Nov 30, 2013
    Posts:
    86
    Hi
    What I'm doing is:
    - assign NavMesh Builder to some empty GO sorrounding the plane
    - assign NavMesh Surface to the plane
    - in a script i have public reference to NavMesh Surface called 'NavSurface' where i drag drop my plane
    - at the start I'm calling: NavSurface.BuildNavMesh(); and myData = NavSurface.navMeshData;
    (myData is a private variable name of NavMeshData)
    - when I want to rebuild a NavMesh in realtime I'm just using NavSurface.UpdateNavMesh(myData);
    - NavMesh Surface on the plane is set to update Children only and Terrain layer only.
    - all new spawned in real time objects (like obstacles or NavMeshModifierVolume) have terrain layer and are spawned under plane parent.
    Works fine for me.
     
    Last edited: Dec 15, 2017
  4. malkere

    malkere

    Joined:
    Dec 6, 2013
    Posts:
    1,212
    I had a hard time understanding what was going on here, but rewrote snacktime's approach a bit and was able to get things working. I also changed it so that almost nothing ever takes place on the main thread because my game is procedural and needs to do everything on the fly. Thus, the Build() function is actually using an empty LayerMask to generate only the framework for a new navmesh, then actually generating it using the async operation.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.AI;
    5.  
    6. public class NavBuilder : MonoBehaviour    {
    7.  
    8.     Vector3 BoundsCenter = Vector3.zero;
    9.     Vector3 BoundsSize = new Vector3(512f, 4000f, 512f);
    10.  
    11.     LayerMask BuildMask;
    12.     LayerMask NullMask;
    13.  
    14.     NavMeshData NavMeshData;
    15.     NavMeshDataInstance NavMeshDataInstance;
    16.  
    17.     void Start() {
    18.         AddNavMeshData();
    19.         BuildMask = ~0;
    20.         NullMask = 0;
    21.     }
    22.  
    23.     void Update () {
    24.         if (Input.GetKeyDown(KeyCode.B)) {
    25.             Debug.Log("Build " + Time.realtimeSinceStartup.ToString());
    26.             Build();
    27.             Debug.Log("Build finished " + Time.realtimeSinceStartup.ToString());
    28.         }
    29.         else if (Input.GetKeyDown(KeyCode.U)) {
    30.             Debug.Log("Update " + Time.realtimeSinceStartup.ToString());
    31.             UpdateNavmeshData();
    32.         }
    33.     }
    34.  
    35.     void AddNavMeshData() {
    36.         if (NavMeshData != null) {
    37.             if (NavMeshDataInstance.valid) {
    38.                 NavMesh.RemoveNavMeshData(NavMeshDataInstance);
    39.             }
    40.             NavMeshDataInstance = NavMesh.AddNavMeshData(NavMeshData);
    41.         }
    42.     }
    43.  
    44.     void UpdateNavmeshData() {
    45.         StartCoroutine(UpdateNavmeshDataAsync());
    46.     }
    47.  
    48.     IEnumerator UpdateNavmeshDataAsync() {
    49.         AsyncOperation op = NavMeshBuilder.UpdateNavMeshDataAsync(
    50.             NavMeshData,
    51.             NavMesh.GetSettingsByID(0),
    52.             GetBuildSources(BuildMask),
    53.             new Bounds(BoundsCenter, BoundsSize));
    54.         yield return op;
    55.  
    56.         AddNavMeshData();
    57.         Debug.Log("Update finished " + Time.realtimeSinceStartup.ToString());
    58.     }
    59.  
    60.     void Build() {
    61.         NavMeshData = NavMeshBuilder.BuildNavMeshData(
    62.             NavMesh.GetSettingsByID(0),
    63.             GetBuildSources(NullMask),
    64.             new Bounds(BoundsCenter, BoundsSize),
    65.             Vector3.zero,
    66.             Quaternion.identity);
    67.         AddNavMeshData();
    68.     }
    69.  
    70.     List<NavMeshBuildSource> GetBuildSources(LayerMask mask) {
    71.         List<NavMeshBuildSource> sources = new List<NavMeshBuildSource>();
    72.         NavMeshBuilder.CollectSources(
    73.             new Bounds(BoundsCenter, BoundsSize),
    74.             mask,
    75.             NavMeshCollectGeometry.PhysicsColliders,
    76.             0,
    77.             new List<NavMeshBuildMarkup>(),
    78.             sources);
    79.         Debug.Log("Sources found: " + sources.Count.ToString());
    80.         return sources;
    81.     }
    82.  
    83. }
    note that this does require you download and install the git components, or at least does in 2017.1 which I am still running. I ran into some posts of increased navmesh performance in 2017.2 or 3 and will probably upgrade soon.

    Now all I need is to write a tile management system for this and a linking system and I should have fully async procedural navmesh. Thank you very much for sharing the code!
     
  5. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    741
    But aren't you deleting the whole navmesh when you reconstruct it rather than just updating only the parts of the navmesh that have not been affected by the updates to your transforms/meshes in the game world.

    I've also found you can't update areas with the modifier without re-bakeing the whole navmesh. Or you can't even use the modifier at all if your object has a source tag component, so no chance of changing its area type in that situation. The API seems to do everything in its power to be non flexible and poor performance by unity as usual.
     
  6. malkere

    malkere

    Joined:
    Dec 6, 2013
    Posts:
    1,212
    I'm not aware of the "modifier" you're talking about. I do rebake the area entirely every time a change is made, but it takes less than 1 second off thread now that I use:
    • a bounds size of 103.4
    • tileSize = 100
    • voxelSize = 0.15
    I store all the datas and instances in separate <xPos,<zPos, var>> dictionaries so I can remove them from the system if they get too far or update them as necessary based on a simple SomethingChanged(Vector3 pos) call. That call generates a Vector2 (I use an Int2 custom class) that then stores it in a AreasToUpdate list. During Update(), the NavBuilder will either generate a new area from the AreasToAdd list, update an area in the AreasToUpdate list, or remove an area from the AreasToRemove list, one thing at a time in that order.