Coding the AI to Take Cover in Unity 2021
In this article, we’ll code the AI behavior for taking cover on our Robot NPC character.
I’ve already placed waypoints at key locations along the Nav Mesh.
Most of them are on pillars, but some take advantage of props such as the generator, boxes, or barrels.
In the Hierarchy, I’ve organized these waypoints under a parent game object that I will reference in code to “load” all of the waypoints.
Don’t Duplicate Code
I’m already loading an array for the general start and end waypoints in my RobotAI script.
I might as well make it a method that can load any array rather than duplicating existing code over and over.
But first, I’ll add my new variables for the cover waypoints and their parent game object.
I’ve created my LoadWaypointArrays() method, and I’ve copied the existing code from the Start() method I’ve already written that does this task for existing waypoints.
This way I can use it as a template, which avoids headaches and saves me time.
The key to making this method work is using the “ref” (reference) keyword for our input parameters.
We want to pass the actual variables and arrays into this method, not the values of those variables.
With this method complete, its time to change the Start() method to use it for both waypoints and cover waypoints.
The code for populating our original array in the Start() method is now just one line of code and of course we need another line of code to load the new array.
The key is once again the “ref” (reference) keyword which has to be used when calling the method as well.
If we take a look at the RobotAI script component in the Editor while we start the game, we can see that everything is loading in properly!
With the groundwork laid, we need to move on to the logic for the AI to take cover.
As it stands, I intend to have each Robot game object with a normal collider and an oversized collider to detect near misses when the player shoots at it.
When the robot detects that a near miss or a hit has occurred, it will determine the nearest cover from our list of cover waypoints, run to that waypoint, wait a few seconds, and then resume running towards the exit waypoint.
To test out this code I’m writing, I’ll create a coroutine and call it from start.
This testing coroutine will wait a few seconds, pretend the robot was hit, rinse and repeat.
I’ve created the RunToCover() method to determine what cover state the AI is currently in, and then change cover state to “RunningTo” if appropriate.
The SetCoverStatus() method was written previously, I’m not overly proud of this switch nightmare but it works!
In this method we assign the _currDestination variable which is transferred to the _navMeshAgent.destination call.
The GetNearestCoverWaypoint() method is fresh code though.
I simply set a ridiculously high value for the shortestDistance variable, iterate through each waypoint’s position in the _coverWaypoints array, update the shortest distance and current index of the array and then assign the waypoint position based on the results.
Vector3.Distance is the key to making this an easy process.
The resulting Vector3 value is returned to the SetCoverStatus() method which updates the _navMeshAgent.destination call.
Unity’s Navigation system takes over from there.
The Move() method determines when the AI has reached cover and then the SetCoverStatus() case AtCover calls the WaitToRun() coroutine.
WaitToRun() waits a few seconds while the AI is at cover, then resets the _navMeshAgent.destination as the end waypoint and calls SetCoverStatus() for None.
SetCoverStatus() then calls SetAnimationState() with “WalkToRun”.
The SetAnimationState() method starts the _naveMeshAgent again and calls the WalkToRun() coroutine.
Lastly, the WalkToRun() coroutine transitions the AI back into a full run from a walk.
That was a lot, I know.
But you made it!
Let’s take a look at the results with our testing method simulating a hit at various times as the Robot AI is running.
I’ve added a cooldown function for running to cover and logic to prevent the AI from running to a waypoint that is behind it on the path.
The cooldown is simple, it relies on a new class variable in RobotAI script, “_coverCooldown”.
In the SetCoverStatus() method, when the AI CoverStatus is “RunningTo” and then set to “AtCover”, I call the new StartCoverCooldown() routine.
For 3 seconds, the class variable _coverCooldown is set to false.
During this time, if SetCoverStatus() has a CoverStatus of “None” and is then set as “RunningTo”, before setting a new NavMeshAgent.destination, it will check this boolean flag.
If true, nothing happens.
After 3 seconds in the StartCoverCooldown() coroutine the boolean flag is reset.
In the GetNearestCoverWaypoint() method, the above logic has been added to determine if the newly assigned waypoint would be ahead or behind the Robot game object.
If behind, it moves on to the next waypoint.
I first determine which floor the AI is on based on which waypoint was selected as the closestCoverWP.
Since the AI runs one direction, then back the other direction, and then back to the original direction as it climbs floors, I have to determine whether to check for greater than or less than on the X positional value.
Everything works as intended!