Search Unity

Agent getting stuck in another Agent when walking in corners - what is the RIGHT solution?

Discussion in 'Navigation' started by castor, Oct 26, 2017.

  1. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    I've been frustrated with this simple problem for over a year, and now the project is advanced enough where we need to figure out if there is a way around it or just ditch Unity's Navmesh system for something more robust.

    So the basic problem I have is that agent avoidance does not take into account the edges of the navmesh, so if the shortest path between 2 points is close to the edge (and there is another agent there) you end up stuck like this:


    While other systems (*cough* A* Pathfinding *cough*) deal with this correctly out of the box:


    Now I've been told that there is no solution to this in Unity system and I would REALLY appreciate an official reply from someone that works in that department.

    Finally, a lot of other developers have suggested that I try the Navmesh Obstacle system, so anytime an agent is not moving, it would activate the carving and the other agents will naturally walk around it.
    In theory, this works fine, but the problem is that when I want to CalculatePath() for an agent to go to another agent (and they both have the NavMesh obstacle carving enabled), I will get a partial path with incorrect corners since there is a hole in the start and end points.
    (top image is what I should get, bottom is the actual result that also looks weird when the agent starts moving)


    Also, the documentation states the following:

    And that last line maybe there is something that is not documented?
    • Alternatively you can use priorities to make certain agents to be avoided more
    So far avoidance only pushes actors, it does not make them 'be avoided more'. Am I missing something?

    Now I'm starting to look at ways to disable the carving when calculating paths, or some other juggling idea...but honestly, that makes no sense...I'm creating new problems trying to fix something that should just work.

    So, what am I missing? What are the possible solutions?

    EDIT: I've also attached a repro scene with the problem. Just Import and press Play.
     

    Attached Files:

    Last edited: Nov 3, 2017
  2. CrymX

    CrymX

    Joined:
    Feb 16, 2015
    Posts:
    179
    Welcome on board...
     
    amarillosebas likes this.
  3. DwinTeimlon

    DwinTeimlon

    Joined:
    Feb 25, 2016
    Posts:
    300
    There are some things you can do, also they are workarounds.

    Have you tried playing around with NavMesh.avoidancePredictionTime yet?

    This might help avoiding it correctly. Also which avoidance quality have you set. You could try:
    Code (CSharp):
    1. NavMeshAgent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance
    You could also check if the character is stuck and then

    Code (CSharp):
    1. NavMeshAgent.ResetPath();
    2. NavMeshAgent.SetDestination(destination);
    Let me know if this helped.
     
  4. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    • NavMesh.avoidancePredictionTime has no effect on this issue, I've tried it.
    • I'm already using the HighQuality agent avoidance.
    • If a character is stuck like in the example above, there is never any change to the actual path. A path is only updated IF there is a change in the Navmesh (and there isn't any with the avoidance system), so making a new path results in a similar solution and the same problem.(Unless I try to implement the NavMesh obstacle that creates the other problem I mentioned)
     
  5. DwinTeimlon

    DwinTeimlon

    Joined:
    Feb 25, 2016
    Posts:
    300
    This is only true if you use CalculatePath. SetDestination doesn't calculate the full path, just a part of it and updates the path during several frames.

    You can still use a NavMeshObstacle on GameObeject with a NavMeshAgent if you disable the NavMeshAgent and activate the NavMeshObstalce when he stands still and vice versa.
     
    Last edited: Nov 3, 2017
  6. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    The problem is not the path calculation.
    A path will only change if there are updates to the Navmesh or if we set a new destination (Using CalculatePath() and manually assign it to the agent or using SetDestination(), it's all the same) and none of these happens in the problem I'm presenting.
    Finally, path calculation DOES NOT take into account any agents, ONLY the navmesh walkable surfaces.
    You can confirm this by enabling path display in the editor (You can have 50 agents with avoidance in the middle of the path and that won't trigger any re-calculations).

    This is the reason why I have the problem in the first place. We have no control or proper debug into the agent avoidance system and decisions.

    About the NavmeshObstacle, it seems to me you skimmed the OP and the MAIN reason why it's not feasible:
     
    Last edited: Nov 3, 2017
  7. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    And if interested, I've attached a repro scene with the problem. Just press Play to see it happen.
     

    Attached Files:

  8. DwinTeimlon

    DwinTeimlon

    Joined:
    Feb 25, 2016
    Posts:
    300
    I got your example working with a repathing trick:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.AI;
    4.  
    5. //--------------------------------------------------------------------------------
    6. public class Agent_Movement : MonoBehaviour {
    7.    public Transform waypointStart;
    8.    public Transform waypointEnd;
    9.  
    10.    Transform currentDestination;
    11.    NavMeshAgent agent;
    12.  
    13.     //-------------------------------------------------------------------------------
    14.     private void Start()
    15.     {
    16.        agent = GetComponent<NavMeshAgent>();
    17.         agent.autoBraking = true;
    18.         agent.autoRepath = true;
    19.         NavMesh.avoidancePredictionTime = 0.5f;
    20.         SetAgentPath(waypointStart);
    21.    }
    22.  
    23.     //-------------------------------------------------------------------------------
    24.     private void SetAgentPath(Transform _transform)
    25.     {
    26.        agent.SetDestination(_transform.position);
    27.         currentDestination = _transform;
    28.     }
    29.    
    30.     //-------------------------------------------------------------------------------
    31.    private void Update()
    32.    {
    33.        if (agent.remainingDistance <= float.Epsilon)
    34.            SetAgentPath(currentDestination == waypointStart ? waypointEnd : waypointStart);
    35.  
    36.         if (agent.velocity.magnitude < float.Epsilon && agent.hasPath)
    37.         {
    38.             agent.ResetPath();
    39.             SetAgentPath(currentDestination == waypointStart ? waypointEnd : waypointStart);
    40.         }
    41.     }
    42. }
    43.  
     
  9. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    Thanks for the suggestion.

    I might be missing something, but all you seem to be doing is randomly asking to bounce between the two waypoints...and thanks to Unity's imprecision it worked going in one direction.

    Here is a more robust version of the problem (using your code). Could you make it work?
     

    Attached Files:

  10. DwinTeimlon

    DwinTeimlon

    Joined:
    Feb 25, 2016
    Posts:
    300
    Ok, my code up there is pretty buggy and also just works by luck. It was a little late for me I guess :). I have looked at the problem with fresh eyes again today and it seems that this is ineed not solveable via repathing.

    The only way I could solve it was through changing the avoidance priority (then the 2nd agent is pushed away) or through switching off avoidance completely when getting stuck and reenable it again when out of this situation, but this is obviously not a really good solution and should instead be handled by the avoidance code from Unity.
     
    Last edited: Nov 4, 2017
  11. Pavelluden

    Pavelluden

    Joined:
    Jun 9, 2013
    Posts:
    2
  12. SM_AF

    SM_AF

    Joined:
    Aug 1, 2017
    Posts:
    23
    Joining the party.
    I am experiencing the same issues and I regularly read the changelogs for every version last year. And I don't even remember ever seeing anything about the Navigation system. Maybe just some bug fix here and there.
    Agent avoidance/dynamic obstacle avoidance need some love from guys at Unity.
     
    aianovskyi likes this.
  13. waizui

    waizui

    Joined:
    Dec 12, 2018
    Posts:
    13
    ok .now 2019.is there any solution been found?
     
  14. DwinTeimlon

    DwinTeimlon

    Joined:
    Feb 25, 2016
    Posts:
    300
    Afaik there is no solution to this yet.

    The best I could come up with for now is writing a "SolveStuck" coroutine. Since I have this setup I never ran into any problem in my RTS and I have hundreds of units on the screen fighting each other.

    Code (CSharp):
    1.  
    2.         private IEnumerator<float> SolveStuck()
    3.         {
    4.             float lastDistanceToTarget = m_navAgent.remainingDistance;
    5.  
    6.             while(true)
    7.             {
    8.                 if(m_navAgent.remainingDistance > m_navAgent.stoppingDistance)
    9.                 {
    10.                     float distanceToTarget = m_navAgent.remainingDistance;
    11.                     if(lastDistanceToTarget - distanceToTarget < 1f)
    12.                     {
    13.                           Vector3 destination = m_navAgent.destination;
    14.                            m_navAgent.ResetPath();
    15.                            m_navAgent.SetDestination(destination);
    16.                            lastDistanceToTarget = distanceToTarget;
    17.                     }
    18.                     yield return Timing.WaitForSeconds(1.0f);
    19.                 }
    20.            }
    21.  

     
    mohaned1001 likes this.
  15. waizui

    waizui

    Joined:
    Dec 12, 2018
    Posts:
    13
    thanks for update .is that you mean add some repath function constantly invoke before units reached their distination
     
  16. DwinTeimlon

    DwinTeimlon

    Joined:
    Feb 25, 2016
    Posts:
    300
    Basically yes. The coroutine is started when an agent starts walking towards a destination and stopped when it was reached.

    But keep in mind, the first post in this thread describes a problem which cannot solved by repathing. What I also do is that units which are not moving (for at least e.g. 10 seconds) have their NavMeshAgents disabled as this could still lead to an agent getting stuck.
     
    Last edited: Apr 2, 2019
    waizui likes this.
  17. waizui

    waizui

    Joined:
    Dec 12, 2018
    Posts:
    13
    ok thanks bro
     
  18. Tomasz_Pasterski

    Tomasz_Pasterski

    Joined:
    Aug 12, 2014
    Posts:
    99
    Got also problem with agents and want to try this approach but....can you explain what is this "Timing" ?
    First time see that convoluted Ienumerator :/

    I get that this must run while agent has path so
    if{agent.hasPath)
    {
    this IEnumerator??
    }
     
    Last edited: Aug 16, 2019
  19. serenity81

    serenity81

    Joined:
    Jan 20, 2020
    Posts:
    4
    I don't think the code below works correctly for the below reasons (I might be wrong but it didn't work for me):

    1) It has issue with Timing line
    2) It doesn't use hasPath to detect if the agent is actually trying to navigate
    3) Last/New Distance Comparison method seems faulty

    Code (CSharp):
    1.  
    2.         private IEnumerator<float> SolveStuck()
    3.         {
    4.             float lastDistanceToTarget = m_navAgent.remainingDistance;
    5.  
    6.             while(true)
    7.             {
    8.                 if(m_navAgent.remainingDistance > m_navAgent.stoppingDistance)
    9.                 {
    10.                     float distanceToTarget = m_navAgent.remainingDistance;
    11.                     if(lastDistanceToTarget - distanceToTarget < 1f)
    12.                     {
    13.                           Vector3 destination = m_navAgent.destination;
    14.                            m_navAgent.ResetPath();
    15.                            m_navAgent.SetDestination(destination);
    16.                            lastDistanceToTarget = distanceToTarget;
    17.                     }
    18.                     yield return Timing.WaitForSeconds(1.0f);
    19.                 }
    20.            }
    21.  
    My Version below fixes the issues above
    Code (CSharp):
    1.  
    2.     IEnumerator SolveStuck() {
    3.         float lastDistanceToTarget = agent.remainingDistance;
    4.  
    5.         while (true) {
    6.             yield return new WaitForSeconds(6f);
    7.  
    8.             //Maybe we can also use agent.velocity.sqrMagnitude == 0f or similar
    9.             if (!agent.pathPending && agent.hasPath && agent.remainingDistance > agent.stoppingDistance) {
    10.                 float distanceToTarget = agent.remainingDistance;
    11.                 if (distanceToTarget != Mathf.Infinity) {
    12.                     if (lastDistanceToTarget - distanceToTarget < 1f && lastDistanceToTarget > distanceToTarget) {
    13.                         //Vector3 destination = agent.destination;
    14.                         //agent.ResetPath();
    15.                         //agent.SetDestination(destination);
    16.                         Debug.Log("Agent Is Stuck");
    17.                     }
    18.                     Debug.Log("Remaining Distance " + distanceToTarget + " Last Distance " + lastDistanceToTarget);
    19.                     lastDistanceToTarget = distanceToTarget;
    20.                 }
    21.             }
    22.         }
    23.     }
    24.  
    The biggest problem with both approaches is that remainingDistance in many cases returns "infinity" because it couldn't calculate it so this causes inaccuracies. I Believe the best approach would be to compare raw position changes to detect being stuck coupled with NavMesh based validation if agent has a path and trying to move in the first place.

    Code (CSharp):
    1.     IEnumerator SolveStuck() {
    2.         Vector3 lastPosition = this.transform.position;
    3.  
    4.         while (true) {
    5.             yield return new WaitForSeconds(3f);
    6.  
    7.             //Maybe we can also use agent.velocity.sqrMagnitude == 0f or similar
    8.             if (!agent.pathPending && agent.hasPath && agent.remainingDistance > agent.stoppingDistance) {
    9.                 Vector3 currentPosition = this.transform.position;
    10.                 if (Vector3.Distance(currentPosition, lastPosition) < 1f) {
    11.                     Vector3 destination = agent.destination;
    12.                     agent.ResetPath();
    13.                     agent.SetDestination(destination);
    14.                     Debug.Log("Agent Is Stuck");
    15.                 }
    16.                 Debug.Log("Current Position " + currentPosition + " Last Position " + lastPosition);
    17.                 lastPosition = currentPosition;
    18.             }
    19.         }
    20.     }
    This code might need tweaking for best results and not tested 100% but maybe it can help someone[/QUOTE]