<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Boulevard Of Broken Dreams &#187; 寻路</title>
	<atom:link href="http://www.ray77.com/tag/%e5%af%bb%e8%b7%af/feed" rel="self" type="application/rss+xml" />
	<link>http://www.ray77.com</link>
	<description>Walking alone ... Waiting alone ...</description>
	<lastBuildDate>Mon, 10 May 2010 14:24:39 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
<xhtml:meta xmlns:xhtml="http://www.w3.org/1999/xhtml" name="robots" content="noindex" />
		<item>
		<title>A* Pathfinding for Beginners</title>
		<link>http://www.ray77.com/a-pathfinding-for-beginners.html</link>
		<comments>http://www.ray77.com/a-pathfinding-for-beginners.html#comments</comments>
		<pubDate>Sun, 09 Nov 2008 13:36:55 +0000</pubDate>
		<dc:creator>Rock</dc:creator>
				<category><![CDATA[Develop]]></category>
		<category><![CDATA[A星]]></category>
		<category><![CDATA[人工智能]]></category>
		<category><![CDATA[启发]]></category>
		<category><![CDATA[寻路]]></category>
		<category><![CDATA[搜索]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[迷宫]]></category>

		<guid isPermaLink="false">http://www.ray77.com/?p=223</guid>
		<description><![CDATA[　　虽然掌握了A*（读作A-star）算法就认为它很容易，对于初学者来说，它却是复杂的。网上有很多解释A*的文章，不过大多数是写给理解了基础知识的人。本文是给初学者的。
　　本文并不想成为关于这个主题的权威论文。实际上它讨论了基础知识并为你做一些准备，以便进一步阅读其他资料和理解它们讨论的内容。本文的后面列出了几个最好的文章，在进阶阅读中。
　　最后，本文不是编程规范的。你应该能够改写这里的东西到任何计算机语言上。如你所期望的，同时，我包含了一个示例程序的链接，在本文后面结束的地方。这个程序包有两个版本：一个是C++，另一个用Blitz Basic语言编写。如果你只是想看看A*的行为，里面也含有可执行exe文件。
　　但我们要超越自己。让我们从头开始 &#8230;
　　介绍：搜索区域
　　我们假设某人想从A点到达B点，一堵墙把它们分开了。如下图所示，绿色是开始点A，红色是结束点B，而蓝色填充的方块是中间的墙。

[图 1][Figure 1]
　　你应该注意的第一件事是，我们把搜索区域分割成了方块的格子。简化搜索区域，如你目前完成的那样，这是寻路的第一步。这个特殊方法把搜索区域简化成了一个二维数组。数组的每一个项目代表了格子里的一个方块，它的状态记录成可行走和不可行走。通过计算出从A到达B应该走哪些方块，就找到了路径。一旦路径找到，我们的人从一个方块的中心移动到下一个方块的中心，直到抵达目标。
　　这些中心点称作“节点”。当你在其它地方阅读关于寻路时，你将经常发现人们讨论节点。为什么不直接把它们认为是方块呢？因为有可能你要把你的寻路区域以非方块的东西来分割。它们可能是矩形，六角形，或任何形状，真的。而节点可以放到形状内的任何位置。在中心，或者沿着边缘，或其它地方。然而我们使用这个系统，因为它最简单。
　　开始搜索
一旦我们把搜索区域简化成了可以管理的大量节点，就象我们上面所做的那样采用格子的布局，下一步就是引导一个搜索来找出最短路径。在A*寻路的做法，我们从开始点A做起，检查它周围的方块，并且向外普通的搜索，直到找到目标。
　　我们这样开始搜索：
　　从开始点A起，添加它到待考虑的方块的“开放列表”。开放列表有点象购物列表。此时只有一个项目在里面，但很快我们会得到更多。它包含了你可能取用的沿途的方块，也可能不用它。基本上，这是需要检查的方块的列表。
　　观察开始点邻近的所有可到达或可行走的方块，忽略有墙，水或其他非法地形的方块。也把它们添加到开放列表。对每一个方块，保存A 点作为它们的“父亲”。这个父亲方块在跟踪路径时非常重要。后面会更多的解释。
　　把开始方块A从开放列表中取出，并放到“封闭列表”内，它是所有现在不需要再关注的方块的列表。
　　在此，你应该有了类似下图的东西。在这个图中，中间的深绿色的方块就是开始方块。它有浅蓝色的外框，表示它被添加到封闭列表了。所有的相邻方块现在都进入要检查的方块的开放列表中了，它们有浅绿的外框。每一个都有灰色的指针指回它的父亲，它就是开始方块。

[图 2][Figure 2]
　　下一步，我们从开放列表中，选出一个相邻的方块，然后多多少少重复早先的过程，下面会说到。但是我们选择哪一个呢？具有最小F值的那个。
　　路径排序
　　找到形成路径的方块的关键是下面的等式：
　　F = G + H
　　这里
　　G = 从开始 点A到格子中给定方块的移动代价，沿着到达该方块而生成的那个路径。
　　H = 从格子中给定 的方块到最终目标 B点的评估移动代价。这种方式通常称作试探法，有点让人混乱。因为这是一个猜测，所以得到这个称谓。在找到路径之前，我们真的不知道实际的距离，因为途中有各种东西（墙，水，等等）。在本教程里给出了一种计算H的方法，但在网上你能找到很多其他的文章。
　　我们需要的路径是这样生成的：反复的遍历开放列表，选择具有最小F值的方块。这个过程在本文稍后会详细描述。先让我们看看如何计算前面提到的等式。
　　如上所述，G是经由到达它的路径，从开始点到给定方块的移动代价。在本例中，我们为每个水平/垂直的移动指定代价为10，而斜角的移动代价为14。我们使用这些值，因为斜角移动的实际距离是2的平方根（别害怕），或者大概1.414倍的水平/垂直的移动代价。出于简化的目的使用了10和14。比例大致是正确的，而我们却避免了方根和小数的计算。倒不是我们没有能力做或者不喜欢数学。使用这些数字也能让计算更快一些。以后你就会发现，如果不使用这些技巧，寻路的计算非常慢。
　　既然我们沿着到达给定方块的路径来计算G的值，找出那个方块的G值的方法就是找到其父亲的G值，再加上10或者14而得，这依赖于他处于其父亲的斜角或者直角（非斜角）而定。这在本例后面会更加清晰，随着我们从开始点离开而得到更多的方块。
　　H能通过多种方法估算。我们这里用到的方法叫做Manhattan方法，计算从当前方块经过水平/垂直移动而到达目标方块的方块总数。然后将总数乘以 10。这种方法之所以叫做Manhattan方法，因为他很象计算从一个地点到达另一个地点的城市街区数量计算，此时你不能斜向的穿越街区。重要的是，当计算H的时候，要忽略任何路径中的障碍。这是一个对剩余距离的 估算值，而不是实际值，这就是试探法的称谓由来。想知道更多？关于试探法的更多说明在这里。
　　G和H相加就算出了F。第一步搜索的结果见下图的描述。F，G，和H值都写入了每个方块。如开始方块相邻右边的方块，F显示在左上方，G显示在左下方，而 H显示在右下方。
 
[图 3][Figure 3]
　　好，让我们来观察某些方块。在有字母的方块中，G = 10。这是由于在水平方向上从开始点（到那里）只有一个方块（的距离）。开始点相邻上方，下方和左边的方块都具有同样的G值：10。斜角的方块G值为 14。
　　H的计算通过估算Manhattan距离而得，即：水平/垂直移动，忽略途中的障碍，到达红色的目标方块的距离。用这种方法，开始点相邻右边的方块和红色方块相距3个方块，那么H值就是30。其上的方块距离为4（记住，只能水平或者垂直移动），H就是40。你也许可以看看其他方块的H值是如何算出的。
　　每个方块的F值，再说一下，不过就是G和H相加。
　　持续的搜索
　　为了继续搜索，我们简单的选择开放列表里具有最小F值的方块。然后对选定的方块做如下操作：
　　将他从开放列表取出，并加入封闭列表。
　　测试所有的相邻方块。忽略封闭列表内的和不可行走的（墙，水及其它非法地形）方块，如果方块不在开放列表中，则添加进去。将选定方块作为这些新加入方块的父亲。
　　如果一个相邻方块已经存在于开放列表，检查到达那个方块的路径是否更优。换句话说，检查经由当前方块到达那里是否具有更小的G 值。如果没有，不做任何事。
　　相反，如果新路径的G值更小，把这个相邻方块的父亲改为当前选定的方块（在上图中，修改其指针方向指向选定方块）。最后，重新计算那个方块的F和G值。如果这样还是很迷惑的话，后面还会有图解说明。
　　好了，让我们看看它是怎样工作的。在初始的9个方块中，当开始方块被纳入封闭列表后，我们的开放列表就只有8个方块了。在这些块中，具有最小F值的是开始方块相邻右边的那个，其F值为40。所以我们选定这个块作为下一个方块。在随后的图例中，它以高亮的蓝色表示。

[图 4][Figure 4]
　　首先，我们把它从开放列表取出，并加入到封闭列表（这就是它现在是高亮的蓝色的原因）。然后我们检查相邻的方块。然而，这个方块相邻右边的是代表墙的方块，所以忽略它们。其相邻左边是开始方块。它处于封闭列表内，所以也忽略它
　　其它4个已经在开放列表中了，所以我们需要检查经由当前方块到达他们是否是更优的路径，使用G值为参考点。我们来看看这个选定方块上面右边的那个方块。它的当前G值是14。如果我们经由当前方块到达那里，G值将是20（10，到达当前方块的G值，再加上10垂直移动到它上面的方块）。20 &#62; 14，所以这不是一个好的路径。看看图解能更好的理解这些。从开始方块斜向移动到那个方块更直接，而不是水平移动一个方块，再垂直移动一个方块。
　　当我们对已经存在于开放列表的所有4个相邻方块都重复这个过程，我们发现经由当前方块没有更佳的路径，所以什么也不用改变。现在看看所有的相邻方块，我们已经处理完毕，并准备移动到下一个方块。
　　现在，我们再遍历开放列表，它只有7个方块了，选择具有最小F值的那个。有趣的是，此时有两个方块都有值54。那么我们选择哪个？实际上这不算什么。为了速度的目的，选择你最后加入到开放列表的那个方块更快。当你更接近目标的时候，它倾向于后发现的方块。但这真的没什么关系。（不同的处理造成了两个版本的A*可能找到不同的等长路径。）
　　我们选择下面的那个，位于开始方块的右边，如下图所示。

[图 5][Figure 5]
　　这一次，当检查相邻的方块时，我们相邻右边的是一个墙方块，所以忽略它。对那个方块上面的块同样忽略。我们也忽略墙下面的方块。为什么？因为你不把临近墙的角切开就无法直接到达那个方块。实际上你需要先向下走，然后越过那个方块，在这个过程中都是围绕角在移动。（说明：切开角的规则是可选的。它的使用依赖于你的节点如何放置。）
　　这样就剩下5个方块了。当前方块下的两个方块不在开放列表中，所以要添加他们，并把当前方块作为它们的父亲。在另外三个方块中，有两个已经在封闭列表中了（开始方块，和当前方块上面的那个，它们都用高亮的蓝色在图中标出来了），所以忽略它们。最后一个方块，当前方块相邻左边的那个，检查经由当前方块到达那里是否得到更小的G值。没有。所以处理完毕，并准备检查开放列表中的下一个方块。
　　我们重复这个过程，直到把目标点添加到开放列表，此时的情形如下图所示。

[图 6][Figure 6]
　　注意开始方块向下的第二个方块，在前面的描述中其父亲已经发生改变。开始它的G值为28，指向其右上角的方块。现在它的值是20，指向其上方的方块。这是在搜索方法中某处发生的吗？在那里G值被检查，而且使用新的路径后，它得到了更小的值。所以它的父亲切换了，G和F也重新计算。而这个改变在本例中不见得非常重要，还有足够多的可能位置，在决定最佳路径的时候，持续的检查会产生各种差别。
　　那么我们怎样决定实际的路径呢？简单，从红色目标方块开始，向后移动到它的父亲，跟从箭头的指示。最终你会回到开始方块，这就是路径！它应该如下图所示。从方块A移动到目标方块B就是从每一个方块（节点）的中心移动到路径上的下一个方块的中心的简单过程，直到到达目标。简单！

[图 7][Figure 7]
 
　　A*方法汇总
　　好了，现在你已经读完了解释，让我们在这里一步一步的列出所有操作：
　　添加开始方块到开放列表。
　　重复下面的过程：
　　a) 查找开放列表中具有最小F值的方块。我们把它作为当前方块。
　　b) 把它放入封闭列表。
　　c) 对当前方块的8个相邻方块的每一个？
　　如果它不可行走，或者存在于封闭列表，忽略它。否则执行下面操作。
　　如果它不在开放列表，将它添加进去。以当前方块作为其父亲。记录这个方块的F，G和H值。
　　如果它已经在开放列表了，检查到达那个方块的路径是否更优，以G值为测量值。更低的G值意味着更好的路径。如果找到，这个方块的父亲改为当前方块，并重新计算这个方块的G和F值。如果你保持开放列表按F值排序的话，可能需要重新排序来解决这个变化。
　　d) 结束循环，当你
　　将目标方块加入到开放列表，此时路径已经找到，或者
　　没有找到目标方块，并且开放列表是空的。此时，没有路径。
　　保存路径。从目标方块往回走，从每个方块走到它的父亲方块，直到抵达开始方块。那就是路径。
　　一点感慨
　　原谅我离题了，但是值得指出的是，当你在网上和分类论坛阅读很多讨论A*寻路的时候，你有时候会发现某些人所指的A*代码实际上并不是真正的A*算法。对于应用中的A*方法，你需要包含上面讨论到的元素  &#8212; 特别是开放列表和封闭列表，以及使用F，G，和H的路径排序。有很多其他的寻路算法，但是其它的方法不是A* &#8212; 通常认为它是最好的算法。Bryan Stout在本文后面的一个参考文档里讨论了这些算法，有正面的，也有反面的。有时候在特定的环境下，选择其他的算法会更好，但是你应该理解你做了什么。好了，说够了。回到文章中来。
关于实现的提示
　　现在你已经理解了基本的方法，这里是当你写自己的程序时要考虑的更多东西。下面的某些材料引用了我用C++和Blitz Basic写的程序，但是这些要点对其他语言也是同样有效的。
　　1.维护开放列表 : 实际上这是A*寻路函数最耗费时间的元素之一。每次访问开放列表时，你都需要找到具有最小F值的方块。有很多种方法可以做到这点。你可以保存所需的路径项目，每次当你需要找到最小F值的方块时，简单的遍历整个列表。这很简单，不过路径长的时候非常慢。这个方法可以改进，通过维护一个排序的列表，每次需要最小F值的方块时，简单的抓出第一个项目就可以了。当我写自己的程序时，这是我用到的第一个方法。
　　这个方法对小的地图相当的好，但不是最快的方案。真正需要速度的认真的A*程序员使用叫做二元堆[binary ...]]></description>
			<content:encoded><![CDATA[<p>　　虽然掌握了A*（读作A-star）算法就认为它很容易，对于初学者来说，它却是复杂的。网上有很多解释A*的文章，不过大多数是写给理解了基础知识的人。本文是给初学者的。</p>
<p>　　本文并不想成为关于这个主题的权威论文。实际上它讨论了基础知识并为你做一些准备，以便进一步阅读其他资料和理解它们讨论的内容。本文的后面列出了几个最好的文章，在进阶阅读中。</p>
<p>　　最后，本文不是编程规范的。你应该能够改写这里的东西到任何计算机语言上。如你所期望的，同时，我包含了一个示例程序的链接，在本文后面结束的地方。这个程序包有两个版本：一个是C++，另一个用Blitz Basic语言编写。如果你只是想看看A*的行为，里面也含有可执行exe文件。</p>
<p>　　但我们要超越自己。让我们从头开始 &#8230;</p>
<p>　　<strong><span style="color: #ff6600;">介绍：搜索区域</span></strong></p>
<p>　　我们假设某人想从A点到达B点，一堵墙把它们分开了。如下图所示，绿色是开始点A，红色是结束点B，而蓝色填充的方块是中间的墙。</p>
<p><img class="aligncenter size-full wp-image-224" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image001.jpg" alt="" width="362" height="256" /></p>
<p><span id="more-223"></span>[图 1][Figure 1]</p>
<p>　　你应该注意的第一件事是，我们把搜索区域分割成了方块的格子。简化搜索区域，如你目前完成的那样，这是寻路的第一步。这个特殊方法把搜索区域简化成了一个二维数组。数组的每一个项目代表了格子里的一个方块，它的状态记录成可行走和不可行走。通过计算出从A到达B应该走哪些方块，就找到了路径。一旦路径找到，我们的人从一个方块的中心移动到下一个方块的中心，直到抵达目标。</p>
<p>　　这些中心点称作“节点”。当你在其它地方阅读关于寻路时，你将经常发现人们讨论节点。为什么不直接把它们认为是方块呢？因为有可能你要把你的寻路区域以非方块的东西来分割。它们可能是矩形，六角形，或任何形状，真的。而节点可以放到形状内的任何位置。在中心，或者沿着边缘，或其它地方。然而我们使用这个系统，因为它最简单。</p>
<p>　　<strong><span style="color: #ff6600;">开始搜索</span></strong></p>
<p>一旦我们把搜索区域简化成了可以管理的大量节点，就象我们上面所做的那样采用格子的布局，下一步就是引导一个搜索来找出最短路径。在A*寻路的做法，我们从开始点A做起，检查它周围的方块，并且向外普通的搜索，直到找到目标。</p>
<p>　　我们这样开始搜索：</p>
<p>　　从开始点A起，添加它到待考虑的方块的“开放列表”。开放列表有点象购物列表。此时只有一个项目在里面，但很快我们会得到更多。它包含了你可能取用的沿途的方块，也可能不用它。基本上，这是需要检查的方块的列表。</p>
<p>　　观察开始点邻近的所有可到达或可行走的方块，忽略有墙，水或其他非法地形的方块。也把它们添加到开放列表。对每一个方块，保存A 点作为它们的“父亲”。这个父亲方块在跟踪路径时非常重要。后面会更多的解释。</p>
<p>　　把开始方块A从开放列表中取出，并放到“封闭列表”内，它是所有现在不需要再关注的方块的列表。</p>
<p>　　在此，你应该有了类似下图的东西。在这个图中，中间的深绿色的方块就是开始方块。它有浅蓝色的外框，表示它被添加到封闭列表了。所有的相邻方块现在都进入要检查的方块的开放列表中了，它们有浅绿的外框。每一个都有灰色的指针指回它的父亲，它就是开始方块。</p>
<p><img class="aligncenter size-full wp-image-225" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image002.jpg" alt="" width="151" height="150" /></p>
<p>[图 2][Figure 2]</p>
<p>　　下一步，我们从开放列表中，选出一个相邻的方块，然后多多少少重复早先的过程，下面会说到。但是我们选择哪一个呢？具有最小F值的那个。</p>
<p>　　<strong><span style="color: #ff6600;">路径排序</span></strong></p>
<p>　　找到形成路径的方块的关键是下面的等式：</p>
<p>　　F = G + H</p>
<p>　　这里</p>
<p>　　G = 从开始 点A到格子中给定方块的移动代价，沿着到达该方块而生成的那个路径。</p>
<p>　　H = 从格子中给定 的方块到最终目标 B点的评估移动代价。这种方式通常称作试探法，有点让人混乱。因为这是一个猜测，所以得到这个称谓。在找到路径之前，我们真的不知道实际的距离，因为途中有各种东西（墙，水，等等）。在本教程里给出了一种计算H的方法，但在网上你能找到很多其他的文章。</p>
<p>　　我们需要的路径是这样生成的：反复的遍历开放列表，选择具有最小F值的方块。这个过程在本文稍后会详细描述。先让我们看看如何计算前面提到的等式。</p>
<p>　　如上所述，G是经由到达它的路径，从开始点到给定方块的移动代价。在本例中，我们为每个水平/垂直的移动指定代价为10，而斜角的移动代价为14。我们使用这些值，因为斜角移动的实际距离是2的平方根（别害怕），或者大概1.414倍的水平/垂直的移动代价。出于简化的目的使用了10和14。比例大致是正确的，而我们却避免了方根和小数的计算。倒不是我们没有能力做或者不喜欢数学。使用这些数字也能让计算更快一些。以后你就会发现，如果不使用这些技巧，寻路的计算非常慢。</p>
<p>　　既然我们沿着到达给定方块的路径来计算G的值，找出那个方块的G值的方法就是找到其父亲的G值，再加上10或者14而得，这依赖于他处于其父亲的斜角或者直角（非斜角）而定。这在本例后面会更加清晰，随着我们从开始点离开而得到更多的方块。</p>
<p>　　H能通过多种方法估算。我们这里用到的方法叫做Manhattan方法，计算从当前方块经过水平/垂直移动而到达目标方块的方块总数。然后将总数乘以 10。这种方法之所以叫做Manhattan方法，因为他很象计算从一个地点到达另一个地点的城市街区数量计算，此时你不能斜向的穿越街区。重要的是，当计算H的时候，要忽略任何路径中的障碍。这是一个对剩余距离的 估算值，而不是实际值，这就是试探法的称谓由来。想知道更多？关于试探法的更多说明在这里。</p>
<p>　　G和H相加就算出了F。第一步搜索的结果见下图的描述。F，G，和H值都写入了每个方块。如开始方块相邻右边的方块，F显示在左上方，G显示在左下方，而 H显示在右下方。</p>
<p> <img class="aligncenter size-full wp-image-226" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image003.jpg" alt="" width="362" height="255" /></p>
<p>[图 3][Figure 3]</p>
<p>　　好，让我们来观察某些方块。在有字母的方块中，G = 10。这是由于在水平方向上从开始点（到那里）只有一个方块（的距离）。开始点相邻上方，下方和左边的方块都具有同样的G值：10。斜角的方块G值为 14。<br />
　　H的计算通过估算Manhattan距离而得，即：水平/垂直移动，忽略途中的障碍，到达红色的目标方块的距离。用这种方法，开始点相邻右边的方块和红色方块相距3个方块，那么H值就是30。其上的方块距离为4（记住，只能水平或者垂直移动），H就是40。你也许可以看看其他方块的H值是如何算出的。</p>
<p>　　每个方块的F值，再说一下，不过就是G和H相加。<br />
　　<strong><span style="color: #ff6600;">持续的搜索</span></strong></p>
<p>　　为了继续搜索，我们简单的选择开放列表里具有最小F值的方块。然后对选定的方块做如下操作：</p>
<p>　　将他从开放列表取出，并加入封闭列表。</p>
<p>　　测试所有的相邻方块。忽略封闭列表内的和不可行走的（墙，水及其它非法地形）方块，如果方块不在开放列表中，则添加进去。将选定方块作为这些新加入方块的父亲。</p>
<p>　　如果一个相邻方块已经存在于开放列表，检查到达那个方块的路径是否更优。换句话说，检查经由当前方块到达那里是否具有更小的G 值。如果没有，不做任何事。</p>
<p>　　相反，如果新路径的G值更小，把这个相邻方块的父亲改为当前选定的方块（在上图中，修改其指针方向指向选定方块）。最后，重新计算那个方块的F和G值。如果这样还是很迷惑的话，后面还会有图解说明。</p>
<p>　　好了，让我们看看它是怎样工作的。在初始的9个方块中，当开始方块被纳入封闭列表后，我们的开放列表就只有8个方块了。在这些块中，具有最小F值的是开始方块相邻右边的那个，其F值为40。所以我们选定这个块作为下一个方块。在随后的图例中，它以高亮的蓝色表示。</p>
<p><img class="aligncenter size-full wp-image-227" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image004.jpg" alt="" width="357" height="256" /></p>
<p>[图 4][Figure 4]</p>
<p>　　首先，我们把它从开放列表取出，并加入到封闭列表（这就是它现在是高亮的蓝色的原因）。然后我们检查相邻的方块。然而，这个方块相邻右边的是代表墙的方块，所以忽略它们。其相邻左边是开始方块。它处于封闭列表内，所以也忽略它</p>
<p>　　其它4个已经在开放列表中了，所以我们需要检查经由当前方块到达他们是否是更优的路径，使用G值为参考点。我们来看看这个选定方块上面右边的那个方块。它的当前G值是14。如果我们经由当前方块到达那里，G值将是20（10，到达当前方块的G值，再加上10垂直移动到它上面的方块）。20 &gt; 14，所以这不是一个好的路径。看看图解能更好的理解这些。从开始方块斜向移动到那个方块更直接，而不是水平移动一个方块，再垂直移动一个方块。</p>
<p>　　当我们对已经存在于开放列表的所有4个相邻方块都重复这个过程，我们发现经由当前方块没有更佳的路径，所以什么也不用改变。现在看看所有的相邻方块，我们已经处理完毕，并准备移动到下一个方块。</p>
<p>　　现在，我们再遍历开放列表，它只有7个方块了，选择具有最小F值的那个。有趣的是，此时有两个方块都有值54。那么我们选择哪个？实际上这不算什么。为了速度的目的，选择你最后加入到开放列表的那个方块更快。当你更接近目标的时候，它倾向于后发现的方块。但这真的没什么关系。（不同的处理造成了两个版本的A*可能找到不同的等长路径。）</p>
<p>　　我们选择下面的那个，位于开始方块的右边，如下图所示。</p>
<p><img class="aligncenter size-full wp-image-228" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image005.jpg" alt="" width="357" height="254" /></p>
<p>[图 5][Figure 5]</p>
<p>　　这一次，当检查相邻的方块时，我们相邻右边的是一个墙方块，所以忽略它。对那个方块上面的块同样忽略。我们也忽略墙下面的方块。为什么？因为你不把临近墙的角切开就无法直接到达那个方块。实际上你需要先向下走，然后越过那个方块，在这个过程中都是围绕角在移动。（说明：切开角的规则是可选的。它的使用依赖于你的节点如何放置。）</p>
<p>　　这样就剩下5个方块了。当前方块下的两个方块不在开放列表中，所以要添加他们，并把当前方块作为它们的父亲。在另外三个方块中，有两个已经在封闭列表中了（开始方块，和当前方块上面的那个，它们都用高亮的蓝色在图中标出来了），所以忽略它们。最后一个方块，当前方块相邻左边的那个，检查经由当前方块到达那里是否得到更小的G值。没有。所以处理完毕，并准备检查开放列表中的下一个方块。</p>
<p>　　我们重复这个过程，直到把目标点添加到开放列表，此时的情形如下图所示。</p>
<p><img class="aligncenter size-full wp-image-229" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image006.jpg" alt="" width="404" height="307" /></p>
<p>[图 6][Figure 6]</p>
<p>　　注意开始方块向下的第二个方块，在前面的描述中其父亲已经发生改变。开始它的G值为28，指向其右上角的方块。现在它的值是20，指向其上方的方块。这是在搜索方法中某处发生的吗？在那里G值被检查，而且使用新的路径后，它得到了更小的值。所以它的父亲切换了，G和F也重新计算。而这个改变在本例中不见得非常重要，还有足够多的可能位置，在决定最佳路径的时候，持续的检查会产生各种差别。</p>
<p>　　那么我们怎样决定实际的路径呢？简单，从红色目标方块开始，向后移动到它的父亲，跟从箭头的指示。最终你会回到开始方块，这就是路径！它应该如下图所示。从方块A移动到目标方块B就是从每一个方块（节点）的中心移动到路径上的下一个方块的中心的简单过程，直到到达目标。简单！</p>
<p><img class="aligncenter size-full wp-image-230" title="A* Pathfinding for Beginners" src="http://www.ray77.com/wp-content/uploads/2008/11/image007.jpg" alt="" width="411" height="308" /></p>
<p>[图 7][Figure 7]</p>
<p> </p>
<p>　　<strong><span style="color: #800080;">A*方法汇总</span></strong></p>
<p>　　好了，现在你已经读完了解释，让我们在这里一步一步的列出所有操作：</p>
<p>　　添加开始方块到开放列表。</p>
<p>　　重复下面的过程：</p>
<p>　　a) 查找开放列表中具有最小F值的方块。我们把它作为当前方块。</p>
<p>　　b) 把它放入封闭列表。</p>
<p>　　c) 对当前方块的8个相邻方块的每一个？</p>
<p>　　如果它不可行走，或者存在于封闭列表，忽略它。否则执行下面操作。</p>
<p>　　如果它不在开放列表，将它添加进去。以当前方块作为其父亲。记录这个方块的F，G和H值。</p>
<p>　　如果它已经在开放列表了，检查到达那个方块的路径是否更优，以G值为测量值。更低的G值意味着更好的路径。如果找到，这个方块的父亲改为当前方块，并重新计算这个方块的G和F值。如果你保持开放列表按F值排序的话，可能需要重新排序来解决这个变化。</p>
<p>　　d) 结束循环，当你</p>
<p>　　将目标方块加入到开放列表，此时路径已经找到，或者</p>
<p>　　没有找到目标方块，并且开放列表是空的。此时，没有路径。</p>
<p>　　保存路径。从目标方块往回走，从每个方块走到它的父亲方块，直到抵达开始方块。那就是路径。</p>
<p>　　一点感慨</p>
<p>　　原谅我离题了，但是值得指出的是，当你在网上和分类论坛阅读很多讨论A*寻路的时候，你有时候会发现某些人所指的A*代码实际上并不是真正的A*算法。对于应用中的A*方法，你需要包含上面讨论到的元素  &#8212; 特别是开放列表和封闭列表，以及使用F，G，和H的路径排序。有很多其他的寻路算法，但是其它的方法不是A* &#8212; 通常认为它是最好的算法。Bryan Stout在本文后面的一个参考文档里讨论了这些算法，有正面的，也有反面的。有时候在特定的环境下，选择其他的算法会更好，但是你应该理解你做了什么。好了，说够了。回到文章中来。<br />
关于实现的提示</p>
<p>　　现在你已经理解了基本的方法，这里是当你写自己的程序时要考虑的更多东西。下面的某些材料引用了我用C++和Blitz Basic写的程序，但是这些要点对其他语言也是同样有效的。</p>
<p>　　1.维护开放列表 : 实际上这是A*寻路函数最耗费时间的元素之一。每次访问开放列表时，你都需要找到具有最小F值的方块。有很多种方法可以做到这点。你可以保存所需的路径项目，每次当你需要找到最小F值的方块时，简单的遍历整个列表。这很简单，不过路径长的时候非常慢。这个方法可以改进，通过维护一个排序的列表，每次需要最小F值的方块时，简单的抓出第一个项目就可以了。当我写自己的程序时，这是我用到的第一个方法。<br />
　　这个方法对小的地图相当的好，但不是最快的方案。真正需要速度的认真的A*程序员使用叫做二元堆[binary heap]的东西，这也是我的代码中所使用的。以我的经验，在大多数解决方案中，这个方法会快至少2-3倍，在长路径上更快（10倍以上）。如果你有兴趣发现更多二元堆的奥秘，参考我的文章，Using Binary Heaps in A* Pathfinding。</p>
<p>　　2. 其他单元： 如果你碰巧深入的阅读我的范例代码，将会注意到它完全忽略了地图上的其他单元。我的寻路怪物实际上是穿越彼此而通过。依赖于游戏，这可能是正确的，或者是不正确的。如果你要考虑地图上的其他单元，并让他们能围绕彼此移动，我建议你在寻路代码里忽略其他的单元，而另外写一些代码来检测两个单元是否发生了碰撞。当碰撞发生时，你可以生成一个新路径或者使用一些标准的移动规则，直到障碍不在路上，然后生成新路径。当计算初始路径时，为什么不包含其他单元？嗯，因为其他单元会动，他们可能不在自己的位置，当你到达那里的时候。这会造成一些怪异的结果，路径计算后，在某处单元突然转向避开一个不再停留在那里的单元，却撞上了另一个经过它路径的单元。</p>
<p>　　寻路代码中忽略其他的单元，然而，这意味着你要写单独的代码来处理碰撞。这是和游戏很相关的，所以我把决定权留给你。本文后面的参考资料一节里， Bryan Stout的文章值得一读，里面有一些可能的解决方案（如强力跟踪[robust tracing]，等等）。</p>
<p>　　3. 关于速度的更多技巧 ：当你开发自己的A*程序，或者改编我所写的那个，最终你会发现寻路使用了大块的 CPU时间，特别是当你有大量的寻路怪物，运行在一个相当大的地图上的时候。如果你读网上的资料，你会发现甚至象星际争霸[Starcraft]或者帝国时代[Age of Empires]这样的专业游戏也会遇到这些问题。如果你发现由于寻路导致运行变慢，这里有一些可能提高速度的主意：<br />
　　考虑小一些的地图或者少一些的怪物。</p>
<p>　　不要一次对太多的怪物做寻路。而是把他们放入队列，从而把他们分散到更多的游戏循环。如果你的游戏运行在，比如，40帧/秒，没有人会注意到。但是他们会注意到每一小段时间的游戏变慢，当大量的怪物都在同一时间寻路的时候。</p>
<p>　　考虑对地图使用大一些的方块。这样就减少了寻路要搜索的方块总数。如果你有雄心的话，可以设计两种或更多寻路系统，依赖于路径的长度而用于不同的场合。这就是专业人士的做法，对长路径使用大的区域，然后当接近目标时切换到使用小一些的方块/区域的精确搜索。如果你对这个概念有兴趣，参考我的文章Two-Tiered A* Pathfinding。</p>
<p>　　考虑对较长的路径应用路点系统，或者设计预计算[precalculated]的多个路径，它们对游戏是固定不变 [hardwired]的。</p>
<p>　　考虑预处理地图，计算出哪些区域是从其他区域不可到达的。我把这些区域叫做“岛屿”。实际上，他们也可以是岛屿或者其他围了墙而无法到达的区域。A*的缺点之一就是，如果你告诉它搜索到达这些区域的路径，它会搜索整个地图，仅当每一个开放列表和封闭列表中的可到达方块/节点都处理后，才会停止。那会浪费大量的CPU时间。这种现象是可以避免的，通过预先决定那些区域是无法到达的，用数组或者类似的数据结构记录这些信息，然后在开始路径搜索前检查它。在我的代码的Blitz版本中，我创建了一个地图预处理器[map pre-processor]来做这件事。它也预先检查寻路算法可以忽略的死点[dead-ends]，这样速度就提高了很多。</p>
<p>　　4. 多样的地形代价： 在本教程以及我的附带程序里，地形只有两种情况：可行走和不可行走。如果你有可以行走但移动代价更高的地形怎么办？沼泽，山坡，地下城的楼梯，等等？这些都是可行走而移动代价高于平坦地面的地形实例。同样的，道路可能具有比它周围地形小一些的移动代价。</p>
<p>　　这个问题很容易解决，当计算任意给定的方块的G值时，加上地形的代价。简单的加上一个奖励代价给这些方块。A*寻路算法已经写成查找最小代价的路径，应该容易处理它。在我描述得简单示例中，当地形只有可行走和不可行走时，A*能找到最短，最直接得路径。但是在多代价[variable-cost]地形环境中，最小代的得路径可能行走了较长的距离。如同选择围绕沼泽的道路，而不是直接穿越沼泽。</p>
<p>　　还有一个有趣的附加考虑是被专业人士称作“影响映射[influence mapping]”的东西。如同上面描述的多代价地形一样，你可以创建一个附加的点系统，并引用到AI的路径中。想象你有一个地图，有大量的怪物守护着穿越山区的通道。每次当电脑送某人到经过这个通道的路径时，都会被困住。如果你愿意，你可以创建一个影响地图，处罚发生大量流血残杀处的那些方块。这会教电脑偏好安全的路径，并帮助它避免不利的位置：仅仅由于路径更短（但更危险），而不停的输送部队和怪物通过这个路径。</p>
<p>　　5. 处理未探索区域： 你是否玩过一款PC游戏，在那里电脑总是准确的知道路该如何走，即使地图没有探索？依赖与游戏，那样的寻路太好了反而不够真实。幸运的是，这个问题很容易解决。</p>
<p>　　答案就是创建一个独立的“发现可行走[knowWalkability]”数组用于每一个玩家以及电脑对手（每一个玩家，不是每一个单元 &#8212; 那将需要更多的计算机内存）。每个数组包含了玩家已探索区域的信息，另一方面，地图上其它区域直到被证实后才被假设是可行走的。使用这个方法，单元会漫步于死点位置，重复做相同的错误选择，直到他们发现周围的路。一旦地图都探索了，寻路就正常工作。</p>
<p>　　6. 更平滑的路径： 虽然A*会自动给出最短的，最低代价的路径，它不会自动给出看起来最平滑的路径。看一看本例最后计算出来的路径（图 7）。那条路径的第一步位于开始方块的右下方。如果第一步的方块就是开始方块相邻下方的方块，路径会不会更平滑些？</p>
<p>　　有很多方法可以解决这个问题。当你计算路径的时候，要处罚那些改变方向的方块，给它们一个附加的G值扣分。这样计算后，你可以走一遍那条路径，看一看那些选择了邻近方块而让路径看起来更好的地方。关于这个问题的完整信息，参考Gamasutra.com上Macro Pinter的文章 Toward More Realistic Pathfinding，它是免费的，但需要注册。</p>
<p>　　7. 非方块搜索区域： 在我们的示例中，我们使用了一个简单的2D方块布局。你不必使用这个方法。你可以使用不规则的形状区域。考虑一下棋盘游戏Risk，和游戏中的国家。你可以设计一个象那样的寻路关卡。为此，你将需要创建一个表来存储哪些国家和哪些国家相邻，以及相关的从一个国家移动到另一个国家的G值。你也需要选择一种估算H值的方法。其它的处理就和上面示例一样。当添加新项目到开放列表中时，你将简单的查找表中的国家，而不是邻近的方块。</p>
<p>　　同样的，你可以创建一个路点系统，对于固定地形场景中的路径。路点通常是一条路径上往来移动的点，这路径可能是一条道路，也可能是一个地下城的关键通道。作为游戏设计者，你能预先指定这些路点。如果没有障碍存在于两个路点间的直线路径上，就可以认为它们是“相邻”的，如同在Risk中那样，你应该保存这些邻接信息到一个某种类型的查找表中，当生成新的开放列表项目的时候使用它。然后记录相关的G值（可能是节点间的直线距离）和H值（可能是节点到目标的直线距离）。其他的和往常一样处理。</p>
<p>　　另有一个使用非方块搜索区域进行斜视角RPG地图搜索的例子，参考我的文章Two-Tiered A* Pathfinding。</p>
<p>　　进阶阅读</p>
<p>　　好了，现在你具备了基础知识和对一些高级概念的感觉。在这里，我建议你到我的代码中跋涉。程序包有两个版本，一个是C++的，另一个是用Blitz Basic语言写的。两个版本都有大量注释，应该容易理解。链接在这里。</p>
<p>　　如果你无法使用C++或者Blitz Basic，可以找到C++版本的两个exe执行文件。通过在Blitz Basic的网站下载免费的demo版Blitz Basic 3D，就能运行Blitz Basic版本。还能找到Ben O‘Neill写的在线使用示范。</p>
<p>　　你也应该考虑通读下面的网页。阅读了本文后，它们应该非常容易理解了。</p>
<p>　　Amit&#8217;s A* Pages: 这是Amit Patel维护的一个很广泛的参考页面，如果你开始没有读过本文，它是有点混乱的。很值得一读。特别看一下关于Amit本人的思想那个主题。</p>
<p>　　Smart Moves: Intelligent Path Finding : 这篇文章位于Gamasutra.com，Bryan Stout所写，需要注册才能阅读。注册是免费的，为了这篇文章是值得的，因为此类的文章可不多见了。程序是Bryan用delphi语言写的，它帮助我学会了A*，我的A*程序背后也有它的灵感。它也讨论了一些替代A*的解决方案。</p>
<p>　　Terrain Analysis: 这是一篇高阶的文章（但很有趣），Dave Pottinger所写。Ensemble Studios的专家。这些家伙开发了帝国时代[Age of Empires]和帝国时代II：帝王时代[Age of Kings]。不要指望能理解这里的所有东西，但它是一篇有趣的文章，也许能带给你一些属于你自己的想法。它包含了一些mip映射[mip- mapping]，影响映射[influence mapping]，和一些其他的高级AI/寻路的概念的讨论。对”洪水泛滥[flood filling]“的讨论给予了我设计自己的&#8221;死点[dead ends]&#8220;和&#8221;岛屿&#8221;地图预处理代码的灵感，这些都包含在我的程序的Blitz版本里面。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ray77.com/a-pathfinding-for-beginners.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
