Search code examples
unity-game-engineagentnavmesh

Unity3d NavMeshAgent.isOnNavMesh becomes false in specific function


I've changed the Title to reflect the addition of clarifying info.

I'm following a [Unity Tutorial][1] and when it came time to test the player click controls Unity gave me an error:

"SetDestination" can only be called on an active agent that has been placed on a NavMesh.

As far as I can tell my agent is active and on the navMesh so this is more than a little confusing. I've tried rebaking the navMesh and repositioning the agent neither of which has worked.

All of the questions I've found thus far have amounted to the asker not having a navMesh at all so... yeah... not very helpful. Any suggestions on how to resolve this while be appreciated.

EDIT: I just added a quick Debug.Log(agent.isOnNavMesh); to my code & low & behold it evaluates to true. Peek confusion.

private void Start()
    {
        Debug.Log(agent.isOnNavMesh);       //Evaluates *true*
        agent.updateRotation = false;

        inputHoldWait = new WaitForSeconds(inputHoldDelay);

        destinationPosition = transform.position;
    }

EDIT-2 Put the same Debug.Log(agent.isOnNavMesh); into my public void OnGroundClick fxn & it evaluates to false after tha click. Begin intrigued confusion.

This is called by a Unity Event System:

public void OnGroundClick(BaseEventData data)
{
    Debug.Log(agent.isOnNavMesh);       //Evaluates *FALSE*
    PointerEventData pData = (PointerEventData)data;
    NavMeshHit hit;

        //Click World Position, hit info, sample distance, navMesh areas to use
    if (NavMesh.SamplePosition(pData.pointerCurrentRaycast.worldPosition, out hit, navMeshSampleDistance, NavMesh.AllAreas))
    {
        destinationPosition = hit.position;
    }
    else
    {
        destinationPosition = pData.pointerCurrentRaycast.worldPosition;
    }

    //give the agent it's destination
    agent.SetDestination(destinationPosition);
    agent.isStopped = false;
}

EDIT-3 I put Debug.Log(agent.isOnNavMesh); in to private void Update(), it evaluated to true and continues to do so even after a Click calls to public void OnGroundClick.

Disabling then enabling the agent at the start of OnGroundClick does not affect the situation

Though I'm still at a loss I'm at least closer to a solution & there's more info than "this doesn't work, please help!" now.

Here is the Code in it's full context:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.AI;

public class PlayerMovement : MonoBehaviour
{
    public Animator animator;                               //Reference to animator
    public NavMeshAgent agent;                              //Reference to NavMeshAgent
    public float inputHoldDelay = 0.5f;                     //Delay player ability to control character while interacting with interactable object
    public float turnSpeedThreshold = 0.5f;                 //minimum speed before character will turn
    public float speedDampTime = 0.1f;                      //Character rate acceleration
    public float slowingSpeed = 0.175f;                     //Character rate of neg accel
    public float turnSmoothing = 15;                        //Character rotational speed


    private WaitForSeconds inputHoldWait;                   //Coroutine Wait timer to delay player input while interacting with interactable object
    private Vector3 destinationPosition;                    //Player designated destination for the agent to pursue


    private const float stopDistanceProportion = 0.1f;
    private const float navMeshSampleDistance = 4f;


    private readonly int hashSpeedParam = Animator.StringToHash("Speed");


    private void Start()
    {
        Debug.Log(agent.gameObject.name);   //Is the "Player" object
        Debug.Log(agent.isOnNavMesh);       //Evaluates *true*
        agent.updateRotation = false;

        inputHoldWait = new WaitForSeconds(inputHoldDelay);

        destinationPosition = transform.position;
    }


    private void OnAnimatorMove()
    {
        //Velocity = Distance over Time, where Distance = change in position between frames & Time = time between frames
        agent.velocity = animator.deltaPosition / Time.deltaTime;
    }


    private void Update()
    {
        Debug.Log(agent.isOnNavMesh);       //Evaluates *true*
        //If path pending, do nothing
        if (agent.pathPending)
            return;

        float speed = agent.desiredVelocity.magnitude;

        if (agent.remainingDistance <= agent.stoppingDistance * stopDistanceProportion)
        {
            Stopping(out speed);
        }
        else if (agent.remainingDistance <= agent.stoppingDistance)
        {
            Slowing(agent.remainingDistance, out speed);
        }
        else if(speed > turnSpeedThreshold)
        {
            Moving();
        }

        animator.SetFloat(hashSpeedParam, speed, speedDampTime, Time.deltaTime);
    }


    private void Stopping(out float speed)
    {
        agent.isStopped = true;
        transform.position = destinationPosition;
        speed = 0.0f;
    }


    private void Slowing(float distanceToDestination, out float speed)
    {
        agent.isStopped = true;
        transform.position = Vector3.MoveTowards(transform.position, destinationPosition, slowingSpeed * Time.deltaTime);

        float proportionalDistance = 1f - distanceToDestination / agent.stoppingDistance;
        speed = Mathf.Lerp(slowingSpeed, 0, proportionalDistance);
    }


    private void Moving()
    {
        Quaternion targetRotation = Quaternion.LookRotation(agent.desiredVelocity);
        transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, turnSmoothing * Time.deltaTime);
    }


    public void OnGroundClick(BaseEventData data)
    {
        agent.enabled = false;              //Disabling then enabling the agent...
        agent.enabled = true;               //does not change anything.

        Debug.Log(agent.gameObject.name);   //Is the "Player" object
        Debug.Log(agent.isOnNavMesh);       //Evaluates *FALSE*

        PointerEventData pData = (PointerEventData)data;
        NavMeshHit hit;

            //Click World Position, hit info, sample distance, navMesh areas to use
        if (NavMesh.SamplePosition(pData.pointerCurrentRaycast.worldPosition, out hit, navMeshSampleDistance, NavMesh.AllAreas))
        {
            destinationPosition = hit.position;
        }
        else
        {
            destinationPosition = pData.pointerCurrentRaycast.worldPosition;
        }

        //give the agent it's destination
        agent.SetDestination(destinationPosition);
        agent.isStopped = false;
    }
}

Solution

  • You have two instances of the PlayerMovement component referring to two different instances of the agent!

    One instance is the one agent.isOnNavMesh is true and works properly in Start and Update. The other instance refers to a different agent where agent.isOnNavMesh is false but this is the one whose OnGroundclick is being called.

    We can tell because GetInstanceID() returns different values in Update vs. OnGroundClick.

    The solution here is to make sure that the OnGroundClick that is registered in the event system is the one in the correct instance of PlayerMovement!