Building a NavMesh at run-time using Unity.AI.Navigation and NavMeshSurface

Hi,

I’m building an AR experience where I receive plane anchors (just GameObjects with a child quad mesh). I’d like to turn these into navigable surfaces and have been looking at Unity.AI.Navigation. However, I’m seemingly unable to actually create a navmesh on the planes. Here is my code:

        foreach (PlayspaceAnchor anchor in _playspaceAnchorTopic.Anchors)
        {
            if (anchor.IsOfClass(classLabel: PlayspaceClass.Label.Floor))
            {
                NavMeshSurface surface = anchor.gameObject.AddComponent<NavMeshSurface>();
                surface.AddData();
                surface.BuildNavMesh();

                _testAgent?.gameObject.SetActive(true);
                _testAgent?.Warp(newPosition: anchor.transform.position);
            }
        }

Pretty simple: I add the NavMeshSurface component and call the build function (a bit concerned that this method is not async, btw). Next, to test it out, I have a reference to a NavMeshAgent and I try to warp it to the center of that plane. But when the script that controls that agent tries to select a new destination, I get:

"GetRemainingDistance" can only be called on an active agent that has been placed on a NavMesh.
UnityEngine.StackTraceUtility:ExtractStackTrace ()
EnemyBehavior:Update () (at Assets/Scripts/AI/EnemyBehavior.cs:90)

Indicating that a NavMesh probably has not been constructed. Using the Scene view, I don’t actually see any NavMesh geometry although the NavMeshSurface component was successfully added as expected.

I’ve also tried using the old NavMeshBuilder system but my code (which worked in Unity 5.6) no longer appears to work. No NavMesh components appear to be created and no NavMesh is visible in the Scene editor.

Any tips or examples? The docs are pretty sparse.

Thanks,

Bart

Not sure how much of a help this will be for you but here is a snippet of my code.
Note I also use MessageDispatcher but you could use whatever or just remove it.

You could subscribe to OnBuilt event then add your agents to the navmesh.

using com.ootii.Messages;
using System.Collections;
using Unity.AI.Navigation;
using UnityEngine;

namespace Apoth
{
    public class NavigationBuilder : MonoBehaviour
    {
        public bool debugMode = false;

        public bool gridsArePrebaked = false;

        public NavMeshSurface surface;

        public bool NavMeshBuilt { get; private set; } = false;

        public static NavigationBuilder instance;

        public static OnInitialized OnBuilt;


        void Awake()
        {
            if (instance == null)
            {
                instance = this;
            }
            else
            {
                Destroy(gameObject);
                return;
            }
        }

        private void OnEnable()
        {
            if (debugMode)
                Debug.Log("NavigationBuilder - OnEnable");

            if (gridsArePrebaked)
                OnNavMeshBuilt();
            else
                MessageDispatcher.AddListener("CreateGridMessage", OnCreateGridMessageReceived, true);
        }

        private void OnDisable()
        {
            if (debugMode)
                Debug.Log("NavigationBuilder - OnDisable");

            MessageDispatcher.RemoveListener("CreateGridMessage", OnCreateGridMessageReceived);
        }

        public void OnCreateGridMessageReceived(IMessage rMessage)
        {
            Debug.Log("NavigationBuilder - OnCreateGridMessageReceived");

            CreateGridMessage cgm = (CreateGridMessage)rMessage.Data;

            Bounds? bounds = null;

            if (cgm != null)
            {
                bounds = cgm.dungeonBounds;
            }

            StartCoroutine(BuildNavMeshAfterDelay(1, bounds));
        }

        IEnumerator BuildNavMeshAfterDelay(float delayTime, Bounds? bounds)
        {
            yield return new WaitForSeconds(delayTime);

            if (bounds.HasValue)
            {
                surface.size = bounds.Value.size;
                surface.center = bounds.Value.center;
            }

            surface.BuildNavMesh();

            Invoke(nameof(OnNavMeshBuilt), 1);
        }

        public virtual void OnNavMeshBuilt()
        {
            Debug.Log("NavigationBuilder - OnNavMeshBuilt");

            NavMeshBuilt = true;

            OnBuilt?.Invoke();
        }
    }
}
1 Like

Thanks – I will try this out. The key differences I’m seeing are:

  • Explicit definition of size and center.
  • NavMeshSurface already exists on object (I did try this as well by placing this component on the anchor prefabs I instantiate).

When the nav mesh is built, is it visible in the Scene window?

Yes you can see it if you have gizmos on and Show NavMesh checked.
8695149--1173330--upload_2022-12-30_13-49-22.png

8695149--1173330--upload_2022-12-30_13-49-22.png

Still doesn’t work for me but I did some tests and am discovering that apparently it doesn’t like quad geometry? I’m playing around in the editor manually baking navmeshes and realizing that quads don’t seem to work but planes do. Doesn’t matter how large the quad is, it seems to be failing to sample it and I don’t see anything online about this being a problem for others.