400 likes | 563 Views
Lecture 20 – Pathfinding. CS 490/590 Wesley Kerr. Acknowledgements. Ian Millington. Artificial Intelligence for Games . Morgan Kaufman Publishers. 2006 images and organization Mat Buckland. Programming Game AI By Example . Jones & Bartlett Publishers. 2004
E N D
Lecture 20 – Pathfinding CS 490/590 Wesley Kerr
Acknowledgements • Ian Millington. Artificial Intelligence for Games. Morgan Kaufman Publishers. 2006 • images and organization • Mat Buckland. Programming Game AI By Example. Jones & Bartlett Publishers. 2004 • Amit’s Game Programming Information • http://www-cs-students.stanford.edu/~amitp/gameprog.html • http://theory.stanford.edu/~amitp/GameProgramming/index.html
AI Model AI gets information AI gets a slice of processor time Pathfinding AI turned into on-screen action
Uniform-cost Search (1,1)
Uniform-cost Search (1,1) up left down right (0,1) (1,2) (2,1) (1,0)
Uniform-cost Search (1,1) up left down right (0,1) (1,2) (2,1) (1,0) up down (0,2) (0,0)
Uniform-cost Search (1,1) up left down right (0,1) (1,2) (2,1) (1,0) up down left down right (0,2) (1,3) (2,2) (0,2) (0,0)
Best First Search • Also known as “heuristic search” • A heuristic is a rule or set of rules that serves as a useful guide for finding good paths • The name “best first” refers to the choice to explore the node with the best “score” first • The heuristic in “heuristic search” refers to the choice to explore the best “score” first • An evaluation function f(n) assigns a score to each node • Two lists are maintained open and closed • The algorithm chooses the best node from the unexplored nodes in the open list
Best First Search function best-first (G, start, goal, evaluate) start.value = evaluate(start) open = { start } // priority queue sorted by value score closed = { } whileopen is not empty node = open.pop() closed.add(node) if(node == goal) return solution found by backtracking (node.cameFrom()) foreachn in node.getNeighbors() if(n in closed) if (n.cost < closed.get(n).cost) closed.get(n).cost = n.cost closed.get(n).cameFrom(node) else n.value = evaluate(n) n.cameFrom(node) open.add(n) returnfailure
Evaluate • How do we estimate the best node? • What about using the cost so far? • g(n) is the cost so far so f(n) = g(n) • Uniform-cost search • What about estimating the remaining distance to the goal? • h(n) is a heuristic that estimates the remaining distance to the goal, so f(n) = h(n) • This form of best-first search is also known as “greedy” search since it selects the node that is supposed to be closest to the goal • What makes a good heuristic? • We will come back to that…
Best First Search h(n) = Euclidean distance between states (1,1) – h(n) = 5
Best First Search h(n) = Euclidean distance between states (1,1) – h(n) = 5 (0,1) – h(n) = 5.83 (1,0) – h(n) = 5.66 (2,1) – h(n) = 4.24 (1,2) – h(n) = 4.47
Best First Search h(n) = Euclidean distance between states (1,1) – h(n) = 5 (0,1) – h(n) = 5.83 (1,0) – h(n) = 5.66 (2,1) – h(n) = 4.24 (1,2) – h(n) = 4.47 (2,0) – h(n) = 5 (3,1) – h(n) = 3.61 (2,2) – h(n) = 3.61
Best First Search h(n) = Euclidean distance between states (1,1) – h(n) = 5 (0,1) – h(n) = 5.83 (1,0) – h(n) = 5.66 (2,1) – h(n) = 4.24 (1,2) – h(n) = 4.47 (2,0) – h(n) = 5 (3,1) – h(n) = 3.61 (2,2) – h(n) = 3.61 (3,0) – h(n) = 4.47 (4,1) – h(n) = 3.16 (3,2) – h(n) = 2.83
Best First Search h(n) = Euclidean distance between states (1,1) – h(n) = 5 (0,1) – h(n) = 5.83 (1,0) – h(n) = 5.66 (2,1) – h(n) = 4.24 (1,2) – h(n) = 4.47 (2,0) – h(n) = 5 (3,1) – h(n) = 3.61 (2,2) – h(n) = 3.61 (3,0) – h(n) = 4.47 (4,1) – h(n) = 3.16 (3,2) – h(n) = 2.83 (2,2) – h(n) = 3.61 (3,3) – h(n) = 2.24
Best First Search h(n) = Euclidean distance between states (1,1) – h(n) = 5 (0,1) – h(n) = 5.83 (1,0) – h(n) = 5.66 (2,1) – h(n) = 4.24 (1,2) – h(n) = 4.47 (2,0) – h(n) = 5 (3,1) – h(n) = 3.61 (2,2) – h(n) = 3.61 (3,0) – h(n) = 4.47 (4,1) – h(n) = 3.16 (3,2) – h(n) = 2.83 (2,2) – h(n) = 3.61 (3,3) – h(n) = 2.24 (2,3) – h(n) = 3.16 (3,4) – h(n) = 2.0
Best First Search h(n) = Euclidean distance between states (1,1) – h(n) = 5 (0,1) – h(n) = 5.83 (1,0) – h(n) = 5.66 (2,1) – h(n) = 4.24 (1,2) – h(n) = 4.47 (2,0) – h(n) = 5 (3,1) – h(n) = 3.61 (2,2) – h(n) = 3.61 (3,0) – h(n) = 4.47 (4,1) – h(n) = 3.16 (3,2) – h(n) = 2.83 (2,2) – h(n) = 3.61 (3,3) – h(n) = 2.24 (2,3) – h(n) = 3.16 (3,4) – h(n) = 2.0
Problems Greedy Best First Search Cheap but not optimal Uniform-cost Search Optimal but expensive
A* • Developed in 1968 • Combines heuristic based approach from best first search with formal approaches like uniform-cost search (Dijkstra) • f(n) = g(n) + h(n) • Standard algorithm used for pathfinding in games • Problem: • Given a source vertex and a goal vertex • Find the shortest path between them • Like Dijkstra we keep track of the cost so far • We include a heuristic to estimate the remaining distance to the goal vertex • Good heuristics make the search more efficient
Algorithm • Initialize CLOSED list to an empty list • Add start onto the OPEN list • while open is not empty • Select node n from the OPEN list with minimum f(n) • Add n to the CLOSED list • ifn is equal to the goal state • return the path found from backtracking • for each neighbor n’ of n • Compute g(n’) = g(n) + c(n, n’) • if n’ is on the open and has cost greater than g(n’) • Remove n’ from the open list, update f(n’), add back to OPEN list • Set parent of n’ to be n • ifn’ is on the closed list and has cost greater than g(n’) • Remove n’ from the closed list, update f(n’), add back to OPEN list • Set parent of n’ to be n • if n’ is not on the OPEN list or the CLOSED list • Compute h(n’), f(n’) = g(n’) + h(n’) • Put n’ onto the open list • Set parent of n’ to be n • return failure
Example A* Search E was closed, but we found a new route from C that is shorter, so it is reopened.
Tuning A* algorithmic performance • Focusing on the two sets OPEN and CLOSED • What operations are performed… • OPEN set • Find the best node and remove it (main loop) • Check whether or not a node is in the set (neighbors) • Insertion of new nodes (neighbors) • Adjust the value f(n) – remove node and reinsert it (neighbors) • CLOSED set • Check whether or not a node is in the set (neighbors) • Insertion of new nodes (neighbors) • Removal of nodes (neighbors)* • For the CLOSED set, use a Hashtable * This should not happen with a monotonic admissible heuristic
Tuning A* algorithmic performance • OPEN set • Find the best node and remove it (main loop) • Check whether or not a node is in the set (neighbors) • Insertion of new nodes (neighbors) • Adjust the value f(n) – remove node and reinsert it (neighbors) • Comparisons in terms of the size of the fringe (F) • Unsorted Array / Linked List • find-element: O(F), insert: O(1), find-best: O(F) • remove-best: O(F) for array and O(1) for linked list • adjust: O(F) to find and O(1) to update • Sorted Array • find-element: O(log F), insert: O(F), find-best: O(1), remove-best: O(1) • adjust: O(log F) to find and O(F) to update (change value / position) • Sorted Linked List • find-element: O(F), insert: O(F), find-best: O(1), remove-best: O(1) • adjust: O(F) to find and O(1) to update • Hash table • find-element: O(1), insert: O(1), find-best: O(F), remove-best: O(F), adjust: O(1) • Binary heap • find-element: O(F), insert: O(log F), find-best: O(1), remove-best: O(log F) • adjust: O(F) to find and O(log F) to update • Hybrid (Hash table and Binary heap)
A* Notes • A* computes f(n) = g(n) + h(n) • Make sure that g(n) and h(n) are in the same scale otherwise A* will prefer one over the other. • For example, you don’t want g(n) in hours and h(n) in meters • Let h*(n) = the true cost of getting from node n to the goal • h(n) is an admissible heuristic when h(n) ≤ h*(n) • h(n) never overestimates the cost from the node to the goal • h(n) ≥ 0, i.e. cannot travel back in time • With an admissible heuristic, we can prove that the first solution found is an optimal solution • Take an AI course and most of them will make you perform this proof, but I will not nor will I present it
Heuristics • The more accurate your heuristic function is, the faster the search algorithm will run • Perfect Heuristic: for all n: h(n) = h*(n) • Only nodes on optimal path will be explored • No extra work performed • Null Heuristic: for all n: h(n) = 0 • Admissible heuristic since it underestimates the cost • A* behaves like uniform-cost search • Comparing Two Heuristics • If h1(n) < h2(n) ≤ h*(n) for all non-goal nodes, then h2(n) is better than h1(n) • We can define a suboptimality score for heuristic functions • For all nodes n: h*(n) – h(n) = δ • You want the heuristic that gets δas close as possible to zero for all nodes • Overestimating • If your heuristic overestimates the cost to the goal, then you will not be guaranteed to find an optimal solution, but you will have a path quicker
Grid Heuristics • Use a distance heuristic that matches the movement • On a square grid with 4 directions of movement use Manhattan distance • On a square grid with 8 directions of movement use Diagonal distance • On a square grid that allows any direction of movement try Euclidean distance
Manhattan Distance • Also called taxi-cab distance • Admissible Heuristic • Let D be the minimum cost for moving between one space to another space in your grid (D is often 1) • The Manhattan Distance between n1 and n2 is
Diagonal Distance • Sometimes called Chebyshev distance • If the cost for moving diagonally is equivalent to the cost for moving in a straight line (D), then you could continue to use the Manhattan Distance • If the cost of movement is not D, but instead something like √2*D then you want something more sophisticated • The Diagonal Distance between n1 and n2 is
Euclidean Distance • As the “crow flies” distance between two points • Admissible heuristic • Works very well in outdoor settings with few obstacles • Suffers on indoor levels since it ignores obstacles and walls • The Euclidean Distance is
Breaking Ties • Often there are many paths with the same length • Consider outdoor maps with flat terrain • May lead to exploring nodes unnecessarily • The tie breaker must be deterministic and it must make the f(n) values different for two nodes • This allows us to prefer one over the other • To break ties we can adjust the values for g(n) or h(n) • We tend to prefer to update h(n) since it is a heuristic • Option 1: • Select a value for p < (min cost of 1 step) / (expected max path length) • Option 2: • Prefer paths that are along the straight line from the starting point to the goal • Let start be the starting point of the search and node be the current node
Cluster Heuristic • Works better in indoor environments • Group nodes together into clusters • Highly connected area of the graph • Can be automatically determined with graph algorithms • If start and goal are within the same cluster rely on Euclidian distance otherwise return the distance between the clusters.
Pathfinding and the Game Loop • If possible, move pathfinding to a separate thread • With an event-driven architecture this is easier • Have an AI component that determines where the agent wants to go and generate a PathPlanning event that contains: the agent, the start location, and the desired goal • A PathfindingSubsystem will receive that event and process it in a separate thread. • Alternatively think of time-slicing • Time-slicing involves modifying the search algorithms • Each algorithm consists of the following cycle • Grab the next node from the priority queue • Add the node closed list • Test to see if the node is the goal • If not the goal, examine all connected nodes and add them to the open list where appropriate • If it is the goal, then return successful path • Construct a manager that keeps track of all running search requests. • Each update, allocate search cycles uniformly to all waiting search requests • Again, use the event system to request new searches and dispatch results
In the meantime… • While waiting for a search to finish, the agent shouldn’t sit idle • Good heuristic is to start moving in the direction of the goal. • Alternatively the PathfindingSubsystem can run the search for a short period of time and return a partial path • This could be why you see agents stuck in RTS games • They are waiting for a good path to follow and in the meantime they are wandering into walls
Group movement • Often in RTS games, players will select a group of characters to move to a single destination • It is likely that one path could be used for all of the characters • Do the expensive path planning for the entire group originating from a central location • For each character replace the start of the path with it’s own individual path • The beginning of the path is unique to a character • The remaining portion of the path is shared • Often the user is unimpressed to see all of his characters following the exact same path • So add in some noise to the nodes for each of the agents • Partly through the path for some of the agents, select a different successor node • Alternatively, flocking behaviors with leaders works well • Generate one path for the leader • All of the other characters are minions who follow the leader