AI move system
As a followup to my post about search path generation, here is the prototype which the path is being fed to. As an added bonus, at the end of this post I will show you how I combined the two prototypes, which illustrates how an animal might randomly explore an obstacled environment.
When working with AI agents that can change behaviors at any time, it is important to have a very robust movement system to facilitate these changes. This prototype addresses the issue of swapping destinations on the fly, providing an easy-to-use interface that simply accepts a list of Vector3 coordinates for a new path, then takes care of the rest. Additionally, the agent can be paused or resumed at any point during its pathfinding process (by pressing “W”).
A simple environment was created to illustrate the concept. Contained within this environment are two custom paths which the agent can swap between at any time (by pressing “Space”):
These two paths are computed and rendered by a PathController object, which simply finds the objects in the scene, compiles them into a list of points, then draws a line between the points. These two lists are then available to the AI agent via a reference established by the agent. By swapping between these lists, the prototype simulates the agent receiving new paths from behavioral changes.
As with the search behavior prototype, both the Scene view and Game view should be active, as the lines are only drawn in the Scene view, but button presses are only registered in the Game view.
Code:
The Unity Package file for the move prototype can be found here:
The code in this prototype is split into two files, each with their own class.
1 – AgentScript
Houses all of the code relevant to this prototype
2 – PathControllerScript
Can largely be ignored, as it is just a placeholder for the actual systems which the movement system will interface with (eg. the search behaviour).
1. AgentScript:
Contains all of the functions relevant to this prototype. It handles distance verification, assigning new destination coordinates when a point in the list is reached, stopping and starting the agent at command and dynamically changing to a new list of points – even in the middle of handling a previous list.
1.0 Fields:
1 – PathControllerScript pCScriptRef
A reference to the PathController object’s PathControllerScript. Necessary to retrieve the two paths computed by it.
2 – IList<Vector3> p1, p2
A reference to the two paths retrieved from the PathControllerScript.
3 – IList<Vector3> path
The AI agent’s current path. Never update this directly, always use the UpdatePath function for this!
4 – NavMeshAgent agent
A reference to the NavMeshAgent component of the AI agent. Necessary for navigating the navmesh and performing pathing calculations.
5 – Bool shouldMove
A boolean used to indicate if the AI agent should be on the move or stop in its tracks. Stopping an agent will not cancel its current path, so it will continue to move to its last assigned location if this variable changes back to true.
6 – Int destPoint
The index variable used to tell the agent which point on the list the NavMeshAgent is currently navigating to. This resets to zero when the path is updated with UpdatePath.
7 – Bool swap
A test-variable used to swap between the two paths in this prototype.
1.1 void Start()
Establishes a reference to the PathController object, through which access to the two paths is gained. The information retrieved is then stored in the p1 and p2 variables for easy access. Additionally, a reference to the NavMeshAgent is established and autoBraking is disabled (so that the agent doesn’t slow down each time it approaches a point in the path list)
1.2 void Update()
Listens for keypresses – spacebar swaps between paths, w enables and disables movement. When shouldMove is true, a constant call to the Move function is made. Otherwise the agent is stopped. Finally, the update function draws the path computed by the NavMeshAgent in the scene view for debugging.
1.3 void SwapPaths()
When called, calls the UpdatePath and passes either the p1 or p2 list, depending on whether swap is true or false. The value of swap is then inverted.
1.4 void Move()
Checks with the NavMeshAgent to see if no path is pending and it is within a specified distance of its current point in the list. If it is close enough and the point is not the last one on the list, a call to the GoToNext function is made, telling the NavMeshAgent to change its destination to the next point.
If the last point in the list is reached, the agent is stopped and the destPoint index is reset to zero.
If the Move function is called while the agent is in the middle of transitioning to a point, this function simply ensures that the agent is not in its “isStopped” mode (which happens if it has been stopped previously).
1.5 void GoToNext()
This function starts out by checking if there is any length to the given path. If not, terminate to prevent issues. If a path of some length is detected, update the NavMeshAgent to move towards the current point in the list (according to the destPoint index value). Once that has been done, increment the destPoint value by 1, so that the next time GoToNext is called, it will direct the AI agent to the next point.
1.6 void UpdatePath(IList<Vector3> _newPath)
This function should always be used when changing the current path of the AI agent, as certain values have to be updated to reflect the change. UpdatePath sets the value of destPoint back to zero, updates the path list with the _newPath list and forces the NavMeshAgent to update its destination to the first point in the new list.
Combining the prototypes:
I promised I would show how I integrated this system with the search behavior prototype I had described in the previous post. But before I get to doing so, I recommend downloading the Unity package containing this demonstration:
The code for this prototype is largely the same as in the Move prototype. Pressing the spacebar generates a new path, while pressing “W” pauses and resumes the pathfinding process.
The primary difference can be found inside the Update()-function in the AgentScript which is attached to the Animal object in the scene. Here you’ll notice that the call to “SwapPaths()” has been replaced with a call to “SpawnTracker(),” which is nearly identical to the SpawnTracker()-function from the search behavior prototype, found on the tracker object. The minor difference from that prototype and this one, is the fact that the calculated path is passed to the UpdatePath()-function from the move prototype, which does exactly what it did previously.
And voilá, that is the benefit of working modularly. If each system is developed on its own, but on robust and easy-to-use principles (and with well-written documentation), integrating them elsewhere is a piece of cake.
Oh and as a small bonus, try enabling the “Tracker” object in the scene view, then uncomment the call to “ExecuteLogicTime()” in the SearchScript attached to it. It illustrates how the sine- and cosine-waves interact to create a spiral pattern, as well as give a clear picture of when an area is no longer accessible.
It was this function which brought my attention to the fact that the points in the path should be shuffled randomly, as you may notice the pathing along the barricade wall. That would have caused any animals searching near the edge of the scene, to bang their heads up against the wall.