# Lecture 14 - AI Pathfinding ### SET09121 - Games Engineering
Babis Koniaris/Tobias Grubenmann
School of Computing. Edinburgh Napier University --- # Recommended Reading - Artificial Intelligence for Games. Second Edition. Millington and Funge (2009). - Whole chapter on pathfinding. ![image](assets/images/ai_book.jpg) --- ## Pathfinding ![image](assets/images/pathfinding.jpg) --- # What is Pathfinding? - Pathfinding (or more specifically path planning) is a decision making process that feeds into the movement. - You can consider it as crossing the boundary between movement and decision making. - Pathfinding is really the key ingredient that allows characters to navigate. - There is a good chance you have covered this before in AI or Algorithms and Data Structures. --- # Why do we need Pathfinding? - Game maps are generally too complicated for simple steering to be in charge. - We could hard-code routes through the map, but that is not a scalable strategy. - So we need a technique that allows an entity to determine a route to follow to get to its destination. - Pathfinding allows us to do this - it examines map data and provides a set of waypoints to follow by the entity. - Pathfinding is just a form of graph search, and there are different methods to do this. - We need a fast solution - A* being the most commonly used. --- # Why Too Complicated? - If there are only simple convex objects, basic avoidance behaviours will look great! - But if you have concavities, obstacle avoidance will break down - In fact, your boids will appear almost magnetically funneled into the concavities! - As we discussed previously, level design impacts AI design --- # Pathfinding Costs - Maps are very big today - some over $100km^2$. - If each square metre was a navigation point that's $10^8$ points. - If we can travel in any direction, things get very expensive. - Generally we are looking for simplifications to combat this. ![image](assets/images/witcher3_map.jpg) --- # Game World Sizes
--- ## Underpinning Theory - Graphs --- # What is a Graph? - Prepare to have terms thrown at you which might give you flashbacks. - A **graph** is just a collection of objects where pairs of objects are related in some way. - We typically refer to the objects as **nodes** (or vertices) and the connections as **edges**. - A graph can therefore be defined as a set of nodes and a set of edges. - From a game pathfinding point of view, a node is a location in the game world, and an edge is a path between two edges. - We don't need to be any more elaborate than that in game terms. --- # Example Graph - Node-link Diagram ![image](assets/images/node-link.png) --- # Weighted Graphs - For pathfinding we are concerned with the cost. - The cost of a path is dependent on some factors that allows us to determine what the cheapest path is. - Game factors: distance, underlying terrain, obstacles - We consider that an edge has a cost associated with it (weight) - To traverse an edge means to incur the cost of that traversal. - In our pathfinding each traversal will have a cost of 1. ![image](assets/images/weighted-graph.png) --- # Directed Graphs - A graph may also be directed. - This means that an edge only has one direction of travel. - We won't use this, but it does exist in games. - For example, jumping down a ledge you cannot get back up. ![image](assets/images/directed-graph.png) --- # Tile Engine and Graphs - We will be building our pathfinding into our tile engine. - It is just easier - the data is all there. - We will use the data directly and build up our path incrementally from the level data. - The algorithm should be reusable though - you just need to specify where you are getting the data from. ![image](assets/images/tile-path.jpg) --- # Tile Graphs - This approach should be OK for anything you are building, but a word of warning... - A tile-based graph pathfinding approach does not scale to large maps. - We mentioned this at the start. - A worst case pathfind means that all paths on all nodes have to be searched. - This leads to an algorithmic complexity of $\mathcal{O}(\lvert V \rvert^2)$. - $\lvert V \rvert$ is the size of the node (vertex) set. - So don't convert your massive million by million tile world into a pathfinding nightmare. --- ## Dijkstra --- # Dijkstra's Algorithm - Defined by Edsger Dijkstra in 1956. - An algorithm to find the shortest path between two nodes in a graph. - For a game, find the shortest path between two locations. - An extension to the algorithm allowed finding of all the paths from a source node. - In other words, how do we get to each of the nodes in the shortest time. - This algorithm is not only used for pathfinding in games. - Google Maps will use something similar for moving in road networks. - Network routing protocols will use such an algorithm. - Dijkstra is typically too expensive to use in games --- # Dijkstra's Algorithm - 6 steps 1. Mark all nodes as initially unvisited. Use this to create the set of *unvisited* nodes. 2. Set distances for the nodes: - Initial node (current node) distance is 0. - Other node distances set to infinity. 3. For the current node look at connected neighbours. Use to determine a tentative distance from the current node. Update the neighbours distances if the new route is shorter. 4. Mark current node as visited (remove from *unvisited* set). We will not visit this node again. 5. If destination has been marked visited (in other words we reached our destination) or all *unvisited* nodes have infinite distance, stop. 6. Else select unvisited node with smallest tentative distance from the initial node and set as current node. Go to step 3. --- # Dijkstra's Algorithm - Dijkstra guarantees that the path found is going to be the shortest - Unlike approaches such as BFS, DFS - BFS: special case of Dijkstra without weights or priority queue - Dijkstra iterates through nodes based on which one has the shortest distance from the start node. - This means it is not actively searching for the destination but doing a traversal of the graph until it happens to find it. ![image](assets/images/dijkstra.png) --- # Example - Dijkstra at Work
--- # Problems with Dijkstra - The problem with Dijkstra's algorithm is it not actually searching for our destination. - Dijkstra's approach sets out to find the shortest path from a source to the neighbouring nodes. - It just might run into the destination at this step. - Therefore, Dijkstra is expensive for pathfinding - it might just get lucky. - This leads to an algorithmic complexity of $\mathcal{O}(\lvert V \rvert^2)$. - So we need a better technique that tries to find our destination node. --- # Dijkstra for many entities going to the same place - What if all your agents are only pathing to a single location? - E.g. enemies swarming the player - If so, then you can precalculate it and have hundreds of agents! - Precalculate the costs to the goal(s) using Dijkstra from any point in the map - E.g. goal is player position - When calculating the path, for each agent: - Look at cost at current position - Look at costs at neighbouring positions - Pick the neighbour position with lower cost - Not needed for this module, but it's food for thought! --- ## A* --- # A* - A* was first described in 1968 (about 10 years after Dijkstra's algorithm) by a team from the Stanford Research Institute. - A* is called a best-first search or an informed-search algorithm. - This is because it takes into account a goal for working out which node to select next. - In a game our goal is the destination we want to get to quickest. - It does this by determining a cost for a node traversal based on whether it best meets the goal. - We can use different heuristics to evaluate these costs. - We will just use Euclidean (straight-line) distance. --- # A* Similarity to Dijkstra - Dijkstra is a special case of A*, where the heuristic is zero - The algorithm is identical to Dijkstra, except a few points: - The priority queue uses a *combined cost* - The combined cost is the sum of the total travel cost from start to point, plus the heuristic - The heuristic is the estimated cost from point to goal - Typical terminology: f/g/h - h: heuristic function - g-score: tentative cost from start to current node - f-score: g-score plus the heuristic value --- # A* - As stated, A* is a best-first search algorithm. - This means it doesn't select a shortest path from where it is, but chooses a node that looks like a better choice towards the goal. - However, in the worst-case A* still might have to search the entire graph. - We still have $\mathcal{O}(\lvert V \rvert^2)$ complexity. ![image](assets/images/astar.png) --- # Example - A* at Work
--- # Heuristics - There are different heuristics we can use to make the pathfinding act in a different manner. - The one we will use is Euclidean distance (straight line): $$h = destination - position $$ - Use if you're not limited to grid-based movement --- # Heuristics - Another is Manhattan distance: $$ d = destination - position $$ $$ h = \lvert d.x \rvert + \lvert d.y \rvert $$ - Use when you can only move in a cardinal direction on a grid - Chebyshev distance is similar to Manhattan but allows diagonal movement: $$ d = destination - position $$ $$ h = \max(\lvert d.x \rvert + \lvert d.y \rvert) $$ - Use when you can only move in cardinal or diagonal directions on a grid --- # A* versus Dijkstra
--- ## Pathfinding and Steering --- # Output from Pathfinding - The output from a path finding or a path planning operation is called a *path* or *walk*. - There are different approaches we can take in a game: - A series of directions of travel (useful for discrete movement). - A list of nodes to visit (better for continuous movement). - We will take the latter approach. ![image](assets/images/graph-walk.png) --- # Pathfinding and Steering - Our aim is to use pathfinding as a decision making process for our movement. - The basic idea is that we have a starting position and a target position. - We use pathfinding to make a decision about how to move to the target position. - The list of nodes to visit then allows us to traverse the map using a steering behaviour. - The simplest approach is just to use an arrive behaviour for each node. Seeking might cause bouncing. - Combining steering behaviours, pathfinding, and physics will give you all the movement behaviour you need. --- ## Summary --- # Other Techniques - We have only looked at the main technique used in games but there are other considerations. - Jump Point Search: optimisation to A* for uniform-cost grids - Algorithm considers "jumps" along straight lines in the grid - HPA*: hierarchical variant - Break map into chunks, identify chunk entries/exits, precompute paths per chunk and run a multi-resolution search at runtime ![image](assets/images/hpastar.png) --- # Summary - We've only covered the basics of pathfinding, but this is enough for what you need. - Pathfinding is really about finding the least expensive path to a destination. - This can obviously change based on the map changing. - Our use of pathfinding will get a list of nodes to visit and the subsequent use of this information to move a character around. - The lab will provide you with an algorithm that will work in the tile engine, but you should be able to extract the core idea if you need to.