The problem we’re trying to solve is moving a game object from its starting point to its destination. The goal of Pathfinding is to find a good path — avoiding obstacles, enemies, and minimizing the cost (fuel, time, distance, equipment, money, etc.). The goal of Movement, “Movement,” is to find a path and travel along it. It is possible to focus on only one of these approaches. At one extreme, when a game object starts to move, a sophisticated pathfinder with a trivial movement algorithm can find a path, and the game object will move along that path and ignore everything else. At the other extreme, a pure motion-only system would not search for a path (the original “path” would be replaced by a straight line), but instead would take only one step at each node, taking into account the surrounding environment. For best results, use both Pathfinding and movement algorithm.

 

 

 

1 introduction

Moving a simple object seems easy. And path searching is complicated. Why does it bother when it comes to path searching? Consider the following:

 

The object (Unit) is initially at the bottom of the map and tries to move to the top. There is nothing in the area the object scans (in pink) to show that it cannot move up, so it keeps moving up. Near the top, it detects an obstacle and changes direction. Then it follows the U-shaped obstacle to its red path. In contrast, a Pathfinder will scan a larger area (the light blue part), but it will find a shorter path (the blue path) without letting the unit go to the concave obstacle.

However, you can extend a motion algorithm to deal with the obstacles shown above. Either avoid creating concave obstacles, or mark concave exits as dangerous (enter only if the destination is inside):

 

Rather than waiting until the last minute to find a problem, pathfinder lets you plan ahead. Movement without path search can work in many situations and can be extended to many more, but path search is a more common way to solve more problems.

1.1 algorithm

Path-searching algorithms in computer science textbooks work from a mathematical perspective on a graph — a collection of nodes linked by edges. A game map based on tiles can be viewed as a graph, with each tile being a node and an edge drawn between each tile:

 

For now, I’ll assume we’re using a two-dimensional grid. Later I’ll discuss how to create other types of graphs outside of your game.

Much of the path-searching algorithms in AI or algorithmic research were designed based on arbitrary graphs, rather than grid-based graphs. We can find something that uses the features of grid maps. There are some things that we think are common sense that the algorithm doesn’t understand. For example, we know something about direction: In general, the farther apart two objects are, the more time it takes to move one toward the other; And we know that there are no secret passages on the map that lead from one point to another. (I’m assuming there isn’t. If there is, it’s hard to find a good path because you don’t know where to start.)

1.2 Dijkstra algorithm and best first search

Dijkstra accesses nodes in the graph, starting at the initial point where the object is located. It iteratively checks the nodes in the node set to be checked and adds the nearest unchecked node to the node set to be checked. The set of nodes expands outward from the initial node until it reaches the target node. Dijkstra’s algorithm is guaranteed to find a shortest path from the initial point to the target point, as long as all the edges have a non-negative substitution value. (I say “shortest path” because there are often many similarly short paths.) In the figure below, the pink nodes are the initial nodes, the blue ones are the target points, and the colored, diamond-like teal areas are the areas scanned by Dijkstra. The lightest areas are those farthest from the initial point, and thus form the frontier of exploration:

 

The best-first search (BFS) algorithm follows a similar process, except that it is able to estimate (called heuristic) the cost of getting any node to the target point. Instead of choosing the node closest to the original node, it chooses the node closest to the target. BFS does not guarantee finding a shortest path. However, it is much faster than Dijkstra because it uses a heuristic function to quickly navigate to the target node. For example, if the target is south of the starting point, BFS will tend to follow a southerly path. In the figure below, the yellower nodes represent the higher heuristic value (high cost to move to the target), while the darker nodes represent the lower heuristic value (low cost to move to the target). This shows that BFS runs faster than Dijkstra.

 

However, both of these examples are just the simplest cases — there are no obstacles on the map and the shortest path is straight. Now consider the concave obstacle described earlier. Dijkstra is slower, but it does guarantee a shortest path:

 

BFS, on the other hand, runs faster, but the path it finds is obviously not a good one:

 

The problem is that BFS is based on a greedy strategy, it tries to move towards the target even though it’s not the right path. Since it considers only the cost of reaching its goal and ignores the costs already incurred in the present, it keeps going even though the path becomes long.

