The Modern Pathfinder
Pathfinding is used extensively in the implementation of artificial intelligence. There are numerous ways to find the optimal path between two points, but the most commonly used are based on the A* algorithm. The methodology is incredibly similar across all heuristics, but the discovery of the shortest path can be widely different.A* is not the only pathfinding system; Dijkstra in 1959 developed the algorithm which served as the basis for A*. The 1950s were ripe with various algorithms, and indeed, many of the variations between them were subtle. By the end of the decade, Dijkstra beat out algorithms like the Bellman-Ford method and one written only a year earlier by Dantzig. Not until recently was Dijkstra substituted by A*, which uses a more cost-effective heuristic.
How Grid-based A* Works
The built-in pathfinding tools in most modern engines use A* to work, but more optimal solutions have been made to be better suited for a 3d environment. Those variants involve NavMeshes, which slice a “walkable” area into a series of convex polygons, allowing for the AI to move around freely within them. This method chooses which polygonal slices are included in the optimal path, and uses their points to deliver complex, accurate, and dynamic movement. Whether the environment is large or small, this method is both highly effective and scalable. However, grid-based pathfinding is still an incredibly effective tool, and is a lot faster to implement, especially with less complicated projects. This is a higher-level look of pathfinding, so I will not go into the actual implementation of it, aside from showing the pseudocode I implemented in my own version.Pseudocode for the A* and Dijkstra algorithms. Both are very similar. |
This can be optimized in many ways:
• A common, but powerful optimization tool is that the AI calculates the distance between the player and itself before initiating the algorithm
• If the player is in range, a vector between the AI and the player is calculated based on the direction the AI is facing, and if that line is interfered by a barrier, assuming that the player is behind a wall, then the AI will not chase the player
The algorithm works by checking the nodes surrounding the AI. To determine which node is the optimal one, it calculates the Scost, which is the node’s distance from the starting AI, and the Ecost, the distance from the destination. Each node is weighted based on the sum of the Scost and Ecost. The expanded nodes, colored orange, are the expanded nodes that were weighted by the algorithm. The node with the lowest weight is chosen as the most promising. The A* heuristic only looks at promising nodes, dramatically decreasing the number of nodes it tests, making the process faster. Based on the promising node with the lowest weight, the process repeats around that node until the destination is reached or returns null, meaning the destination is not reachable. Additionally, if a node is occupied by an obstacle, it ignores it. Of course, promising nodes can change based on the obstacles it discovers, as it determines the fastest path based on what it already knows.
The algorithm returns a 2-dimensional array of x and y coordinates of all the promising nodes leading the AI to the destination.
{{ 120,184 }, { 136,200 }, { 152,200 }, { 168,200 }, { 184,200 }, { 200,200 }, { 216,200 }, { 232,200 }, { 248,216 }, { 264,232 }, { 280,248 }, { 296,264 }, { 312,264 }, { 328,248 }, { 344,232 }, { 360,216 }, { 360,200 }}
To retrieve the x coordinates from the first node, you would call the first position from the array (0), and the first position within that array (0,0). The same would go for retrieving the y coordinate, but retrieving the second position (0,1). In an inelegant form that would be read as x equaling enemy_path[0,0] and y equaling enemy_path[0,1].
As the AI moves towards that first node or the destination moves, as in the player moves, the coordinate position of the first node will update, changing the trajectory of the AI. For smoother movement, programmers use linear interpolation, which in this case creates a closer coordinate between the AI and the first node, making the movement smoother. How that coordinate is divided up is visually interpreted as speed.