Navmesh Agent Take Cover

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?

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.

1 Like

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?

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.

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 .

2 Likes

Thank you, I guess I just need to experiment with this and see how it all works.

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/

1 Like

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.

15 Likes

Oh wow, holy crap, thanks for the helpful necropost :smile: never knew about FindClosestEdge.

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… :wink:

Could you please supply some code? I can’t for the life of me make it work

@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.

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.

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.

        List<NavMeshHit> hitList = new List<NavMeshHit>();
        NavMeshHit navHit;

        // 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
        for(int i = 0; i < 15; i++) {
            Vector3 spawnPoint = transform.position;
            Vector2 offset = Random.insideUnitCircle * i;
            spawnPoint.x += offset.x;
            spawnPoint.z += offset.y;

            NavMesh.FindClosestEdge(spawnPoint, out navHit, NavMesh.AllAreas);

            hitList.Add(navHit);
        }

        // sort the list by distance using Linq
        var sortedList = hitList.OrderBy(x => x.distance);

        // Write the list in console to check if it's sorted. (Spoiler: it is)
        foreach(NavMeshHit hit in sortedList) {
            Debug.Log(hit.distance);
        }

        // Loop through the sortedList and see if the hit normal doesn't point towards the enemy.
        // 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)
        foreach(NavMeshHit hit in sortedList) {
            if(Vector3.Dot(hit.normal, (enemy.transform.position - transform.position)) < 0) {
                agent.SetDestination(hit.position);
                break;
            }
        }

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.

3 Likes