Wouldn’t it be better to combine the best of both? A* algorithm invented in 1968 is A combination of heuristic approaches such as BFS and conventional methods such as Dijsktra algorithm. Somewhat differently, heuristics like BFS often give an approximate solution rather than a guaranteed optimal solution. However, although A* is based on heuristics that do not guarantee the best solution, A* guarantees finding A shortest path.

1.3 A * algorithm

I’m going to focus on the A star algorithm. A* is the most popular choice for path searching because it is quite flexible and can be used in A wide variety of situations.

Like other graph search algorithms, A* potentially searches A large area of the graph. Like Dijkstra, A* can be used to search for shortest paths. Like BFS, A* can guide itself with heuristic functions. In simple cases, it is just as fast as BFS.

 

In the case of A concave obstacle, A* finds A path as good as Dijkstra’s:

 

The secret to its success is that it combines blocks of information from Dijkstra (nodes near the initial point) and BFS (nodes near the target point). In standard terms for discussing A*, g(n) denotes the cost of going from the initial node to any node N, and h(n) denotes the heuristic estimated cost of going from the node N to the target point. In the figure above, yellow(h) represents the node away from the target and teal(g) represents the node away from the initial point. A* weighs the two as you move from the initial point to the target point. Every time we go through the main loop, it checks the lowest node of f(n), n, where f(n) = g(n) + h(n).

2 heuristic algorithm

The heuristic function H (n) tells A* the estimate of the minimum cost from any node N to the target node. Choosing a good heuristic function is important.

2.1 A* use of heuristic functions

Heuristic functions can control the behavior of A* :

  • In an extreme case, if h(n) is zero, then only g(n) matters, then A* evolves into Dijkstra, which is guaranteed to find the shortest path.
  • If h(n) is often less than (or equal to) the actual cost of moving from n to the target, then A* is guaranteed to find A shortest path. The smaller h(n), the more nodes A* extends, the slower it’s going to run.
  • If h(n) is exactly equal to the cost of moving from n to the target, then A* will just find the best path and not extend any other nodes, which will run very fast. Although this may not happen in all cases, you can still make them exactly equal in some special cases. It’s good to know that A* will work perfectly as long as you give it the perfect information.
  • If h(n) is sometimes higher than the actual cost of moving from n to the target, then A* is not guaranteed to find A shortest path, but it runs faster.
  • At the other extreme, if h(n) is much larger than g(n), then only h(n) matters and A* evolves into the BFS algorithm.

So we have an interesting situation, where we can decide what we want to get out of A star. At exactly the right point, we want to get the shortest path as quickly as possible. If we aim too low, we’ll still get the shortest path, but at a slower pace; If we aim too high, then we abandon the shortest path, but A* runs faster.

In games, this feature of A* is very useful. For example, you may find that in some cases you want a “good” path rather than a “perfect” path. In order to weigh g(n) and h(n), you can modify either one.

Note: Academically, A* algorithm is called simply A if the value of the heuristic function is an underestimate of the actual cost. However, I continue to call it A* because the implementation is the same and there is no difference between A and A* in the field of game programming.

2.2 Speed or accuracy?

The ability of A* to change its own behavior is based on heuristic cost functions, which are very useful in games. A compromise between speed and accuracy will make your game run faster. In many games, you don’t really need the best path, just an approximation. What you need depends on what’s happening in the game, or how fast the machine is running the game.

Suppose your game has two terrains, plains and mountains. The cost of moving in the plains is 1 and in the mountains it is 3. A* is going to search three times as far along flat land as it does along mountainous land. This is because there may be a path along the plains to the mountains. Setting the evaluation distance between the two adjacent points to 1.5 can accelerate the search process of A*. And then A star will compare 3 to 1.5, which is no worse than comparing 3 to 1. It is not as dissatisfied with mountainous terrain, so it won’t spend as much time trying to find a way around it. Alternatively, You can speed up A*’s search by the amount it searches for paths around mountains — just tell A* that movement cost on mountains is 2 instead of 3. Now it will search only twice as far along the flat terrain as along mountainous terrain. Either approach gives up ideal paths to get something quicker.

