■实施上的注意事项
Notes on Implementation
现在您了解了基本的方法,当你编写自己的程序时,有一些额外的事情要考虑。下面给出我用C ++和Blitz Basic编写的程序,用其他语言也同样有效。
Now that you understand the basic method, here are some additional things to think about when you are writing your own program. Some of the following materials reference the program I wrote in C++ and Blitz Basic, but the points are equally valid in other languages.
1.其他单元(防止碰撞):如果你碰巧仔细看我的演示代码,你会注意到它完全忽略了屏幕上的其他单元。该单元直接通过对方。根据游戏,这可能是可以的,或者它可能是不可以的。如果你想考虑其他单元的路径搜索算法,并让它们彼此走动,另一个,我建议你只考虑要么在路径的计算,处理它们的当前位置为不能走时停止或相邻的路径搜索单元。对于正在相邻的单元,防止碰撞可以通过惩罚沿着各自的路径节点,从而鼓励单元路径搜索找到一个替代路径(在#2描述)。
1. Other Units (collision avoidance): If you happen to look closely at my example code, you will notice that it completely ignores other units on the screen. The units pass right through each other. Depending on the game, this may be acceptable or it may not. If you want to consider other units in the pathfinding algorithm and have them move around one another, I suggest that you only consider units that are either stopped or adjacent to the pathfinding unit at the time the path is calculated, treating their current locations as unwalkable. For adjacent units that are moving, you can discourage collisions by penalizing nodes that lie along their respective paths, thereby encouraging the pathfinding unit to find an alternate route (described more under #2).
如果你选择认为是移动和不相邻的寻路单元其他单元,你将需要开发一种方法来预测他们会在任何给定的时间点,使他们能够得到适当的回避。否则,你最终可能会用异样的路径,其中单位锯齿状,以避免其他单元,不存在了。
If you choose to consider other units that are moving and not adjacent to the pathfinding unit, you will need to develop a method for predicting where they will be at any given point in time so that they can be dodged properly. Otherwise you will probably end up with strange paths where units zig-zag to avoid other units that aren‘t there anymore.
你还会,当然,需要开发一些碰撞检测代码,因为再好的路径是在它的计算方法时,事情可能随时间而改变。当发生碰撞时的单位必须要么计算新的路径,或者如果其他单元被移动,它不是一个迎面碰撞,等待其他单元继续与当前路径之前到步骤闪开。
You will also, of course, need to develop some collision detection code because no matter how good the path is at the time it is calculated, things can change over time. When a collision occurs a unit must either calculate a new path or, if the other unit is moving and it is not a head-on collision, wait for the other unit to step out of the way before proceeding with the current path.
这些技巧可能是足以让你开始。如果您想了解更多,这里有一些你可能会发现有用的链接:
These tips are probably enough to get you started. If you want to learn more, here are some links that you might find helpful:
角色的转向行为:
http://www.red3d.com/cwr/steer/
克雷格雷诺的方向盘上的工作是从路径搜索有点不同,但它可以与路径搜索集成,以做出更完整的移动和防撞系统。
Steering Behavior for Autonomous Characters: Craig Reynold‘s work on steering is a bit different from pathfinding, but it can be integrated with pathfinding to make a more complete movement and collision avoidance system.
计算机游戏中的长短转向:
http://ducati.doc.ntu.ac.uk/uksim/uksim%2704/Papers/Simon%20Tomlinson-%2004-20/paper04-20%20CR.pdf
在转向和路径搜索文学的一个有趣的调查。这是一个PDF文件。
The Long and Short of Steering in Computer Games: An interesting survey of the literature on steering and pathfinding. This is a pdf file.
协调机组运行:
http://www.gamasutra.com/features/game_design/19990122/movement_01.htm
由帝国时代的设计师戴夫·乍首先在两部分组成的系列对形成和文章基于组的运动。
Coordinated Unit Movement: First in a two-part series of articles on formation and group-based movement by Age of Empires designer Dave Pottinger.
实现协调运动:
http://www.gamasutra.com/view/feature/3314/implementing_coordinated_movement.php
戴夫·乍的两部分组成的系列第二。
Implementing Coordinated Movement: Second in Dave Pottinger‘s two-part series.
2.多样的地形成本:在本教程中,我的程序随行,地形仅仅是两件事情之一 – 能走和不能走。但是,如果你有地形就是能走,但在更高的移动成本?沼泽,丘陵,地下城的楼梯,等等 - 这些都是地形是适合步行的所有例子,但在成本要比平坦,开阔地高。类似地,道路可能具有较低的运行成本比周围的地形。
2. Variable Terrain Cost: In this tutorial and my accompanying program, terrain is just one of two things – walkable or unwalkable. But what if you have terrain that is walkable, but at a higher movement cost? Swamps, hills, stairs in a dungeon, etc. – these are all examples of terrain that is walkable, but at a higher cost than flat, open ground. Similarly, a road might have a lower movement cost than the surrounding terrain.
这个问题很容易加入,当你计算任何给定节点的G值在地形成本处理。只是奖金的成本添加到这样的节点。在A *路径搜索算法已经写入寻找最低成本路径,应该很容易地处理这个问题。在简单的例子中,我描述的,当地势只是能走和不能走,A *将查找最短,最直接的途径。但是,在可变成本的地形环境,以最少的成本路径可能行走了较长的距离 - 就像把周围的沼泽道路,而不是直接通过它。
This problem is easily handled by adding the terrain cost in when you are calculating the G cost of any given node. Simply add a bonus cost to such nodes. The A* pathfinding algorithm is already written to find the lowest cost path and should handle this easily. In the simple example I described, when terrain is only walkable or unwalkable, A* will look for the shortest, most direct path. But in a variable-cost terrain environment, the least cost path might involve traveling a longer distance – like taking a road around a swamp rather than plowing straight through it.
一个有趣的附加考虑是什么专业人士称之为“影响映射。”正如上述的可变成本的地形,你可以创建一个额外的积分系统,并将其应用到AI的路径。试想一下,你有个一堆通过山区保护的单位地图。每当计算机通过通某人发送的路径上,它被重击。如果你愿意,你可以创建一个影响地图,处罚节点,其中大量的屠杀正在发生。这会教电脑偏好安全的路径,并帮助它避免愚蠢的情况下,它不断通过特定的路径出兵,只是因为它是短(但更危险)。
An interesting additional consideration is something the professionals call "influence mapping." Just as with the variable terrain costs described above, you could create an additional point system and apply it to paths for AI purposes. Imagine that you have a map with a bunch of units defending a pass through a mountain region. Every time the computer sends somebody on a path through that pass, it gets whacked. If you wanted, you could create an influence map that penalized nodes where lots of carnage is taking place. This would teach the computer to favor safer paths, and help it avoid dumb situations where it keeps sending troops through a particular path, just because it is shorter (but also more dangerous).
另一种可能的用途是惩罚沿着附近的移动台的路径节点。之一的A *的缺点之一是,当一组的单元的所有尝试找到的类似位置的路径,通常有一个显著程度的重叠,如一个或多个单元试图利用相同或类似的路由到它们的目的地。添加一个点球已经被其他单位声称“节点将有助于确保一定程度的分离,并减少冲突。不要把这样的节点作为不能走,但是因为你还愿意多台设备才能够挤过紧通道鱼贯,如果有必要的。此外,你应该只处罚的单元,附近的路径搜索单元,不是所有路径的路径,否则你会得到奇怪的躲避行为,避免单元的单元,都远不及他们当时的路径。此外,你应该只惩罚谎言沿着一条路径,而不是已经访问过并留下以前的路径节点的当前和未来的部分道路节点。
Yet another possible use is penalizing nodes that lie along the paths of nearby moving units. One of the downsides of A* is that when a group of units all try to find paths to a similar location, there is usually a significant amount of overlap, as one or more units try to take the same or similar routes to their destinations. Adding a penalty to nodes already ‘claimed‘ by other units will help ensure a degree of separation, and reduce collisions. Don‘t treat such nodes as unwalkable, however, because you still want multiple units to be able to squeeze through tight passageways in single file, if necessary. Also, you should only penalize the paths of units that are near the pathfinding unit, not all paths, or you will get strange dodging behavior as units avoid paths of units that are nowhere near them at the time. Also, you should only penalize path nodes that lie along the current and future portion of a path, not previous path nodes that have already been visited and left behind.
3.处理未探索区域:你曾经玩过一款PC游戏的计算机总是准确的知道路该如何走,即使地图没有探索?根据不同的游戏,那样的路径搜索太好可以是不现实的。幸运的是,这是可以很容易处理的问题。
3. Handling Unexplored Areas: Have you ever played a PC game where the computer always knows exactly what path to take, even though the map hasn‘t been explored yet? Depending upon the game, pathfinding that is too good can be unrealistic. Fortunately, this is a problem that is can be handled fairly easily.
答案是创建一个独立的“已知的通过性”阵列的每个玩家以及电脑对手的(每个玩家,不是每一个单元 - 那将需要更多的计算机内存)。每个阵列将包含有关该玩家已探索区域的信息,与地图的其他部分假设为适宜步行,直到证明并非如此。使用这种方法,单位将漫步死角,使类似的错误选择,直到他们发现周围的路。一旦地图探索,然而,路径搜索会正常工作。
The answer is to create a separate "knownWalkability" array for each of the various players and computer opponents (each player, not each unit -- that would require a lot more computer memory). Each array would contain information about the areas that the player has explored, with the rest of the map assumed to be walkable until proven otherwise. Using this approach, units will wander down dead ends and make similar wrong choices until they have learned their way around. Once the map is explored, however, pathfinding would work normally.
4.更平滑的路径:虽然A *会自动给出最短,成本最低的路径,它不会自动给出看起来最平滑的路径。看一看在我们的(图7)计算的例子最终路径。在该路径中,第一个步骤是下面,并开始方格的右侧。会不会我们的道路更顺畅,如果第一步是正下方的起点,而不是方形的方格?
4. Smoother Paths: While A* will automatically give you the shortest, lowest cost path, it won‘t automatically give you the smoothest looking path. Take a look at the final path calculated in our example (in Figure 7). On that path, the very first step is below, and to the right of the starting square. Wouldn‘t our path be smoother if the first step was instead the square directly below the starting square?
有几种方法来解决这个问题。当你正在计算路径,你可以处罚节点那里有方向的变化,增加了处罚他们的G值扣分。或者,你可以通过你的路径运行的计算后,寻找在那里选择相邻节点的地方会给你看起来更好的路径。欲了解更多关于整个问题,请向更加逼真路径搜索,一个(免费的,但需要注册)在Gamasutra.com上Macro Pinter的文章。
There are several ways to address this problem. While you are calculating the path you could penalize nodes where there is a change of direction, adding a penalty to their G scores. Alternatively, you could run through your path after it is calculated, looking for places where choosing an adjacent node would give you a path that looks better. For more on the whole issue, check out Toward More Realistic Pathfinding, a (free, but registration required) article at Gamasutra.com by Marco Pinter.
5.非方格搜索区域:在我们的例子中,我们使用了一个简单的二维方格布局。你并不需要使用这种方法。你可以使用不规则的形状区域。想想棋盘游戏的风险,以及国家在那场比赛。你可以设计一个路径搜索方案进行一场比赛那样。要做到这一点,你需要创建一个表,用于存储毗邻的国家,并与移动从一个国家到下一个相关的G值。你还需要拿出用于估计H.其他一切会被处理一样在上面的例子中的方法。而不是使用相邻的方格,你会简单地查找相邻国家在表中增加新的项目到开启列表时。
5. Non-square Search Areas: In our example, we used a simple 2D square layout. You don‘t need to use this approach. You could use irregularly shaped areas. Think of the board game Risk, and the countries in that game. You could devise a pathfinding scenario for a game like that. To do this, you would need to create a table for storing which countries are adjacent to which, and a G cost associated with moving from one country to the next. You would also need to come up with a method for estimating H. Everything else would be handled the same as in the above example. Instead of using adjacent squares, you would simply look up the adjacent countries in the table when adding new items to your open list.
同样,你可以创建一个固定的地形图路径的航点系统。航点通常走过的路径上的点,也许在一个地牢道路或隧道的关键。作为游戏设计者,你能预先指定这些路点。两个航点会被认为是“相邻”彼此是否有它们之间的直线路径上没有障碍。由于在风险的例子,您将节省在某种类型的查找表这个邻接信息,并用它生成新的开启列表项目时。那么你会(可能通过使用节点间的直线距离)和H成本(可能使用从节点到目标的直线距离)记录相关的G值。一切将继续如常。
Similarly, you could create a waypoint system for paths on a fixed terrain map. Waypoints are commonly traversed points on a path, perhaps on a road or key tunnel in a dungeon. As the game designer, you could pre-assign these waypoints. Two waypoints would be considered "adjacent" to one another if there were no obstacles on the direct line path between them. As in the Risk example, you would save this adjacency information in a lookup table of some kind and use it when generating your new open list items. You would then record the associated G costs (perhaps by using the direct line distance between the nodes) and H costs (perhaps using a direct line distance from the node to the goal). Everything else would proceed as usual.
阿米特Patel还写了一个简短的文章钻研一些替代品。对于使用非方形的搜索区域上等距RPG地图搜索的另一个例子,看看我的文章两个层次的A *路径搜索。
Amit Patel has written a brief article delving into some alternatives. For another example of searching on an isometric RPG map using a non-square search area, check out my article Two-Tiered A* Pathfinding.
6.一些超速提示:当你开发自己的A *程序,或者改编我写的,你最终会发现路径搜索使用你的CPU时间大幅大块,特别是如果你对路径搜索的一台像样的数目板和一个相当大的地图。如果你在网上读过的东西了,你会发现,这是真实的,即使谁设计像星际争霸或帝国时代游戏的专业人士。如果你看到的东西开始放缓,由于路径搜索,这里有一些想法,可能会加快速度:
6. Some Speed Tips: As you develop your own A* program, or adapt the one I wrote, you will eventually find that pathfinding is using a hefty chunk of your CPU time, particularly if you have a decent number of pathfinding units on the board and a reasonably large map. If you read the stuff on the net, you will find that this is true even for the professionals who design games like Starcraft or Age of Empires. If you see things start to slow down due to pathfinding, here are some ideas that may speed things up:
■考虑一个小地图或更少的单位。
Consider a smaller map or fewer units.
永远不要做路径搜索以上几个单元的时间。相反,把它们放在一个队列,它们分布在几个游戏循环。如果你的游戏时,比如说,每秒40个周期运行,没有人会注意到。但他们会发现,如果游戏似乎在一段时间放慢每一次当一束单位都计算路径在同一时间。
Never do path finding for more than a few units at a time. Instead put them in a queue and spread them out over several game cycles. If your game is running at, say, 40 cycles per second, no one will ever notice. But they will notice if the game seems to slow down every once in a while when a bunch of units are all calculating paths at the same time.
请考虑使用更大的方格(或者任何你正在使用的形状)为您的地图。这减少了搜索以找到的路径的节点的总数。如果你有雄心,可以设计出了用于在不同的情况下,这取决于路径的长度的两个或更多个路径搜索系统。这是专业人士做的,使用大面积的长路径,然后切换到更精细的使用较小的方格/地区搜索,当你接近目标。如果你有兴趣在这个概念,看看我的文章两个层次的A *路径搜索。
Consider using larger squares (or whatever shape you are using) for your map. This reduces the total number of nodes searched to find the path. If you are ambitious, you can devise two or more pathfinding systems that are used in different situations, depending upon the length of the path. This is what the professionals do, using large areas for long paths, and then switching to finer searches using smaller squares/areas when you get close to the target. If you are interested in this concept, check out my article Two-Tiered A* Pathfinding.
对于更长的路径,考虑修订是硬连接到游戏预先计算好的路径。
For longer paths, consider devising precalculated paths that are hardwired into the game.
考虑预处理地图找出哪些领域是从地图的其余部分无法访问。我把这些领域的“孤岛”。在现实中,他们可以是岛屿或者其他任何地区,是另有围墙关闭,无法访问。其中A *的缺点之一是,如果你告诉它来寻找路径等方面,它会搜索整个地图,停车,只有当每平方访问/节点已通过打开和关闭名单处理。这会浪费大量的CPU时间。它可以通过预先确定哪些地区是不可访问(通过洪水填充或类似的程序),记录在某种类型的阵列信息,然后在开始路径搜索前检查它来预防。
Consider pre-processing your map to figure out what areas are inaccessible from the rest of the map. I call these areas "islands." In reality, they can be islands or any other area that is otherwise walled off and inaccessible. One of the downsides of A* is that if you tell it to look for paths to such areas, it will search the whole map, stopping only when every accessible square/node has been processed through the open and closed lists. That can waste a lot of CPU time. It can be prevented by predetermining which areas are inaccessible (via a flood-fill or similar routine), recording that information in an array of some kind, and then checking it before beginning a path search.
在拥挤的,迷宫似的环境中,考虑节点标记不随地导致的死角。这些区域可以手动预先指定的地图编辑器,或者如果你有雄心的,你可以开发一个算法,自动识别等领域。在给定的死胡同区域节点的任何集合可以赋予一个唯一的识别号码。然后路径搜索时,只停下来考虑一个死胡同区域节点,如果起始位置或目的地恰好是在特定的死胡同区问题,你可以放心地忽略所有的死角。
In a crowded, maze-like environment, consider tagging nodes that don‘t lead anywhere as dead ends. Such areas can be manually pre-designated in your map editor or, if you are ambitious, you could develop an algorithm to identify such areas automatically. Any collection of nodes in a given dead end area could be given a unique identifying number. Then you could safely ignore all dead ends when pathfinding, pausing only to consider nodes in a dead end area if the starting location or destination happen to be in the particular dead end area in question.
7.维护开启列表:这实际上是A *路径搜索算法中最耗费时间的元素之一。您可以访问开启列表时,都需要找到具有最小F值的方格。有几种方法可以做到这一点。根据需要,你可以保存路径项目,每次当你需要找到最小F值的方格时,简单的遍历整个列表。这是简单的,但对于长路径很慢。这可以通过维护一个排序的列表,每次需要最小F-成本方形时间只需抓住了第一个项目从名单得到改善。当我写我的程序,这是我用第一种方法。
7. Maintaining the Open List: This is actually one of the most time consuming elements of the A* pathfinding algorithm. Every time you access the open list, you need to find the square that has the lowest F cost. There are several ways you could do this. You could save the path items as needed, and simply go through the whole list each time you need to find the lowest F cost square. This is simple, but really slow for long paths. This can be improved by maintaining a sorted list and simply grabbing the first item off the list every time you need the lowest F-cost square. When I wrote my program, this was the first method I used.
这将工作得相当好为小地图,但它不是最快答案。严重的A *程序员谁想要真正的速度使用一种叫做二进制堆,这是我在我的代码中使用。在我的经验,这种方法将是至少2-3倍的速度在大多数情况下,并且在几何形状更快(快10+次)上较长的路径。如果你主动去寻找更多关于二叉堆,看看我的文章,在A *路径搜索使用二进制堆。
This will work reasonably well for small maps, but it isn‘t the fastest solution. Serious A* programmers who want real speed use something called a binary heap, and this is what I use in my code. In my experience, this approach will be at least 2-3 times as fast in most situations, and geometrically faster (10+ times as fast) on longer paths. If you are motivated to find out more about binary heaps, check out my article, Using Binary Heaps in A* Pathfinding.
另一种可能的瓶颈是你的方式明确和维护路径搜索调用之间的数据结构。我个人更喜欢存储所有阵列。虽然节点可以生成,记录并保存在一个动态的,面向对象的方式,我发现,创建和删除这些对象所需的时间量增加了额外的开销,不必要的水平会减慢速度。如果你使用数组,不过,你需要调用之间干净的东西了。你会想在这种情况下的最后一件事就是花零时间做完一切了在调用路径搜索后,特别是如果你有一个大的地图。
Another possible bottleneck is the way you clear and maintain your data structures between pathfinding calls. I personally prefer to store everything in arrays. While nodes can be generated, recorded and maintained in a dynamic, object-oriented manner, I find that the amount of time needed to create and delete such objects adds an extra, unnecessary level of overhead that slows things down. If you use arrays, however, you will need to clean things up between calls. The last thing you will want to do in such cases is spend time zero-ing everything out after a pathfinding call, especially if you have a large map.
我避免这种开销通过创建一个二维数组称为whichList(X,Y),其指定在每个节点上我的地图作为任一开启列表或关闭列表上。路径搜索的尝试之后,我不归零数组。相反,我在每一个路径搜索呼叫复位onClosedList和onOpenList的价值观,每个路径尝试寻找类似+5什么都递增。通过这种方式,算法可以放心地忽略垃圾从以前的路径搜索的尝试遗留任何数据。我也喜欢存放F,G和H阵列的成本值。在这种情况下,我只是写在任何预先存在的价值和不打扰清除阵列时,我做的。
I avoid this overhead by creating a 2d array called whichList(x,y) that designates each node on my map as either on the open list or closed list. After pathfinding attempts, I do not zero out this array. Instead I reset the values of onClosedList and onOpenList in every pathfinding call, incrementing both by +5 or something similar on each path finding attempt. This way, the algorithm can safely ignore as garbage any data left over from previous pathfinding attempts. I also store values like F, G and H costs in arrays. In this case, I simply write over any pre-existing values and don‘t bother clearing the arrays when I‘m done.
在多个阵列存储数据占用更多的内存,虽然如此,有一个权衡。最终,你应该使用什么方法,你是最舒服的。
Storing data in multiple arrays consumes more memory, though, so there is a trade off. Ultimately, you should use whatever method you are most comfortable with.
8. Dijkstra的算法:当A *通常被认为是最好的路径搜索算法(见上面的小咆哮),存在至少一个其它的算法有其用途 - Dijkstra算法。 Dijkstra的是基本相同的A *,除了没有启发式(H始终为0)。因为它没有启发式,它通过在每一个方向同样扩大了搜索。正如你可能想象的,因为这Dijkstra算法通常是结束了探索一个更大的区域之前目标被发现。这通常使得它比A *慢。
8. Dijkstra‘s Algorithm: While A* is generally considered to be the best pathfinding algorithm (see rant above), there is at least one other algorithm that has its uses - Dijkstra‘s algorithm. Dijkstra‘s is essentially the same as A*, except there is no heuristic (H is always 0). Because it has no heuristic, it searches by expanding out equally in every direction. As you might imagine, because of this Dijkstra‘s usually ends up exploring a much larger area before the target is found. This generally makes it slower than A*.
那么,为什么使用它?有时候,我们不知道我们的目标位置是。假设你有一个需要去获得某种资源的一些资源收集装置。它可能知道几个资源区域,但它希望去最近的一个。在这里,Dijkstra的比A *更好,因为我们不知道哪一个是最接近的。我们唯一的选择是重复使用A *查找到每一个的距离,然后选择这条道路。可能有无数类似的情况,我们知道那种位置,我们可能会寻找的,想找到最近的一个,但不知道它在哪里或哪一个可能是最接近的。
So why use it? Sometimes we don‘t know where our target destination is. Say you have a resource-gathering unit that needs to go get some resources of some kind. It may know where several resource areas are, but it wants to go to the closest one. Here, Dijkstra‘s is better than A* because we don‘t know which one is closest. Our only alternative is to repeatedly use A* to find the distance to each one, and then choose that path. There are probably countless similar situations where we know the kind of location we might be searching for, want to find the closest one, but not know where it is or which one might be closest.
(待续)