Search Unity

Navmesh Agent Take Cover

Discussion in 'Navigation' started by Lethn, May 9, 2016.

  1. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    It's simple enough to get NavMesh Agents to follow waypoints and patrol somewhere or to get them to wonder about aimlessly, but how is it even possible to get agents to position themselves behind objects based on the direction the player is facing?

    So for instance if I were to shoot a raycast out at the agent from the player which indicates which direction the player is aiming with a gun how would I get the agents to automatically search for an obstacle to put between them and the raycast?

    Would I in fact need empties like waypoints to indicate which areas are cover and which aren't so they pick a random spot to hide behind? Or is there a way of having them take cover in a more clever or dynamic way?
     
  2. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,327
    Usually you mark areas of the map as cover: add an empty GO with a cover tag or component and for each cover point within a certain distance raycast to the player.
     
    JBR-games likes this.
  3. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    That makes sense, but is there any other way of perhaps making it more intelligent? Or is this a bit like with kinematic objects or jumping over obstacles where you have to design everything yourself for each scene?
     
  4. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,327
    Implicit cover points is faster at runtime but you can always devise a utility to analyse space for you by shooting rays, slicing space in voxel etc... I haven't done that because it's just simpler to tag objects but I am sure you can find some papers on that somewhere in the interweb.
     
  5. JBR-games

    JBR-games

    Joined:
    Sep 26, 2012
    Posts:
    708
    A trick i did was to add mesh planes at any spots on the ground around objects that where good cover spots, both sides of objects. Set as navmesh cover layer, and set the cost lower so that the ai will try to walk in cover spots as it moves to its targets. Then Bake your navmesh & set those meshes inactive(so you can easily edit later if needed) . From there you could also do a nav layer check and maybe Play kneeling or ducking animation when the character is on a cover layer.

    That with the cover GOs with raycast checks as mentioned, should make for a fairly realistic ai .
     
    Novack and laurentlavigne like this.
  6. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Thank you, I guess I just need to experiment with this and see how it all works.
     
  7. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    You can use a random approach to find cover points. For example, you create, on the fly, a set random points around the current agent location, then you test, for each point, whether the player has a line of sight on it. If not, then it is a good candidate for cover.
    I've use this approach for third person shooter to demonstrate AIs build with Panda BT. This example is available in the package, see:
    http://www.pandabehaviour.com/
     
    nomanjaved97 likes this.
  8. andrewgotow

    andrewgotow

    Joined:
    Dec 28, 2013
    Posts:
    18
    The NavMesh class also exposes the FindClosestEdge function, which will return the nearest boundary on the NavMesh. This is typically an un-walkable surface, such as a wall or cliff. Perform some random samples around your character, and find the nearest edges to each. Then, determine the best candidate for cover from this sample set.

    A neat way of doing this, is using the "normal" field of the returned NavMeshHit structures. This is a vector pointing away from the edge, or in this case, the wall! Sort candidate samples by their "distance" value, and then eliminate all those samples who's normal points towards the enemy...

    Vector3.Dot(navMeshHit.normal, (enemy.position - transform.position)) < 0

    If the normal points towards the enemy, then the position is on the wrong side of cover, and your character will be vulnerable. Then, just pick the first point in the queue, and navigate there! This will be the nearest, viable cover point, which puts a wall between the character, and the enemy.

    This approach works on the fly without you having to annotate cover points, works for dynamic nav-mesh carving (enemies will take cover behind physically simulated objects, vehicles, etc.), and allows them to use off-mesh links like jumps and vaulting over cover to get behind it!

    Lastly, if you want to tweak the difficulty, change the comparison in the normal dot-product. -0.5 means that enemies will seek out extremely good cover. 0.5 means that the enemies are easier to flank.
     
  9. Lethn

    Lethn

    Joined:
    May 18, 2015
    Posts:
    1,583
    Oh wow, holy crap, thanks for the helpful necropost :D never knew about FindClosestEdge.
     
  10. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    I used to parse the entire navmesh to get all edges and bake out data to an octree which contained edge and an edge height property and edge normal, then it was pretty trivial to take cover by filtering all edges near the AI with normals going same direction as the line between player and enemy using dot.

    I think the above posts have it covered though.... ;)
     
  11. Filip-ljunglof

    Filip-ljunglof

    Joined:
    Dec 13, 2015
    Posts:
    25
    Could you please supply some code? I can't for the life of me make it work
     
    Last edited: Oct 10, 2017
  12. UDN_5c806b49-d8a0-4f67-a296-c12c91aa7396

    UDN_5c806b49-d8a0-4f67-a296-c12c91aa7396

    Joined:
    Jan 9, 2017
    Posts:
    152
    @Filip-ljunglof have you figured this out? I'm trying to figure this out too myself.
    In my case, I have 2 NavMeshAgent AIs, Team Good vs Team Bad. When either one is within radius, they start shooting each other. But there is a wall in between. What to do? I want them to intelligently, move around the wall and battle it out.
     
    Last edited: Sep 2, 2020
  13. Filip-ljunglof

    Filip-ljunglof

    Joined:
    Dec 13, 2015
    Posts:
    25
    Hi, no I never managed to create a solution to this issue at the time and I eventually game up on the project after hitting more technical issues I couldn't solve at the time. After seeing your initial post I did create a solution based on Andrew's answer.

    However, what you're asking now about having 2 NavMeshAgents getting stuck on opposite side of a wall is another issue all together. You will probably find the best solution to this problem either by searching the internet for existing posts having similar problem or create a new thread if you can't find anything similar.

    To answer your question though my instant solution to this would be to, if the agent loses sight of the enemy agent it will move to the position it last saw the enemy agent at.
    This solution is of course untested and would most likely just have the agent running around the wall swapping sides with each other.
    As said before you will probably find the best solution to this by either searching the web or creating another thread.
     
  14. Filip-ljunglof

    Filip-ljunglof

    Joined:
    Dec 13, 2015
    Posts:
    25
    To those who stumble upon this thread having the same problem, I created a solution based on Andrew's answer. It's not perfect but the best I can do in about 1 hour, following the instructions.

    Code (CSharp):
    1.         List<NavMeshHit> hitList = new List<NavMeshHit>();
    2.         NavMeshHit navHit;
    3.  
    4.         // Loop to create random points around the player so we can find the nearest point to all of them, storting the hits in a list
    5.         for(int i = 0; i < 15; i++) {
    6.             Vector3 spawnPoint = transform.position;
    7.             Vector2 offset = Random.insideUnitCircle * i;
    8.             spawnPoint.x += offset.x;
    9.             spawnPoint.z += offset.y;
    10.  
    11.             NavMesh.FindClosestEdge(spawnPoint, out navHit, NavMesh.AllAreas);
    12.  
    13.             hitList.Add(navHit);
    14.         }
    15.  
    16.         // sort the list by distance using Linq
    17.         var sortedList = hitList.OrderBy(x => x.distance);
    18.  
    19.         // Write the list in console to check if it's sorted. (Spoiler: it is)
    20.         foreach(NavMeshHit hit in sortedList) {
    21.             Debug.Log(hit.distance);
    22.         }
    23.  
    24.         // Loop through the sortedList and see if the hit normal doesn't point towards the enemy.
    25.         // If it doesn't point towards the enemy, navigate the agent to that position and break the loop as this is the closest cover for the agent. (Because the list is sorted on distance)
    26.         foreach(NavMeshHit hit in sortedList) {
    27.             if(Vector3.Dot(hit.normal, (enemy.transform.position - transform.position)) < 0) {
    28.                 agent.SetDestination(hit.position);
    29.                 break;
    30.             }
    31.         }
    The biggest problem with this approach is FindClosestEdge method as you need some way to iterate over it. Unfortunately there isn't any FindNextClosestEdge or any simple way to achieve this. The only solution I could find was to change the sourcePosition parameter in the FindClosestEdge method. I did this by adding a offset to the transform's position using Random.insideUnitCircle. Otherwise the closest edge will always be the same, if you only use transform.position. This approach works but I don't think it's an ideal solution.

    Second, because you always find the closest edge the position you find is well an edge. Meaning that you will always move towards a edge for cover. Resulting in that most of the time only half of the character model is actually behind the wall/cover. An additional method to find the center of the wall from the edge and have the character navigate there instead is probably needed.

    Hope this helps someone, if you have any further questions please ask.
     
    OneDevArmy, dryox and moeinsorosh96 like this.