The choice between speed and accuracy is not static. You can dynamically select based on CPU speed, the number of slices of time spent searching for paths, the number of units on the map, the importance of the object, the size of the group, the difficulty, or any other factor. One way to achieve a dynamic compromise is to establish a heuristic function that assumes that the minimum cost of passing through a grid space is 1, and then establish a cost function for measuring (scales) :

G ‘(n) = 1 + alpha * (g(n) — 1)

If alpha is 0, then the value of the improved cost function is always 1. In this case, the terrain cost is completely ignored, and the A* job becomes simply to determine whether A grid can pass. If alpha is 1, then the original cost function will work, and then you get all the advantages of A*. You can set alpha to any value from 0 to 1.

You can also consider making a choice about the return value of the heuristic: the absolute minimum cost or the expected minimum cost. For example, if most of the terrain on your map is grass at cost 2 and some of the rest is roads at cost 1, then you might consider having the heuristic function return 2* distances instead of roads.

The choice between speed and accuracy is not global. There are certain areas of the map where accuracy is important and you can make dynamic choices based on that. For example, if we might stop recalculating the path or change direction at some point, it is more important to choose a good path near the current position, so why bother with the accuracy of subsequent paths? Or, for a safe area on a map, the shortest path may not be very important, but when escaping from an enemy village, safety and speed are of Paramount importance. In safe areas, it is possible to consider taking an approximate path rather than an exact shortest path, so that pathfinding is fast. But in dangerous areas, the safety and speed of escape are important, that is, the accuracy of the path is important, so you can spend more time finding the exact path.

2.3 Units of Measurement

A* calculates f(n) = g(n) + h(n). In order to add these two values, they must use the same unit of measure. If g(n) is measured in hours and h(n) is measured in meters, then A* will think that g or h is too big or too small, so you won’t get the right path, and your A* algorithm will run slower.

2.4 Precise heuristic functions

If your heuristic is exactly equal to the actual optimal path, as shown in the figure in the next section, you will see that there will be very few nodes for the A* extension. What happens inside the A* algorithm is that at every node it calculates f(n) = g(n) + h(n). When h(n) matches exactly g(n), the value of f(n) will not change along the path. All nodes that are not on the right path have an f value greater than the one on the right path. If there are already nodes with lower f values, A* will not consider nodes with higher f values, so it will certainly not deviate from the shortest path.

2.4.1 Exact heuristic function of precalculation

One way to construct an exact heuristic function is to precompute the length of the shortest path between any pair of nodes. In many game maps this is not feasible. Then, there are several ways to approximate this heuristic function:

(Translator: This is not easy to translate, the original for now)

Then an heuristic function h ‘is added to evaluate the cost of getting to a nearby waypoint from any location. (The latter can also be calculated by preestimation, if desired.) The final heuristic function can be:

h(n) = h'(n, w1) + distance(w1, w2), h'(w2, goal)

Or if you want a better but more expensive heuristic, evaluate the above equation with all w1 and w2 pairs near the node and the target, respectively. Translator: Or if you want a better but more expensive heuristic, evaluate the above with all pairs w1, W2 That are close to the node and the goal, respectively.)

2.4.2 Linear exact heuristic algorithm

In special cases, you can make the heuristic function precise without precalculation. If you have an obstacle free and slow terrain, the shortest path from the initial point to the target should be a straight line.

If you are using a simple heuristic function (we don’t know the obstacles on the map), it should match the exact heuristic function. If not, then you have a problem with units of measure, or the type of heuristic function you choose.

2.5 Heuristic algorithm in grid map

In grid maps, there are some well-known heuristic functions.

2.5.1 Distance from Manhattan

The standard heuristic function is Manhattan Distance. Consider your cost function and find the minimum cost D of moving from one location to a neighboring location. Therefore, the heuristic function in my game should be D times the distance to Manhattan:

H(n) = D * (abs (n.x — goal.x) + abs (n.y — goal.y))

You should use units of measure that match your cost function.

 

(Note: the above image has a tie-breaker added to the heuristic.}

