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

Issues with NavMeshAgent.SetDestination Stopping Agents

Discussion in 'Navigation' started by Ziflin, Feb 28, 2019.

  1. Ziflin

    Ziflin

    Joined:
    Mar 12, 2013
    Posts:
    132
    It appears that whenever NavMeshAgent.SetDestination is used and pathPending=true occurs, Unity immediately stops the agent (velocity=0) and it will not move until the new path is completed. I understand that this simplifies their code, but for player and enemy movement this is not acceptable. If the player is constantly moving towards a mouse-intersection point similar to Diablo/Grim Dawn/etc, SetDestination will be called each frame and if the paths return pending, the agent will never move. Similarly if nearby enemies are pathing to the player they will also not move.

    Is there a solution to this problem while still using the async SetDestination call? Note: "Don't call SetDestination so often" is not a solution in these case as when it is needed the agent will still stop moving. So far these are the only solutions that I can think of:
    • Set NavMesh.pathfindingIterationsPerFrame to a high number. This basically turns SetDestination into a synchronous call, so it is not very helpful.
    • Use two NavMeshAgents, one for steering along a completed path, and one to compute pathing asynchronously. Then switch the paths when a new one is valid. Does anyone do this? This seems like it could waste time because the 2nd agent will still attempt to update its steering as well and there does not appear to be a way to disable this.
    • Use NavMeshAgent.CalculatePath. Sadly this is not asynchronous and it does not behave the same way as SetDestination() which will calculate a path even if the point is well inside a non-walkable area (something that is needed mostly for player path calculations). To have similar behavior, one needs to call NavMesh.FindClosestEdge first (or call it if CalculatePath fails, and then call CalculatePath again with the result).
    • Use the new Experimental.AI.NavMeshQuery API. This looks promising for an ECS setup, but it seems like you still need to have an Agent to perform the steering. Hopefully they'll expose the existing steering code in a similar API.
    Ideally
    • SetDestination would offer a parameter to keep steering along an existing path while computing the new one.
    • Or CalculatePathAsync would be added to calculate a new path asynchronously and then swap it with the agent's path when it becomes ready.
     
  2. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    I know you said it's not an option, but you really shouldn't be calling SetDestination each frame. You should only call it when, to use the Diablo example, you click to move to a new place.
    As for your actual problem, I think you'll have to roll your own NavMeshAgent that can, when given a new destination, predict where he'll be once the new calculation is completed (which is probably impossible since you don't know how long the calculation will take, so you'll probably want to approximate this) and use that position as the start for the new path, then lerp between the current position and start of the new path before setting it as the agent's active path.
    Pretty roundabout way, but not sure if there's much more you can do.
    Considering you get pauses every time you call SetDestination I am really thinking you're bloating the system with too many calls (which is logical if you're calling it each frame for every agent. Again, not designed to be used that way).
     
  3. Ziflin

    Ziflin

    Joined:
    Mar 12, 2013
    Posts:
    132
    I know you said it's not an option, but you really shouldn't be calling SetDestination each frame. You should only call it when, to use the Diablo example, you click to move to a new place.
    Thanks for the reply, but this simply isn't true. Diablo, League of Legends, Grim Dawn, etc. all work by navigating to the point under the cursor each frame the mouse button is held down. This works fine with either CalculatePath or even with SetDestination (if the iterations are set high enough so). Profiling these it take somewhere between 0.01-0.03ms each frame (inside the editor) so it's definitely not a matter of taxing the system.

    Even if I prevent the call to SetDestination while a path is pending, it still does not work as it will making the agent pause its movement whenever I do call it. This seems to be from an unfortunately bad decision to have NavMeshAgent only have a single path stored inside it. Because of this it can't continue to steer along the previous path (if desired) while the new path is pending.

    I have a very simple test case showing exactly this issue, so I suppose I'll submit it as a bug report and see if I can convince Unity to look at it as I've seen this issue mentioned several times before in the past. There are some demos showing 100,000 agents pathing at 40-50fps using the new NavMeshQuery API, but as I mentioned above, I'd like to avoid re-writing the steering/avoidance code that already works fine.
     
  4. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    You're right, when holding the mouse down you'll indeed want to recalculate. I didn't think of that situation.
    Bug reporting or waiting on the NavmeshQuery API to mature seem like the best options to me.
     
  5. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    You can use CalculatePath in connection with SamplePosition. Where you sample the end multiple times if needed at an increasing radius. For a click to move I wouldn't think you need that big of a radius, it should be obvious when something is way out of bounds, and just confirm that with a red x or something as the cursor.
     
  6. octopus0246

    octopus0246

    Joined:
    Nov 21, 2021
    Posts:
    2
    If anyone comes across this thread make sure its not because your calling Agent.SetDestination(transform.position) [where transform is the transform the agent is on]. This is too dumb a mistake that i must believe someone else will do the same.