(translator note: Manhattan distance – two in latitudinal direction combined with distance, in what direction the D (I, J) = | XI – XJ | | + YI – YJ |. For one who has a south north, east direction rules is layout of urban streets, the distance from one point to another point is in the north and the south on upward travel distance and in what direction of travel distance so the Manhattan distance is also called a taxi distance, Manhattan distance is not distance invariant, when axis changes, the distance between points is different – baidu know)

2.5.2 Diagonal distance

If you allow diagonal motion in your map then you need a different heuristic function. (4 east, 4 north) the distance to Manhattan will become 8*D. However, you can simply move (4 northeast) instead, so the heuristic function should be 4*D. This function uses the diagonal, assuming the cost of both the line and the diagonal is D:

h(n) = D * max(abs(n.x – goal.x), abs(n.y – goal.y))

 

If the cost of the diagonal motion is not D, but is similar to D2 = SQRT (2) * D, then the above heuristic function is not accurate. You need something a little more sophisticated:

h_diagonal(n) = min(abs(n.x – goal.x), abs(n.y – goal.y))

h_straight(n) = (abs(n.x – goal.x) + abs(n.y – goal.y))

h(n) = D2 * h_diagonal(n) + D * (h_straight(n) – 2*h_diagonal(n)))

Here, we calculate H_diagonal (n) : the number of steps you can move along the diagonal; H_straight (n) : Manhattan distance; Then combine these two terms, multiply all the slash steps by D2, and multiply all the remaining straight steps (note that this is the number of steps for the Manhattan distance minus 2 times the slash steps) by D.

2.5.3 Euclidean distance

If your units can be moved at any Angle (other than in the direction of the grid) then you might want to use straight-line distances:

h(n) = D * sqrt((n.x-goal.x)^2 + (n.y-goal.y)^2)

However, if this is the case, you will run into trouble when using A* directly, because the cost function g will not match inspire the function h. Because the Euclidean distance is shorter than both the Manhattan distance and the diagonal distance, you can still get the shortest path, but A* will run longer:

 

Euclidean distance after 2.5.4 square

I’ve seen some A* web pages that let you avoid expensive square roots of Euclidean distances by using the square of the distance:

h(n) = D * ((n.x-goal.x)^2 + (n.y-goal.y)^2)

Don’t do it! This obviously leads to problems with units of measurement. When A* calculates f(n) = g(n) + h(n), the distance squared will be much more expensive than g, and you will stop because the heuristic function evaluation is too high. For longer distances, doing this would approach the extreme case of g(n) without counting anything, and A* would degenerate into BFS:

 

2.5.5 Breaking ties Breaking ties

One reason for poor performance comes from the ties heuristic function. Some paths are explored when they have the same f value, although we only need to search for one of them:



Ties in f values.

To solve this problem, we can add a small tie breaker to the heuristic function. The added value must be deterministic for the node (that is, it cannot be a random number), and it must differentiate f values. Because A* sorts f values, making f values different means that only one “Equivalent” f value will be tested.

One way to add value is to nudge the h’s units of measure a little. If we reduce the scale it Ethiopia, then the f note will increase gradually as we move toward the goal. Unfortunately, this means that A* tends to expand to nodes closer to the original point than to nodes closer to the target. One could add scale it Ethiopia (even 0.1%) and A* would tend to scale up towards the goal.

Heuristic *= (1.0 + p)

The factor p is selected such that p < the minimum cost of moving one step/the expected longest path length. Assuming you don’t want your path to be more than 1000 steps, you can make P = 1/1000. The result of this added value is that A* has fewer nodes to search for than before.



Tie-breaking scaling added to heuristic.

When obstacles are present, of course you still need to find your way around them, but be aware that when you get around them, the area A* will search is very small:



Tie-breaking scaling added to heuristic, works nicely with obstacles.

Steven Van Dijk suggests that a more straightforward approach is to pass H to a comparison function. When f values are equal, the comparison function checks for h and then adds value.

A different way to add value is to prefer the line (line) from the initial point to the target point:

dx1 = current.x – goal.x

dy1 = current.y – goal.y

dx2 = start.x – goal.x

dy2 = start.y – goal.y

cross = abs(dx1*dy2 – dx2*dy1)

Heuristic + * 0.001 = cross

This code calculates the vector cross-product between the start to goal vector and the current point to goal vector. When these vectors don’t line up, the cross product will be larger. As a result, this code chooses a path that slightly favors the line from the initial point to the target point. When there are no obstacles, A* not only searches very little area, but the path it finds looks pretty good:



Tie-breaking cross-product added to heuristic, produces pretty paths.

However, because this added value tends to be a straight path from the initial point to the target point, strange results can occur when obstacles appear (note that this path is still the best, but it just looks strange) :



Tie-breaking cross-product added to heuristic, less pretty with obstacles.

In order to interactively study the value-added method improvement, please refer to James Macgill A * is an applet (www.ccg.leeds.ac.uk/james/aStar… A* “approach, you’ll see the added value. When you use the “Fudge” method, you should see the result of adding the cross product to the heuric function above.

Another way to add value, however, is to carefully construct your A* priority queue so that newly inserted nodes with A special F value are always better than previously inserted nodes with the same F value.

You may also want to see more flexible (translator note: the original for sophisticated) add additional value of AlphA * algorithm (home1. Stofanet. Dk/breese/pape…

2.5.6 Area search

If you want to search for any uncertain node in the vicinity of the target, instead of a specific node, you should create an heuristic function h ‘(x) such that h’ (x) is h1(x), H2 (x), h3(x)… And these h1, H2, and H3 are heuristic functions of neighboring nodes. However, A faster approach is to have A* search only in the center of the target area. Once you get any node from the OPEN set adjacent to the target, you can stop searching and build a path.

3 Implementation notes

3.1 general

The A* algorithm is fairly simple, if you ignore the actual implementation code. There are two sets, the OPEN set and the CLOSED set. Where the OPEN set holds the nodes to be examined. At the beginning, the OPEN set contains only one element: the initial node. The CLOSED set holds the nodes that have been examined. At first, the CLOSED set is empty. If the graph is drawn, the OPEN set is the frontier and the CLOSED set is the interior of the region. Each node also holds a pointer to its parent so we know how it was found.

The best node n (the node with the smallest f value) is repeatedly fetched from the OPEN set in the main loop and checked. If n is the target node, then we’re done. Otherwise, node N is removed from the OPEN set and added to the CLOSED set. Then check its neighbor n ‘. If the neighbor n ‘is in the CLOSED set, then it is already checked, so we do not need to consider it *; If n prime is in the OPEN set, then it’s definitely going to be checked later, so we’re not going to think about it now. Otherwise, add it to the OPEN set, and set its parent to n. The cost g(n ‘) of the path to n ‘is set as g(n) + movementcost(n, n’).

clear; G= [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 1 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0. 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0. 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0, 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, 0, 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0. 0 0 0 0 0 0 1 1 1 0 0 1 1 0 0 0 0 0 0, 0 1 1 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0. 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0. 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0, 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0. 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0; 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0. 0 0 1 1 0 0 0 0 0 0 to 1 1 1 1 1 1 1 1 1 0; 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0. 0 0 0 0 0 0 1 1 1 1 0 0 0 1 0 0 0 0 0 1; 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 0 0, 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0, 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]; Ditu. Qishi = [1, 1]; Ditu. Mubiao = [20]; ditu.daxiao=20; zhangai=[]; for i=1:ditu.daxiao for j=1:ditu.daxiao if (G(i,j)==1) zhangai=[zhangai;j,i]; end end end path=Astar(zhangai,ditu); b = G; b(end+1,end+1) = 0; figure(4); colormap([1 1 1;0 0 0]); % create color pcolor (0.5: size (G, 2) + 0.5, 0.5: size (G, 1) + 0.5, b); % to grid color set (gca, 'XTick', 1: size (G, 1), 'YTick, 1: size (G, 2)); % set axis image xy; % Use the same data unit along each axis, keep consistent; plot(path(:,1),path(:,2),'-m','linewidth',2); A = path (1, 3);Copy the code