<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>文章翻译</title><link>http://blog.vckbase.com/panic/category/598.html</link><description>来自老外的观点</description><managingEditor>Panic</managingEditor><dc:language>zh-CHS</dc:language><generator>.Text Version 0.958.2004.214</generator><item><dc:creator>Panic</dc:creator><title>[翻译]A*分层寻路</title><link>http://blog.vckbase.com/panic/archive/2005/07/21/9906.html</link><pubDate>Thu, 21 Jul 2005 10:05:00 GMT</pubDate><guid>http://blog.vckbase.com/panic/archive/2005/07/21/9906.html</guid><wfw:comment>http://blog.vckbase.com/panic/comments/9906.html</wfw:comment><comments>http://blog.vckbase.com/panic/archive/2005/07/21/9906.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://blog.vckbase.com/panic/comments/commentRss/9906.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/panic/services/trackbacks/9906.html</trackback:ping><description>&lt;P&gt;A*分层寻路&lt;/P&gt;
&lt;P&gt;作者：Patrick Lester 2003年1月9日更新&lt;BR&gt;译者：&lt;A href="http://blog.vckbase.com/panic"&gt;&lt;a title="Panic" href="http://blog.vckbase.com/panic/" &gt;Panic&lt;/a&gt;&lt;/A&gt; 2005年7月21日&lt;BR&gt;译者序：很久没有翻译文章了，这次找了这个短一些的。这个文章是偶以前翻译的《&lt;A href="http://blog.vckbase.com/panic/archive/2005/03/20/3778.html"&gt;A*寻路初探&lt;/A&gt;》的补充，介绍了A*更进一步的，更实用的方法。&lt;/P&gt;
&lt;P&gt;原文链接：&lt;A href="http://www.policyalmanac.org/games/twoTiered.htm"&gt;http://www.policyalmanac.org/games/twoTiered.htm&lt;/A&gt;&lt;BR&gt;以下是翻译正文：&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;在我的主题&lt;A href="http://www.policyalmanac.org/games/aStarTutorial.htm"&gt;A* Pathfinding for Beginners&lt;/A&gt;中(译者注：译文 &lt;A href="http://blog.vckbase.com/panic/archive/2005/03/20/3778.html"&gt;A*寻路初探&lt;/A&gt;)中，我概述了A*算法，说明了如何创建一个通用的寻路功能。然而仅创建一个寻路功能，用途是很有限的。&lt;BR&gt;考虑如下的RPG场景，一个剑士想找到绕过旁边墙壁的路：&lt;BR&gt;&lt;IMG height=182 src="/images/vckbase_com/panic/noNodes.jpg" width=256 border=0&gt;&lt;/P&gt;
&lt;P&gt;对于这类地图，你可以用不同的方式，密度来放置节点。在这个例子中，我们使用高密度网格，如下：&lt;BR&gt;&lt;IMG height=172 src="/images/vckbase_com/panic/microPathNodes1.jpg" width=266 border=0&gt;&lt;/P&gt;
&lt;P&gt;在这个图中，白色节点是可通过的。没有节点的地方是不可通过的。这个例子还有一些不同的地方。在这个例子中，你可以&amp;#8220;穿越&amp;#8221;不可通过区域的拐角。同时，&amp;#8220;方格&amp;#8221;也不再是方格。因为这是一个投影视角的例子，我们在Y(垂直方向)拥有X(水平方向)两倍的节点。这使得正确的投影路径成为可能。&lt;/P&gt;
&lt;P&gt;如你所见，用这么密集的节点网络，我们不仅能寻路绕过旁边的墙壁，还能在墙壁和旁边的桶之间找到路径。我们密集的网格也使得绕过底部不可通过的火把成为可能。这总的说来是精确寻路。&lt;/P&gt;
&lt;P&gt;好，在短距离上，那的确很酷，但是如果我们要穿越整个地图进行寻路，该怎么办？使用像这么密的网格，轻易就会让你在一条路径上使用A*的搜索节点的数量达到10,000+。这几乎会让任何PC失去响应。&lt;/P&gt;
&lt;P&gt;那么我们来看另一种选择。创建一个低密度的网格，就像下面这样，如何？&lt;BR&gt;&lt;IMG height=245 src="/images/vckbase_com/panic/macroNodes.jpg" width=364 border=0&gt;&lt;/P&gt;
&lt;P&gt;在这个例子里，节点在等边菱形的中心。菱形之间通过性的数据沿边界存储在同一个数组中，在图片上用小红点表示。要从一个图素移动到它周围8个图素上，相邻的图素必须是可通过的，并且路径没有被挡住。&lt;/P&gt;
&lt;P&gt;这个节点网格的密度是先前的72分之一。原来我们需要面对10,000+的节点，而现在，取而代之的是1/72原来的数量，或者说，少于200个。我们的电脑可以轻易处理这个。穿越地图寻路的问题迎刃而解。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;合而为一&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;那么，我们用哪种方法呢？两者！&lt;/P&gt;
&lt;P&gt;在给定的搜索中，你得先在地图范围内，用宏观寻路的方法。然后切换到微观寻路，寻找从当前位置到路径上两个宏观节点以外的路径。每次你进入一个宏观菱形&amp;#8220;方格&amp;#8221;，你应该用微观寻路算法寻路到两个宏观节点之前。这导致浪费了每次微观寻路的后半截路径，但是你有必要这样做以便路径看起来足够好－并且这也不是很严重的浪费，因为你的微观寻路算法只计算了相关的一小段路径。一旦你到达了距离目标2-3宏观节点的地方，你应该完全切换到微观寻路算法，完成到最终位置的寻路。&lt;/P&gt;
&lt;P&gt;用这种方法，你将得到更接近现实的快速穿越地图的寻路以及能穿越角落，木桶等等就像最开始那个微观寻路例子的效果。有够酷，哈！&lt;/P&gt;
&lt;P&gt;如果你 &lt;STRONG&gt;真的&lt;/STRONG&gt; 有野心，你可以开发3种寻路器，如果你的地图实在太大，一个工作在真正的宏观层次，一个在中间层，一个在微观层。专家已经为像帝国时代(Age of Empires)这样的游戏设计了这些。这仅仅取决于你的需要。&lt;/P&gt;
&lt;P&gt;好，就这些了。像以前一样，你可以通过这个联系我：&lt;BR&gt;&lt;IMG height=26 src="/images/vckbase_com/panic/mail2.jpg" width=211 border=0&gt;&lt;BR&gt;现在，祝你好运。&lt;/P&gt;
&lt;P&gt;译者参考文章：&lt;BR&gt;&lt;!--StartFragment --&gt;&amp;nbsp;&lt;A href="http://blog.vckbase.com/panic/archive/2005/03/20/3778.html"&gt;A*寻路初探&lt;/A&gt;&lt;BR&gt;&lt;!--StartFragment --&gt;&amp;nbsp;&lt;A id=CategoryEntryList1_EntryStoryList_Entries__ctl7_TitleUrl HREF="/panic/archive/2005/03/28/4144.html"&gt;在A*寻路中使用二叉堆&lt;/A&gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/panic/aggbug/9906.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Panic</dc:creator><title>[翻译]加速&amp;优化技术</title><link>http://blog.vckbase.com/panic/archive/2005/05/17/5444.html</link><pubDate>Tue, 17 May 2005 14:28:00 GMT</pubDate><guid>http://blog.vckbase.com/panic/archive/2005/05/17/5444.html</guid><wfw:comment>http://blog.vckbase.com/panic/comments/5444.html</wfw:comment><comments>http://blog.vckbase.com/panic/archive/2005/05/17/5444.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/panic/comments/commentRss/5444.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/panic/services/trackbacks/5444.html</trackback:ping><description>&lt;FONT size=6&gt;加速&amp;amp;优化技术&lt;/FONT&gt; 
&lt;DIV class=ArticleAuthor&gt;作者：&lt;A href="mailto:tomh@globalnet.co.uk"&gt;Tom Hammersley&lt;/A&gt;&amp;nbsp;26/11/2003&lt;BR&gt;译者：&lt;A href="http://blog.vckbase.com/panic"&gt;pAnic&lt;/A&gt; 2005年5月17日&lt;BR&gt;译者序：这是一篇讲解3D引擎中，某些细节优化的文章，文中使用的方法同样适用于其他场合。不过因为本人对这方面的技术和词汇不是很熟悉，所以这篇翻译可能存在很多谬误，希望大家多加批评指正。&lt;BR&gt;原文：&lt;A href="http://www.devmaster.net/articles/speed-up/"&gt;&lt;STRONG&gt;Speed-up &amp;amp; Optimization Techniques&lt;/STRONG&gt;&lt;/A&gt;&lt;BR&gt;以下是翻译全文：&lt;BR&gt;&lt;/DIV&gt;
&lt;H1&gt;绪论&lt;/H1&gt;
&lt;P&gt;在这个页面，我收集了一些不同的加速3D引擎的小窍门。我会先介绍一些显而易见的，因为许多人会忽视它们，接着是一些更精彩的。如果你有其他的窍门和算法，尽可以告诉我。&amp;nbsp;&lt;/P&gt;
&lt;H1&gt;Float --&amp;gt; int 转化&lt;/H1&gt;
&lt;P&gt;值得尝试的做法，因为这种转化一般会更慢。我知道的这两种方法是用#pragma和内联函数。首先，#pragma：&lt;BR&gt;&lt;BR&gt;&lt;SPAN class=codeKeyword&gt;#pragma&lt;/SPAN&gt; aux RoundToInt=\&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "fistp DWORD [eax]"\&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; parm nomemory [eax] [8087]\&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; modify exact [8087];&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;这种方法表现的非常好，WDISASM 表明编译器抛弃了对__CHP的调用而内联了这个转化。另一种方法不使用#pragma,可以被移植到其他编译器。这由"InnerSect"提出： &lt;/P&gt;&lt;PRE class=code&gt;&lt;SPAN class=codeKeyword&gt;#define&lt;/SPAN&gt; FIST_MAGIC ((((65536.0 * 65536.0 * 16)+(65536.0 * 0.5))* 65536.0))
int32 QuickFist(&lt;SPAN class=codeKeyword&gt;float&lt;/SPAN&gt; inval)
{
 &lt;SPAN class=codeKeyword&gt;double&lt;/SPAN&gt; dtemp = FIST_MAGIC + inval;
 &lt;SPAN class=codeKeyword&gt;return&lt;/SPAN&gt; ((*(int32 *)&amp;amp;dtemp) - 0x80000000);
}
&lt;/PRE&gt;
&lt;P&gt;他做了一些测试，说来有趣，如果我没记错的话，他发现上面的方法更快。&lt;/P&gt;
&lt;H1&gt;提前计算&lt;/H1&gt;
&lt;P&gt;提前计算所有数据！你没法提前计算太多东西。不要以为什么都能查表，那样你会失去灵活性。关注不同的方法。首先要提前计算的是你的法向量。表面法向量和顶点法向量。对调试来说非常重要－这些向量的计算耗时惊人。一旦你提前计算了这些数值，你只要像对待对象的其他部分一样旋转他们就可以了。可是不要去平移或者缩放他们。它们必须保持为单位向量。如果你偶然缩放了他们，可以乘以一个相反的比例把他们还原；例如：&lt;/P&gt;&lt;PRE class=codeNormal&gt;                    | Sx  0  0  0 |
 Transform Matrix = |  0 Sy  0  0 |
                    |  0  0 Sz  0 |
                    |  0  0  0  0 |
 然后你需要这样:
  NormalX *= 1 / Sx
  NormalY *= 1 / Sy
  NormalZ *= 1 / Sz
&lt;/PRE&gt;
&lt;P&gt;当你进行裁减和计算光照的时候，你也不需要平移他们。Also you don't need to transform your normals when you perform culling or lighting. 简单的反向平移你考虑到的其他向量，避免矩阵/向量的乘法。你反向平移可以简单的通过重置上方3&amp;#215;3的矩阵来实现：&lt;/P&gt;&lt;PRE class=codeNormal&gt; For every row
  For every column
   output[row][column] = input[column][row]
  End
 End
&lt;/PRE&gt;
&lt;P&gt;转换灯光，视角向量，以及任何依赖这个矩阵的向量，都因为提前计算而节省了大量工作。你只需要像往常一样计算，用新的向量和旧的法向量。&lt;/P&gt;
&lt;H1&gt;倒数&lt;/H1&gt;
&lt;P&gt;如果你需要做很多次除数相同的除法，取除数的倒数，变成乘法。倒数就是1/n，n是你的除数。如果你确实很聪明，你会发现在运行 fdiv 的时候还有其他事可做。一个不错的窍门是预热高速缓存，高速缓存命中失败会导致从内存中载入数据等占用很多时间。所以，当你坐等fdiv完成的时候，为什么不读一些内存地址呢？那样它们就会被载入高速缓存了。&amp;nbsp;在我的屏幕渲染的代码里面，这样看起来运行良好。你可以把这个技术用在透视纹理映射上。也有助于你做透视转换。如果想还原那个倒数，再做一次1/n就可以了。非常精彩。如果你喜欢冒险，你可以把数据存储为倒数，例如Z。我还没试过－这会很难调试。&lt;/P&gt;
&lt;H1&gt;顶点标识&lt;/H1&gt;
&lt;P&gt;任意给定一个需要处理的顶点列表，你会发现有很多－也就是说至少一半－是不可见的，在裁减区域之外。你需要快速排除它们。一个简单的方法就是给这些顶点加一个'可见'标志。&lt;/P&gt;
&lt;P&gt;进入处理循环之前，在准备期间，把这个标志设为false。然后，进入循环之后，如果发现顶点是必要的，就设为true。例如，你发现一个三角形可见，就把它所有的顶点可见标志都设为true。在那之后，你可以简单的跳过或者排除那些没有设这个标志的顶点。这对复杂模型非常有用，因为他们包含大量顶点。同样可以这样处理有很多不可见部分的很大的场景，跳过不必要的渲染。如果用于光照，那你必须得小心。如果你进行可见性裁减的时候，没有计算光照，你会发现在边缘的地方出现错误。你在寻找已定义数据和未定义数据的交界，这很费神。。。&lt;/P&gt;
&lt;P&gt;一种方法识用一个计数器来标识。方法很简单。初始化的时候，你把计数器设置为某个非法值，例如-1 (0xFFFFFFFF).。同时，你把帧计数器设置为0。然后在你处理对象的过程中，如果你发现一个面/顶点需要处理，你就把它的计数器设置为和帧计数器相等。反之，不作任何设置和清除。接着，当你实际处理的是时候，你把它和帧计数器对比，那些计数器值和当前帧计数器相等的顶点/面片会被使用，其他的则被跳过。这在大的数据集很方便，因为那种情况下，每帧都正确清除那个标志代价高昂。&lt;/P&gt;
&lt;H1&gt;指针&lt;/H1&gt;
&lt;P&gt;指针非常方便。利用它们你能实现精巧的数据结构，就像链表，二叉树等等。你也可以利用它们快速寻址。比如说你有一个数组，每个元素都是27字节长。你可以把它们填充到32字节，利用移位来计算地址。但是，数组的每个元素都浪费了5个字节。这很浪费。因此，用指针来寻址。就是说你的顶点结构有27字节。在三角形结构中，不要用int vertindex[3]，而是用vertex *vertptr[3]。然后简单的载入指针，寻址，就可以了。(译者注：说实话，这段话我没有完全理解，有不对的地方恳请读者指正)&lt;/P&gt;
&lt;H1&gt;三角形vs 多边形&lt;/H1&gt;
&lt;P&gt;三角形易于处理，渲染速度快。但是如果你有一系列6边形构成的表面，你很容易把它们替换为多边形，不过对三角形渲染器来说，你得做6倍的工作。但是三角形渲染更快。个人推荐三角形，在基于三角形的环境操作起来很简单。多边形有它的优势，可能值得一试。如果有人拥有凸多边形偏移纹理的算法，我会非常感兴趣。&lt;/P&gt;
&lt;H1&gt;过度渲染/渲染不完全&lt;/H1&gt;
&lt;P&gt;过度渲染是速度杀手。尤其是复合渲染代码。你绘制，再次绘制，二次重绘，三次重绘，等等。损失时间和速度。一个简单的排除过度渲染的方法是从前到后排列你的三角形，给它们提供Z-Buffer/S-Buffer。这仍然导致问题。完全依赖 Z-Buffer的三角形渲染会最终变成浪费时间。扫描所有的点，而不渲染任何东西！同样的复杂的分段插入S-Buffers代码，也要付出一定代价。&amp;nbsp;对大的三角形 S-Buffer看起来运作良好。但是，对大量小三角形，它就没那么吃香了；例如，我的S-buffer算法渲染一个不到3k三角形的头部模型，只计算lambert阴影，耗费了接近30秒钟。很明显，三角形数量使得分段插入的代码负载过大。我想，这里的解决方法是开发更高效的闭合和VSD算法。将来处理这个问题有很多可能性。&lt;/P&gt;
&lt;P&gt;另一个问题是&amp;#8220;渲染不完全&amp;#8221;(我的术语)。这是说，你花费了太多时间在处理离屏的多边形上。一些有帮助的因素是，场景中的可见多边形不会明显变化。到现在为止，你大概可以计算一个FPS中的可见多边形集了，用玩家的方向寻找一些需要注意的多边形，如果玩家靠的太近，就把它们变为临界点。有效范围例如包围球/包围盒在这里也能派上用场。Hierarchial模型也可能有帮助，用来决定哪部分模型是不必要的。&lt;/P&gt;
&lt;H1&gt;总结&lt;/H1&gt;
&lt;P&gt;这里有几条规则，可以让你的引擎跑得更快：&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;尽可能提前计算 
&lt;LI&gt;不做不必要的计算 
&lt;LI&gt;不要重复计算同一内容 
&lt;LI&gt;如果可能，要利用以前计算的结果&amp;nbsp; 
&lt;LI&gt;寻找那些可以事半功倍的场合 
&lt;LI&gt;在用汇编改写你的函数前，自问&amp;#8220;我已经找到最好的解决方法了吗？&amp;#8221; 
&lt;LI&gt;试验！ 
&lt;LI&gt;冒险。 
&lt;LI&gt;&lt;EM&gt;不要&lt;/EM&gt;&amp;nbsp;重写任何代码仅仅因为&amp;#8220;看起来慢&amp;#8221; 
&lt;LI&gt;探索你的目标构架，了解它的特性&lt;/LI&gt;&lt;/OL&gt;&lt;img src ="http://blog.vckbase.com/panic/aggbug/5444.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Panic</dc:creator><title>[翻译]MMORPG开发入门</title><link>http://blog.vckbase.com/panic/archive/2005/05/11/5271.html</link><pubDate>Wed, 11 May 2005 12:13:00 GMT</pubDate><guid>http://blog.vckbase.com/panic/archive/2005/05/11/5271.html</guid><wfw:comment>http://blog.vckbase.com/panic/comments/5271.html</wfw:comment><comments>http://blog.vckbase.com/panic/archive/2005/05/11/5271.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://blog.vckbase.com/panic/comments/commentRss/5271.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/panic/services/trackbacks/5271.html</trackback:ping><description>&amp;nbsp; 
&lt;TABLE cellPadding=0 width="100%" border=0&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD class=ArticleTitle width="80%"&gt;&lt;FONT size=5&gt;MMORPG开发入门&lt;BR&gt;&lt;/FONT&gt;&lt;FONT size=3&gt;作者：&lt;!--StartFragment --&gt; Radu Privantu &lt;BR&gt;译者：&lt;A href="http://blog.vckbase.com/panic"&gt;pAnic&lt;/A&gt; 2005年5月11日&lt;BR&gt;原文链接：&lt;A href="http://www.devmaster.net/articles/building-mmorpg/"&gt;http://www.devmaster.net/articles/building-mmorpg/&lt;/A&gt;&lt;BR&gt;译者序：这是一篇讲解如何开发一款MMORPG的入门文章，作者本人也是一款游戏的开发者，文中的内容源于实践，有很高的参考价值。很多人都想拥有自己的游戏，这篇文章对那些想自己开发游戏的人来说可能是一纸福音，也可能是一盆冷水。无论如何，开发游戏都不是一件简单的事情。&lt;BR&gt;以下是翻译正文：&lt;BR&gt;&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD width="2%"&gt;&amp;nbsp;&lt;/TD&gt;
&lt;TR&gt;
&lt;TD width="100%" dragover="true"&gt;
&lt;P&gt;文章的中心是如何起步开发你自己的 大型多人在线角色扮演游戏(&amp;nbsp;原文：Massive Multiplayer Online Role Playing Games) (MMORPG)(译者注：俗称：网络游戏，网游)。 针对的读者是经验和资源有限的开发者。 读完文章之后，你应该懂得如何起步， 还有一些关于什么是应该做的和不应该做的忠告。第一步是评估你的能力和资源。你必须对自己诚实，因为做你力不从心的事情会浪费你的时间并让你心灰意冷。&lt;/P&gt;
&lt;P&gt;&lt;/P&gt;
&lt;H1&gt;第一步：评估你的能力&lt;/H1&gt;
&lt;H2&gt;必须的技能:&lt;/H2&gt;
&lt;OL&gt;
&lt;LI&gt;
&lt;P&gt;懂至少一种编程语言。 迄今为止， C++因为性能和效率的优越性成为游戏开发者的首选。 Visual Basic, Java&amp;nbsp;或者 C# 可能也是不错的选择。&lt;/P&gt;
&lt;LI&gt;熟悉一种图形库。通常的选择是SDL, OpenGL, 或者DX/D3D。(&lt;FONT color=#ff0000&gt;译者注：网上也有很多免费/付费引擎下载和出售&lt;/FONT&gt;) 
&lt;LI&gt;
&lt;P&gt;选择一种网络通讯库。 你可以从WinSock, SDL_net, 或DirectPlay中选择。(&lt;FONT color=#ff0000&gt;译者注：很多人喜欢开发自己独特的网络库，这并不复杂，似乎ACE也是一种选择&lt;/FONT&gt;)&lt;/P&gt;
&lt;LI&gt;对游戏开发有大体的经验。 例如，事件循环， 多线程， GUI 设计，等等。 &lt;/LI&gt;&lt;/OL&gt;
&lt;H2&gt;强烈推荐的技能:&lt;/H2&gt;
&lt;OL&gt;
&lt;LI&gt;C/S结构通讯。 
&lt;LI&gt;
&lt;P&gt;多平台开发。 你可能希望设计一个MMORPG, 尤其是服务器能运行在多种操作系统。为此，我推荐使用SDL, OpenGL 和SDL_net。&lt;/P&gt;
&lt;LI&gt;网站开发。如果你想让用户通过网站查看玩家统计，服务器信息和其他信息，这是必须的。(&lt;FONT color=#ff0000&gt;译者注：其实网站可以交给其他人开发，如果有必要的话&lt;/FONT&gt;)。 
&lt;LI&gt;安全管理。 你当然不想因为有人攻击你的服务器而浪费时间！ 
&lt;LI&gt;
&lt;P&gt;团队组织能力。 你需要一个你能成功领导和管理的团队。&lt;/P&gt;&lt;/LI&gt;&lt;/OL&gt;
&lt;H1&gt;第二步：初步规划&lt;/H1&gt;
&lt;P&gt;我注意到很多人在不同的论坛发帖子寻找团队开发MMORPG。他们中的大部分是这样：&amp;#8220;我们成立了一个公司/游戏工作室，需要3个美工，两个程序，1个音乐制作，等等。为了创新，不要参考过去的MMORPG，你有全部的自由用来创造你想要的世界，等等。&amp;nbsp;我们会在项目完成并赚到钱的时候付给你酬劳，等等&amp;#8221;。 不幸的是，以现有的技术和带宽，你无法拥有一个动态的世界。 朝向无法到达的目标前进只会导致失败。正确的做法是拿出一些小规模的，功能性强的，可扩展的设计和构架。&lt;/P&gt;
&lt;H2&gt;基本软件构架&lt;/H2&gt;首先，尝试创建一个简单的C/S模型，有如下功能：&lt;BR&gt;
&lt;OL&gt;
&lt;LI&gt;创建一个新角色 
&lt;LI&gt;保存那个角色(服务器端) 
&lt;LI&gt;用那个角色登陆 
&lt;LI&gt;能够和其他人交谈 
&lt;LI&gt;能在3D空间游览&lt;/LI&gt;&lt;/OL&gt;保存角色看起来简单，其实不然。&amp;nbsp;例如，有两种方式保存角色：使用数据库服务或者使用文件。两者有各自的优缺点： &lt;BR&gt;&lt;BR&gt;
&lt;TABLE cellSpacing=0 cellPadding=3 width="100%" border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD noWrap width="10%"&gt;&amp;nbsp;&lt;/TD&gt;
&lt;TD align=middle width="45%"&gt;&lt;B&gt;数据库&lt;/B&gt;&lt;/TD&gt;
&lt;TD align=middle width="45%"&gt;&lt;B&gt;文件&lt;/B&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;B&gt;优点&lt;/B&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;UL&gt;
&lt;LI&gt;添加新域或者修改现有的都很简单。 
&lt;LI&gt;更新玩家统计数据非常简单(从游戏外)。 
&lt;LI&gt;你可以通过SQL查询方便的获取不同种类的统计结果。 
&lt;LI&gt;
&lt;P&gt;无需自行完成I/O操作，数据库会替你做好。 &lt;/P&gt;
&lt;LI&gt;易于更新/恢复。&lt;/LI&gt;&lt;/UL&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;UL&gt;
&lt;LI&gt;高速操作(读/写)。 
&lt;LI&gt;实现简单。 
&lt;LI&gt;无需额外的库。 
&lt;LI&gt;
&lt;P&gt;不依赖数据库服务器。因此你不必担心数据库升级或安全问题。 &lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;&amp;nbsp;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;B&gt;缺点&lt;/B&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;UL&gt;
&lt;LI&gt;容易出错。 例如，做一个更新查询的时候遗漏了'where'子句。会导致惨痛的损失，尤其是你没有备份的时候。 
&lt;LI&gt;数据库会比打开/写入一个玩家档案文件慢。你查询一些数据的时候会耗费几个毫秒，尤其是大量玩家同时登入/登出的时候。 
&lt;LI&gt;需要额外的代码进行游戏和数据库间的数据转换。 
&lt;LI&gt;需要操作数据库和SQL的经验。并且需要一个程序和数据库之间的接口库。 
&lt;LI&gt;如果因为某些原因数据库文件损坏，那算你倒霉，你可能会丢失所有的玩家数据(尤其是短期内没有备份的时候)。&lt;/LI&gt;&lt;/UL&gt;&lt;/TD&gt;
&lt;TD&gt;
&lt;UL&gt;
&lt;LI&gt;很难添加新的域，除非一开始就很小心的设计了文件的格式/结构。 
&lt;LI&gt;没法做全体玩家的查询。(这可以通过每天晚上用程序把重要字段添加进一个数据库间接实现)。 
&lt;LI&gt;
&lt;P&gt;如果你想更新/检查玩家状态，你必须额外写代码。 &lt;/P&gt;
&lt;LI&gt;更新和还原比较复杂。&lt;/LI&gt;&lt;/UL&gt;&amp;nbsp;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P&gt;现在你决定了如何存储角色，你还得选择C/S通讯的网络协议：TCP 还是 UDP？,我们都知道TCP速度慢，但是更准确，并且需要额外带宽。我实际使用TCP并没有遇到什么问题。&amp;nbsp;如果你有充足的带宽，TCP是个好选择，至少对初学者是这样。&amp;nbsp; UDP 会很麻烦，尤其是对新手。 记住，游戏或引擎的初步测试会在你的局域网进行，所有的包都会按顺序依次抵达。在Internet上无法保证这一点。虽然包会按顺序到达，但是有时候会丢包，这通常是个麻烦事。&amp;nbsp;当然，你可以设计你的协议使得C/S能够从丢包中恢复。但这对初学者来说很痛苦，不值得推荐。 &lt;/P&gt;
&lt;H1&gt;第三步：选择数据传输协议&lt;/H1&gt;
&lt;P&gt;又是看起来很简单，其实不然。你不能只是发送'\0'结尾的串。因为你需要一个通用的协议，能同时适用字符串和二进制数据。用0(或其他字符)做结束符是不明智的，因为那个结束符可能是你要发送的数据的一部分。此外，如果你发送20字节，然后再20字节，服务器极有可能收不到两个20字节的包。取而代之的是，它会一次性收到40字节，这是为了避免浪费带宽在不必要的头上。&amp;nbsp;而且，你可以发送1KB的包，但服务器会以两个小包的形式收到它。所以你必须知道哪里是一个包的开始，哪里是结束。在 &amp;#8220;永恒大陆&amp;#8221;(&lt;FONT color=#ff0000&gt;译者注：原文：&amp;nbsp;Eternal Lands，本文的作者正在开发的一款MMORPG&lt;/FONT&gt;)中，我们用如下的方法：&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;B&gt;Offset 0&lt;/B&gt;: 1&amp;nbsp;字节 表示传输的命令。 
&lt;LI&gt;&lt;B&gt;Offset 1&lt;/B&gt;: 2 字节，传输的数据长度。 
&lt;LI&gt;&lt;B&gt;Offset 3&lt;/B&gt;: 变长，消息内容。&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;这种方法有一致的优点：所有的数据传输有统一的标准。缺点是有些命令有固定已知的长度，浪费了一些带宽。以后我们会改成混合的方法。&lt;/P&gt;
&lt;P&gt;下一件事是决定服务器模型： &amp;#8220;非阻塞soket,不使用线程&amp;#8221;，或者&amp;#8220;阻塞soket，使用线程&amp;#8221;。两种方法(使用线程 vs 不使用线程)各有优缺点。&lt;/P&gt;
&lt;P&gt;&lt;B&gt;线程:&lt;/B&gt;&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;服务器响应会更加平滑，因为如果一个玩家需要大量时间(例如从数据库中读取数据)，这会在它自己的线程中完成，不会影响其他人。(&lt;FONT color=#ff0000&gt;译者注：也许作者的意思是每个玩家都有独立的线程，但这对MMORPG不太现实&lt;/FONT&gt;) 
&lt;LI&gt;难以恰当的实现和调试：你可能需要大量同步，并且一个小疏忽就会导致灾难性的后果( 服务器瘫痪，物品复制，等等)。 
&lt;LI&gt;可以利用多处理器。&lt;/LI&gt;&lt;/OL&gt;&lt;B&gt;无线程:&lt;/B&gt; 
&lt;OL&gt;
&lt;LI&gt;实现和调试更简单。 
&lt;LI&gt;响应速度慢。&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;在我的公司，我们使用无线程的方法，因为我没有足够的资源和人力处理线程模式。&lt;/P&gt;
&lt;H1&gt;第四步：客户端&lt;/H1&gt;
&lt;P&gt;你打算做2D还是3D游戏？有些人认为2D游戏做起来简单。我两者都做过，并且我倾向于3D游戏更简单。容我解释。&lt;/P&gt;
&lt;P&gt;2D下，你通常有一个帧缓冲，也就是一个巨大的象素点数组。象素点的格式会因显卡的不同而不同。 有些是RGB模式，另一些是BGR模式，等等。每种颜色的bit数也会不同。只有在16bpp模式才有这个问题。8-bit和24-bit模式简单一些，但有他们各自的问题(8-bit颜色数太少(256)，而24-bit速度更慢)。同时，你需要制作你的精灵动画程序，不得不自己排序所有对象，以便他们以正确的顺序绘制。&amp;nbsp;当然，你可以用OpenGL或者D3D制作2D游戏，但通常这并不值得。并不是所有人都有3D加速卡，所以使用3D库开发2D游戏一般会带给你两者的缺点：不是所有人都能玩，你也不能旋转摄像机，拥有漂亮的阴影，和3D游戏炫目的效果。(&lt;FONT color=#ff0000&gt;译者注，目前绝大部分显卡都支持565的16bpp格式，这个也成为目前16位色的业界通用格式，有不少文章和代码都是讲述这一格式下图像处理的，尤其是使用MMX技术&lt;/FONT&gt;)&amp;nbsp;&lt;/P&gt;
&lt;P&gt;3D的途径，正如我所说，更简单。但是需要一些数学(尤其是三角)的知识。现代的图形库很强大，免费提供了基本的操作(你不需要从后到前排列对象，改变物体的色彩和/或帖图都十分简单，对象的光照会按照光源和它的位置计算(只要你为它们计算了法向量)，还有更多)。并且。3D给了你的创作和运动更多的自由度，缺点就是不是所有人都能玩你的游戏(没有3D卡的人数可能会让你大吃一惊的)，并且，预渲染的图片总是比实时渲染的更漂亮。(&lt;FONT color=#ff0000&gt;译者注：市面上想买不支持3D的显卡目前很困难，只是高性能的3D卡价格也不低&lt;/FONT&gt;)&lt;/P&gt;
&lt;H1&gt;第五步：安全&lt;/H1&gt;
&lt;P&gt;显然，不能相信用户。任何时候都不能假设用户无法破解你精巧的加密算法(如果你使用了的话)或者协议，用户发送的任何信息都要通过验证。极有可能，在你的服务器上，你有固定的缓冲区。例如，通常有一个小(可能是4k)缓冲区用来接收数据(从soket)。恶意用户会发送超长数据。如果不检查，这会导致缓冲区溢出，引起服务器瘫痪，或者更坏的，这个用户可以hack你的服务器，执行非法代码。每个单独的消息都必须检查：缓冲区是否溢出，数据是否合法(例如用户发送&amp;#8220;进入那扇门&amp;#8221;，即使门在地图的另一端，或者&amp;#8220;使用治疗药水&amp;#8221;尽管用户没有那种药水，等等)。 我再次强调，验证所有数据非常重要。一旦有非法数据，把它和用户名，IP，时间和日期，和非法的原因记录下来。偶尔检查一下那个记录。如果你发现少量的非法数据，并且来自于大量用户，这通常是客户端的bug或者网络问题。然而，如果你发现从一个用户或者IP发现大量非法数据，这是明显的迹象表明有人正在欺骗服务器，试图hack服务器，或者运行宏/脚本。同时，决不要在客户端存储数据。客户端应该从服务器接收数据。换句话说，不能发送这样的消息&amp;#8220;OK，这是我得物品列表&amp;#8221;或者&amp;#8220;我的力量是10，魔法是200，生命值是2000/2000&amp;#8221;。&amp;nbsp;而且，客户端不应收到它不需要的数据。例如：客户端不应该知道其他玩家的位置，除非他们在附近。&amp;nbsp;这是常识，给每个人发送所有玩家会占用大量带宽，并且有些玩家会破解客户端从中获取不公平的利益(像在地图上显示特定玩家的位置)(&lt;FONT color=#ff0000&gt;译者注：就像传奇的免蜡烛外挂&lt;/FONT&gt;)。所有这些似乎都是常识，但，再次，你会惊奇的发现有多少人不知道这些我们认为的常识。 &lt;/P&gt;
&lt;P&gt;另一个要考虑的问题，当涉及到安全：玩家走动的速度必须在服务器计算，而不是客户端。(&lt;FONT color=#ff0000&gt;译者注：这是重要的原则，但是会耗费大量服务器资源。魔兽世界没有这样做，它采用类似其他玩家揭发的形式掩盖这个事实，导致加速外挂可以用，但是在有其他玩家的时候会暴露&lt;/FONT&gt;)。服务器应该跟踪时间(以ms为单位)当客户最后一次移动的时候，并且，移动的请求如果比通常的极限更快到来，这个请求应该被抛弃。不要记录这类虚假请求，因为这可能是因为网络延迟(也就是玩家延迟，过去的10秒内发送的数据同时到达了)。&lt;/P&gt;
&lt;P&gt;检查距离。如果一个玩家试图和100亿公里以外的玩家交易(或者甚至在另一张地图上)，记录下来。如果一个玩家试图查看，或者使用一个遥远的地图对象，记录它。小心假的ID。例如，正常情况下每个玩家都会分配一个ID(ID在登陆的时候分配，可以是持久的(唯一ID)。 如果ID在玩家登陆的时候赋予9或怪物被创建的时候)，显然可以用玩家数组(保存玩家)的位置(索引)作为ID。&lt;/P&gt;
&lt;P&gt;所以第一个登陆的玩家ID是0，第二个是1，依此类推。现在，通常你会有一个限制，比如说2000个索引在玩家列表里。所以如果一个客户端发送一条命令类似：&amp;#8220;查看ID200000的角色&amp;#8221;，这会使服务器当机，如果没有防备的话，因为服务器会访问非法的内存区域。所以，一定要检查，就像这样： "if actor id&amp;lt;0 or if actor id&amp;gt; max players 然后记录非法操作并且断开玩家。如果你使用C或者C++，注意或者定义索引为'unsigned int' 并且检查上限，或因为某些原因定义为int(int,默认是有符号的)，记得检查 &amp;lt;0 and &amp;gt;max&amp;nbsp;。没有做这些会严重挫伤你和其他用户。类似的，要检查超出地图坐标。如果你的服务器有某种寻路算法，并且客户端通过点击地面来移动，确保他们不要点击在地图外部。 &lt;/P&gt;
&lt;H1&gt;第六步：获得一个团队&lt;/H1&gt;
&lt;P&gt;制作游戏需要大量的工作(除非是个Pong and Tetris游戏)。尤其是MMORPG。你无法单靠自己。理论上，一个完整的团队组成是这样：&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;至少&lt;B&gt;3 个程序员：&lt;/B&gt; 1 个做服务器，两个客户端(或者一个客户端，一个负责工具，例如美术插件，世界编辑器，等等)。有6个程序员是最好的，更多就没必要了。这取决于你的领导能力。最少一个美工，2到3个更合适。如果这是个3D游戏，你需要一个3D美工，一个2D美工(制作帖图，界面，等等)，一个动画师，和一个美术部负责人。美术部应该由有经验的人组织和安排，除非你就是个艺术家。, 
&lt;LI&gt;&amp;nbsp;&lt;STRONG&gt;少数世界构建者:&lt;/STRONG&gt;创建所有地图是个漫长的过程， 并且直接关系到游戏的成败。再次，你需要一个世界构建部的负责人。你的世界需要协调一致，所以不能只有一个意气用事的人。 
&lt;LI&gt;一个&amp;nbsp;&lt;B&gt;网站管理员&lt;/B&gt;是必须的，除非你精通网站设计，并且愿意花时间做网站。音效和音乐不是必须的，但是有音效和音乐的游戏比没有的会更吸引人。 
&lt;LI&gt;
&lt;P&gt;一个游戏经济系统&amp;nbsp;&lt;B&gt;设计师&lt;/B&gt;.。你也许觉得那很简单，可以自己来做，但事实上那是最复杂的工作之一。如果经济系统设计不良(比如物品没有平衡，资源在地图上随意放置，等等。)玩家会觉得无聊并且退出游戏。我们早期的进展存在很大的问题，尤其是因为经济系统主要是由我(一个程序员)设计的，它没有被恰当的计划。&amp;nbsp;于是，我们花费了两个月来重新思考和建立一整个新的经济系统。这需要一次完全的物品清除。我告诉你，玩家会很不乐意你删除他们的物品。幸运的是，大部分玩家赞同这个想法，但是这么多小时的争论，妥协，解释和时间的浪费还是让我们丧气。以后会更多。 &lt;/P&gt;&lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;如前所说，你需要一个10~15人的团队，不包括协调员和管理者。这10~15人必须是有经验的。如果都是新手就不值得，因为你需要花大量时间解释要做什么，怎样做，为什么他现在的做法不好，等等。&lt;/P&gt;
&lt;P&gt;一开始就凑齐10~15人几乎是不可能的。不管你在不同的论坛发多少帖，你也无法找到合适的团队成员。毕竟，如果一个人在他/她的领域得心应手，为什么在你无法拿出任何东西的时候他/她要加入你的团队？很多人有远大的想法，但是实现它们需要大量时间和努力，所以他们宁可从事自己的工作也不会加入你。那如果你需要10~15人，但是无法让他们加入你的团队，你如何才能制作一款MMORPG呢？ 好，事实上，你一开始不需要所有人都到位。你真正需要的是一个程序员和一个美工。如果你是个程序员，只要找个美工就可以了。请求懂美术的朋友帮忙，花钱请大学生/朋友做一些美术或者其他工作。&lt;/P&gt;
&lt;P&gt;现在你有了一个美工，你期待的游戏的样子，现在可以开始实现了。一旦你有了可以运行的C/S引擎，一些用来展示的截图(或者更好，玩家可以登陆你的世界，四处走动，聊天)，更多的人会愿意加入你的团队。更恰当的是，除非你使用独有的技术，否则你的客户端可以开源。许多程序员会加入(作为志愿者)一个开源工程而不是非开源项目。而服务器不应该开源(除非你打算做一款完全开源的MMORPG)。&lt;/P&gt;
&lt;P&gt;其他一些忠告：在有东西可展示之前，不要夸大你的游戏。最惹人烦的事情之一就是一个新手发一个&amp;#8220;需要帮助&amp;#8221;的请求，并且解释这个游戏到底有多酷，最后要求一个巨大的团队加入他的游戏制作。一旦你拥有了网站广告(通常是在一个免费主机)，你会看到一个吸引人的导航条，包含&amp;#8220;下载&amp;#8221;，&amp;#8220;截图&amp;#8221;，&amp;#8220; 原画&amp;#8221;(译者注，原文：Concept art，概念艺术，在游戏应该指美工的原始设计)，&amp;#8220;论坛&amp;#8221;。你点击下载链接，然后看到美妙的&amp;#8220;建设中&amp;#8221;页面(或者更糟糕，一个404错误)。然后你点击截图，得到同样的结果。如果你没有东西给人下载，就不要放下载链接。如果没有截图展示，不要放截图链接。然而更好的是，在工程进展10%(程序和美工)之前，不要浪费时间在网站上。 &lt;/P&gt;
&lt;H1&gt;第七步：打破某些神话&lt;/H1&gt;
&lt;OL&gt;
&lt;LI&gt;&lt;B&gt;你无法制作MMORPG, 只有大公司才可以。&lt;/B&gt;&lt;BR&gt;我不同意。虽然制作一款像魔兽世界(World of Warcraft)，无尽任务2(Ever Quest 2)，亚瑟王的召唤2(Asheron's Call 2)，血统2(Lineage 2)，和其他一些游戏对一个小型的自发团队是不可能的，但是做一款像样的游戏还是可以的，只要你有经验，动机，和时间。,你需要1000小时的编程来制作一个可运行的测试版，大概10~15k小时完成几乎完整的客户端和服务器。但是作为团队领导者，你不能只编程。保持团队团结，解决争执，维护公共关系(PR)，技术支持，架设服务器，惩罚捣乱分子，自由讨论，等等都是你的职责。你可能会被非编程的任务淹没。你很可能需要上班/上学，这减少了你花费在项目上的时间。我们很幸运，没有成员离开团队，但是如果这种事情发生，那的确是大问题。假设你的美工半途离开。或者更糟糕，他/她没有给你使用他/她作品的许可。当然这可以通过和他们签订合同来解决，但找另外一个美工仍然很麻烦。一个工程中有两种不同的美术风格也是问题。 
&lt;LI&gt;
&lt;P&gt;&lt;B&gt;需要大笔金钱(通常 4-6 位数)&amp;nbsp;用来架设一个 MMORPG 服务器.&lt;BR&gt;&lt;/B&gt;当然，这不是真的。我见过专业服务器，1000GB/月，不到100美元/月(2~300美元的初装费)。除非你的数据传输协议设计非常不合理，1000GB/月对一个1000玩家在线(平均)的服务器来说足够了。当然，你还需要另一个服务器做网站和客户端下载(客户端下载会占用大量流量，当游戏变得流行的时候)。我们的客户端有22MB，有时候会有400GB/月的传输量。而我们还没有很流行(仍然)。另一件事，我们不需要另一台专用服务器开启这个工程。ADSL/cable服务器可以胜任，直到你的同时在线人数达到20~30。然后要么找一个友好的主机公司，用广告交换免费主机，要么就只能自己掏腰包了。&amp;nbsp; &lt;/P&gt;
&lt;LI&gt;
&lt;P&gt;&lt;B&gt;制作一个MMORPG很有趣。&lt;BR&gt;&lt;/B&gt;这不是真的。你可能认为每个人都会赏识你，玩家会支持你，你标新立异，并且，当然，很多玩家都玩你的游戏。玩家可能让人讨厌。即使是完全免费的游戏，他们也能找到理由抱怨。更糟糕的是人们经常会抱怨矛盾的事。战士会抱怨升级太难，商人会对战士掠夺大量钱财很失望。如果你减少怪物掉落物品，有些玩家就会威胁说要退出游戏。如果你增加，同样的一群人会不满新手能更简单赚钱的事实。 真是左右为难。改革和改进是必须的。如果你决定改变某些东西，例如给加工物品增加挑战性，有些人会说太难了。如果你不做，他们又会说太简单无味。你会发现满意的玩家通常不会说什么并且感到满意，同时破坏者会怨声载道。&lt;/P&gt;&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;MMORPG的经济比单机版难以平衡的多。在单机游戏，你可以逐渐改良武器，只要玩家进展，他/她可以使用更好的装备，丢弃(或者卖掉)旧的。另一方面，在多人游戏里，这种观点不成立，因为每个人都试图得到最好的武器，而跳过低等级武器。大部分玩家宁可空手省钱，直到他们能买游戏中最好的武器。经济系统设计要参考相关的文章。&lt;/P&gt;
&lt;P&gt;迄今为止我列举的所有事情，加上额外的工作和挑战，足以让你在决定涉足这个工程之前三思而行。你必须知道你的决定意味着什么。&lt;/P&gt;
&lt;H1&gt;总结&lt;/H1&gt;
&lt;P&gt;希望这篇文章能给你足够的知识。我的下一篇文章将介绍如何建立一个经济系统(更明确的，要避免哪些错误)，还有一些调试服务器和客户端的信息。&lt;/P&gt;
&lt;H1&gt;关于作者&lt;/H1&gt;
&lt;P dragover="true"&gt;这篇文章作者是&amp;nbsp;Radu Privantu, 永恒大陆(Eternal Lands) &lt;A href="http://www.eternal-lands.com/"&gt;www.eternal-lands.com&lt;/A&gt;的主程序和项目规划, 永恒大陆是一款免费，客户端开源的MMORPG。作者可以通过 chaos_rift at yahoo dot com 联系。&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;img src ="http://blog.vckbase.com/panic/aggbug/5271.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Panic</dc:creator><title>[翻译]C/C++ 跨平台I/O操作技巧</title><link>http://blog.vckbase.com/panic/archive/2005/05/09/5231.html</link><pubDate>Mon, 09 May 2005 10:05:00 GMT</pubDate><guid>http://blog.vckbase.com/panic/archive/2005/05/09/5231.html</guid><wfw:comment>http://blog.vckbase.com/panic/comments/5231.html</wfw:comment><comments>http://blog.vckbase.com/panic/archive/2005/05/09/5231.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://blog.vckbase.com/panic/comments/commentRss/5231.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/panic/services/trackbacks/5231.html</trackback:ping><description>&lt;P align=justify&gt;&lt;SPAN class=title&gt;C/C++&lt;/SPAN&gt; 跨平台I/O操作技巧&lt;BR&gt;&lt;SPAN class=author&gt;作者：&amp;nbsp;&lt;A href="mailto:johnjbolton@yahoo.com"&gt;John Bolton&lt;/A&gt;&lt;BR&gt;译者：&lt;A href="http://blog.vckbase.com/panic"&gt;pAnic&lt;/A&gt; 2005年5月9日&lt;BR&gt;&lt;/SPAN&gt;&lt;/P&gt;译者序：文章的内容很简单，也不见得完全正确。跨平台I/O需要考虑的事情远非这篇文章的篇幅所能描述，这里介绍的只是一些最基本，最简单的需要注意的事情。&lt;BR&gt;以下是翻译的正文：&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;
&lt;P&gt;如果你正在写从文件或网络读写数据的跨平台C/C++代码，那么你必须明白有些问题是因语言，编译器，平台而不同的。 主要的问题是数据对齐，填充，类型大小，字节顺序和默认状态char是否有符号。&lt;/P&gt;
&lt;H1&gt;对齐&lt;/H1&gt;
&lt;P&gt;特定机器上，特定的数据被对齐于特定的边界。如果数据没有正确对齐，结果可能是效率降低甚至崩溃。 当你从I/O源读取数据的时候，确保对齐是正确的。&lt;/P&gt;
&lt;H1&gt;填充&lt;/H1&gt;
&lt;P&gt;"填充" 是数据集合中不同元素之间的间隔， 一般是为了对齐而存在。不同编译器和平台下，填充的数量可能会不同。&amp;nbsp;不要假设结构的大小和成员的位置在任何编译器和平台下都是相同的。 不要一次性读取或者写入一整个结构体，因为写入的程序可能会使用和读取的程序不同的填充方式。对于域也同样适用。&lt;/P&gt;
&lt;H1&gt;类型大小&lt;/H1&gt;
&lt;P&gt;不同数据类型的大小随编译器和平台而不同。 在C/C++中， 内置类型的大小完全取决于编译器(在特定范围内). 不要读写大小不明确的数据类型。也就是说，不要读写&lt;SPAN class=code&gt;bool&lt;/SPAN&gt;, &lt;SPAN class=code&gt;enum&lt;/SPAN&gt;, &lt;SPAN class=code&gt;long&lt;/SPAN&gt;, &lt;SPAN class=code&gt;int&lt;/SPAN&gt;, &lt;SPAN class=code&gt;short&lt;/SPAN&gt;, &lt;SPAN class=code&gt;float&lt;/SPAN&gt;, 或者&lt;SPAN class=code&gt;double类型&lt;/SPAN&gt;.(译者注：事实似乎不是这样，我记得C/C++标准规定了一些数据类型的长度，例如short 2字节，long 4字节等等，在符合标准规定的编译器上，使用这些类型可以保证跨平台的正确性)&lt;/P&gt;
&lt;P&gt;
&lt;TABLE cellSpacing=0 cellPadding=3 border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD class=tblhdr&gt;用这些&lt;/TD&gt;
&lt;TD class=tblhdr&gt;替代这些...&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;int8&lt;/SPAN&gt;, &lt;SPAN class=code&gt;uint8&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;char&lt;/SPAN&gt;, &lt;SPAN class=code&gt;signed char&lt;/SPAN&gt;, &lt;SPAN class=code&gt;unsigned&lt;/SPAN&gt; &lt;SPAN class=code&gt;char&lt;/SPAN&gt;, &lt;SPAN class=code&gt;enum&lt;/SPAN&gt;, &lt;SPAN class=code&gt;bool&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;int16&lt;/SPAN&gt;, &lt;SPAN class=code&gt;uint16&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;short&lt;/SPAN&gt;, &lt;SPAN class=code&gt;signed short&lt;/SPAN&gt;, &lt;SPAN class=code&gt;unsigned&lt;/SPAN&gt; &lt;SPAN class=code&gt;short&lt;/SPAN&gt;, &lt;SPAN class=code&gt;enum&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;int32&lt;/SPAN&gt;, &lt;SPAN class=code&gt;uint32&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;int&lt;/SPAN&gt;, &lt;SPAN class=code&gt;signed int&lt;/SPAN&gt;, &lt;SPAN class=code&gt;unsigned&lt;/SPAN&gt; &lt;SPAN class=code&gt;int&lt;/SPAN&gt;, &lt;SPAN class=code&gt;long&lt;/SPAN&gt;, &lt;SPAN class=code&gt;signed&lt;/SPAN&gt; &lt;SPAN class=code&gt;long&lt;/SPAN&gt;, &lt;SPAN class=code&gt;unsigned long&lt;/SPAN&gt;, &lt;SPAN class=code&gt;enum&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;int64&lt;/SPAN&gt;, &lt;SPAN class=code&gt;uint64&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;long&lt;/SPAN&gt;, &lt;SPAN class=code&gt;signed long&lt;/SPAN&gt;, &lt;SPAN class=code&gt;unsigned&lt;/SPAN&gt; &lt;SPAN class=code&gt;long&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;int128&lt;/SPAN&gt;, &lt;SPAN class=code&gt;uint128&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;long long, signed long&lt;/SPAN&gt; &lt;SPAN class=code&gt;long, unsigned long&lt;/SPAN&gt; &lt;SPAN class=code&gt;long&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;float32&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;float&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;float64&lt;/SPAN&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;SPAN class=code&gt;double&lt;/SPAN&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;/P&gt;
&lt;H1&gt;字节顺序&lt;/H1&gt;
&lt;P&gt;字节顺序，就是字节在内存中存储的顺序。 不同的处理器存储多字节数据的顺序是不同的。 小端处理器由低到高存储(换句话说，和书写的顺序相反).。大端处理器由高到低存储(和书写顺序相同)。如果数值的字节顺序和读写它的处理器不同，它必须被事先转化。同时，为了标准化网络传输的字节顺序，定义了网络字节顺序。 &lt;/P&gt;
&lt;H1&gt;&lt;SPAN style="FONT-FAMILY: 'Courier New'"&gt;char&lt;/SPAN&gt; - 有符号还是无符号?&lt;/H1&gt;
&lt;P&gt;一个鲜为人知的事实，char默认可以是有符号的也可以是无符号的-完全取决于编译器。结果导致你从char转化为其他类型的时候(比如int)，结果会因编译器而不同。&amp;nbsp; 例如：&lt;/P&gt;&lt;PRE class=code&gt;char   x;
int    y;

read( fd, &amp;amp;x, 1 );   // 读取一个byte值为0xff
y = x;               // y 是 255 或者 -1, 依赖编译器
&lt;/PRE&gt;
&lt;P&gt;不要把数据读入一般的char。明确指定是有符号或者无符号的。&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/panic/aggbug/5231.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Panic</dc:creator><title>[翻译]使用管理器</title><link>http://blog.vckbase.com/panic/archive/2005/04/06/4425.html</link><pubDate>Wed, 06 Apr 2005 05:30:00 GMT</pubDate><guid>http://blog.vckbase.com/panic/archive/2005/04/06/4425.html</guid><wfw:comment>http://blog.vckbase.com/panic/comments/4425.html</wfw:comment><comments>http://blog.vckbase.com/panic/archive/2005/04/06/4425.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://blog.vckbase.com/panic/comments/commentRss/4425.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/panic/services/trackbacks/4425.html</trackback:ping><description>&amp;nbsp; 
&lt;TABLE cellSpacing=0 cellPadding=3 width="100%" border=0&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD align=middle colSpan=2&gt;
&lt;P align=justify&gt;&lt;SPAN class=title&gt;使用管理器&lt;BR&gt;&lt;/SPAN&gt;作者： &lt;A href="mailto:e.j.folkertsma@student.utwente.nl"&gt;Ebor Folkertsma&lt;/A&gt;&lt;BR&gt;译者：&lt;A href="http://blog.vckbase.com/panic"&gt;&lt;a title="Panic" href="http://blog.vckbase.com/panic/" &gt;panic&lt;/a&gt;&lt;/A&gt; 2005年4月6日&lt;BR&gt;译者序：这篇文章适合已经对模板有初步认识的读者，文章介绍的方法很简单，尽管不是最好的，但是的确可以满足很多情况的应用。希望看过这篇文章的朋友能写出更好，更适合自己的代码。&lt;BR&gt;原文链接：&lt;A href="http://www.gamedev.net/reference/articles/article1826.asp"&gt;http://www.gamedev.net/reference/articles/article1826.asp&lt;/A&gt;&lt;BR&gt;以下是翻译全文：&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;TABLE cellSpacing=0 cellPadding=5 width="100%" border=0&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;
&lt;P align=justify&gt;&lt;SPAN class=author&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;
&lt;H1&gt;关于文章？&lt;/H1&gt;
&lt;P&gt;文章介绍了一种管理各种数据结构的好办法.&amp;nbsp;我将探讨如何制作一个 &lt;I&gt;单件管理器模板&lt;/I&gt;. 对那些不明白前两个词（译者注：指模板和单件）含义的人，网上有很多不错的教程. 为了完全明白这篇文章的含义，你最好先了解模板和单件. 我不在这里讨论这两个问题，因为认真的程序员应该熟悉他们。&lt;/P&gt;
&lt;H1&gt;目的&lt;/H1&gt;
&lt;P&gt;文章的目的是编写一个稳定的管理器，提供了基本功能，并且永远都不需要重写.&amp;nbsp;那这对你来说，意味着什么呢？ 利用它，你可以管理自己的数据结构，不需要浪费时间在重复的工作上。 你可以用它管理各种数据，例如图像，纹理，声音，模型，等等。 &lt;/P&gt;
&lt;H1&gt;如何编写一个&amp;#8220;普通&amp;#8221;模板管理器&lt;/H1&gt;
&lt;P&gt;首先我解释一下如何编写一个&amp;#8220;普通&amp;#8221;模板管理器, 它比后面讲的单件模板管理器要简单的多. 我们从一段代码开始，然后我会解释相关内容：&lt;/P&gt;
&lt;BLOCKQUOTE&gt;&lt;PRE class=code&gt;template &amp;lt;class T, int n&amp;gt; class TManager
{
public:
    TManager(void);
    ~TManager(void);	

    int   Add(const T* p_T);
    int   Delete(const T* p_T);
    int   Delete(int index);
    int   DeleteAll();
	
    T*    Get(int index);
    T*    Get(const T* p_T);
	
    Int   GetCount();
    int   GetError();
    char *GetErrorString();

protected:
	int  error;
	T*   p_T[n];	
};
&lt;/PRE&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;(注意，这只是接口，想看具体实现，请看附带的源代码。)&lt;/P&gt;
&lt;P&gt;这就是全部接口。 大部分函数功能显而易见, 只有最后三个有点奇怪. 这些函数不是必须的，我只是这样用。GetCount() 函数返回管理器能管理的对象数量上限。&amp;nbsp;GetError() 函数返回最后一次函数错误的错误码。 一般我只提供两种返回值－－一个表示成功，一个表示失败。一旦失败，我就调用GetError() 函数得到错误。 你会注意到，这也是OpenGL和其他一些标准API运作的方式。 GetErrorString() 函数返回一个适当的错误信息描述(这在调试和Log的时候会派上用场)。 理解这里行为的关键是这个模板&amp;#8220;生成&amp;#8221;代码, 当你从它继承一个类的时候，每个函数的实际代码。 注意：'T'表示你要管理的类，'n'表示你的管理器的最大容量.&lt;/P&gt;
&lt;P&gt;当你查看源代码的时候&amp;nbsp;(附在文档后) 聪明人会发现我做了些存疑的操作. 在一些方法里，我比较和赋值类对象用了标准的'=='和'='操作符。因此，如果你要使用这个管理器，重载这两个操作符是很重要的。 不过因为重载这两个操作符很简单，我把它留给你做练习。&lt;/P&gt;
&lt;H1&gt;使它成为&amp;#8216;单件&amp;#8217;模板管理器&lt;/H1&gt;
&lt;P&gt;现在你明白了&amp;#8216;普通&amp;#8217;模板的运作，这里是&amp;#8216;单件&amp;#8217;模板管理器的实现:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;&lt;PRE class=code&gt;template &amp;lt;class T, int n&amp;gt; class TPersistentManager
{
public:
	TPersistentManager(void);
	~TPersistentManager(void);	

	int  Add(const T* p_T);
	int  Delete(const T* p_T);
	int  Delete(int index);
	int  DeleteAll();
	
	T*   Get(int index);
	T*   Get(const T* p_T);
	
	Int  GetCount();
	int  GetError();
	char *GetErrorString();

private:
	static int refcount;
	static TManager&amp;lt;T, n&amp;gt;	*p_m_tManager;	
};

// Note that you MUST initialize these for correct functionality
template &amp;lt;class T, int n&amp;gt; int TPersistentManager&amp;lt;T, n&amp;gt;::refcount = 0;
template &amp;lt;class T, int n&amp;gt; TManager&amp;lt;T, n&amp;gt;* TPersistentManager&amp;lt;T, n&amp;gt;::p_m_tManager = 0x00000000;

template &amp;lt;class T, int n&amp;gt;
TPersistentManager&amp;lt;T, n&amp;gt;::TPersistentManager(void)
{
    if(refcount == 0)
    {
        p_m_tManager = new TManager&amp;lt;T, n&amp;gt;;
    }

    refcount++;
}


template &amp;lt;class T, int n&amp;gt; 
TPersistentManager&amp;lt;T, n&amp;gt;::~TPersistentManager(void)
{
    refcount--;

    if(refcount == 0)
    {
        delete p_m_tManager;
	  p_m_tManager = 0x00000000;
    }
}

template &amp;lt;class T, int n&amp;gt; 
int TPersistentManager&amp;lt;T, n&amp;gt;::Add(const T* p_T)
{
    return p_m_tManager-&amp;gt;Add(p_T);
}

// .
// . Rest of code omitted&lt;!--StartFragment --&gt;&amp;#8230; see the attached code files
// .

template &amp;lt;class T, int n&amp;gt; 
char *TPersistentManager&amp;lt;T, n&amp;gt;::GetErrorString()
{
    return p_m_tManager-&amp;gt;GetErrorString();
}
&lt;/PRE&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;这个class至关重要, 它确保了TPersistentManager template只有一个TManager 指针。它同时重定向所有的操作到那个唯一的TManager 指针。 注意，这个重定向是必须的，否则你就不能派生自己的类并使用那些功能。&amp;nbsp;如果你派生自 TManager ，它只是为你的新类分配新的变量. 同时注意，它维护了一个引用计数，用来决定是否每个类实例都被释放了, 这时就可以释放管理类的全部内存了。.&lt;/P&gt;
&lt;H1&gt;如何将其应用到你的类型&lt;/H1&gt;
&lt;P&gt;一切都好, 单现在还有个问题: "我如何使用它t?" 答案很简单: 你只要像对待其他模板类那样生成你的实例就可以了。. 这里有个例子以便看起来清晰：&lt;/P&gt;
&lt;BLOCKQUOTE&gt;&lt;PRE class=code&gt;class CImageSystem : public TPersistentManager&amp;lt;CImage, 128&amp;gt;
{
public:
    CImageSystem();
    ~CImageSystem();

    . // Functions you want to make specific for the CImageSystem class
    .
    .
}
&lt;/PRE&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;现在你就可以在不同的文件中生成多个CImageSystem 实例但它们都使用同一个CImageSystem&amp;nbsp;类 (那是一个单件类).&lt;/P&gt;
&lt;H1&gt;评估&lt;/H1&gt;
&lt;P&gt;尽管代码有些凌乱(我无法否认) 但它使得你可以轻易创建各种管理类而无需重写代码. 尽管TPersistentManager中的那些函数调用看起来像是架空的(并且就是那样), 如果你有一个好的编译器它们就会被内联, 结果某些情况函数调用开销减少到接近0% 。&lt;/P&gt;
&lt;H1&gt;结论&lt;/H1&gt;
&lt;P&gt;我想我没法解释所有不能一眼看穿的内容。只有两个选择。用这种方法，或者其他的。 我不想说这是管理数据最好的方法, 但它的确是方法之一(而且我觉得是个不错的方法)。&lt;/P&gt;
&lt;P&gt;如果有人有建议，注解，或者评论,&amp;nbsp;请发Emal给我到 &lt;A href="mailto:e.j.folkertsma@student.utwente.nl"&gt;e.j.folkertsma@student.utwente.nl&lt;/A&gt;&lt;/P&gt;
&lt;H1&gt;源代码&lt;/H1&gt;
&lt;P&gt;这是&amp;nbsp;&lt;A href="http://www.gamedev.net/reference/programming/features/datamanagers/source.zip"&gt;源代码&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;注意这可能不是最好的代码. 我花了60分钟来写并且加了少许修饰(因为我无意把它写到最好)以增加可读性。&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;img src ="http://blog.vckbase.com/panic/aggbug/4425.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Panic</dc:creator><title>[翻译]良好设计的根基</title><link>http://blog.vckbase.com/panic/archive/2005/04/03/4347.html</link><pubDate>Sat, 02 Apr 2005 20:45:00 GMT</pubDate><guid>http://blog.vckbase.com/panic/archive/2005/04/03/4347.html</guid><wfw:comment>http://blog.vckbase.com/panic/comments/4347.html</wfw:comment><comments>http://blog.vckbase.com/panic/archive/2005/04/03/4347.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://blog.vckbase.com/panic/comments/commentRss/4347.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/panic/services/trackbacks/4347.html</trackback:ping><description>&lt;P&gt;良好设计的根基&lt;/P&gt;
&lt;P&gt;作者：TANSTAAFL&lt;BR&gt;译者： &lt;A title=Panic href="/panic/"&gt;&lt;A title=Panic HREF="/panic/"&gt;&lt;a title="Panic" href="http://blog.vckbase.com/panic/" &gt;Panic&lt;/a&gt;&lt;/A&gt;&lt;/A&gt; 2005年4月3日&lt;/P&gt;
&lt;P&gt;译者序：这是一篇介绍游戏开发中，如何作更好设计的文章，尽管针对性很强，但是对其他开发也是有益的参考。作者引经据典，用富有哲理的语言描述了设计的各个重要方面。对语言的把握能力不足使我无法完整的表达出作者的情感，希望感兴趣的读者对照原文作进一步理解。&lt;BR&gt;原文链接：&lt;A href="http://www.gamedev.net/reference/articles/article864.asp"&gt;http://www.gamedev.net/reference/articles/article864.asp&lt;/A&gt;&lt;BR&gt;以下是翻译全文：&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&amp;#8220;做(开发)游戏若烹小鲜&amp;#8221;&lt;BR&gt;(这句话出自《道德经》,针对游戏开发做了修改) (译者注：《道德经》原文是：治大国若烹小鲜)&lt;/P&gt;
&lt;P&gt;这句话的含义并不浅显。对我来说，它意味着差的游戏设计是很容易的，就像把鱼烤的很难吃一样简单。&lt;/P&gt;
&lt;P&gt;良好设计的根基（游戏或其他）&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; *健壮性（又译：鲁棒性）－健壮性是任何程序最重要的方面。它意味着无论用户如何操作，程序都不会产生致命的运行错误。这看起来可能是个艰巨的任务，几乎无法达成。事实上，不可能开发出完全没有漏洞的程序。无论你的容错机制如何精巧，总难免有遗漏的地方，而且一些用户因为太聪明或者太愚蠢，以至于作出一些让你的程序产生致命运行错误的事情。（译者注：这句话很经典，可惜翻译的不好。原文是：No matter how smart/clever you are in implementing error handling, there is always something that you missed, and some user out there is clever enough or stupid enough to do something with your program that will cause a fatal run-time error.）&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; *可维护性－这是第二个重要的方面。如果你的代码可读性好，那么就容易维护，而且出了错容易改。如果你的代码看起来像一团麻（译者注：原文：spaghetti，意大利式面条），那你将为了修改和扩展程序费尽周折。尽可能简化你的工作，写你能写出的最易读的代码。&lt;BR&gt;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; *功能性－第三个重要的方面，功能性正如它字面的意思。程序必须有功能。也就是说，它必须实现它所承诺的功能。&lt;BR&gt;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; *优雅－优雅和功能性之间的界限很模糊。优雅是操作直观，界面简洁。复杂的用户界面很容易让用户丧失信心。幸运的是，数年来很多标准被开发出来，这使得你的游戏更易操作。&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; *速度－信不信由你，速度是程序最不引人注意的方面。如果一个程序无法运作，出错，失效，不借助上帝或者国会的力量就无法修补，或者很难用，那么它跑得再快也没意义。如果程序可以正常运作，一定的延迟是可以容忍的。然而，如果你的游戏是实时进行的，那你就不能牺牲速度。在关卡之间你可以花些时间，当你载入什么的时候，仅此而已。&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; *键盘习惯－移动，我强烈建议使用光标键，或者小键盘（如果可能在对角线方向运动的话）。大多数2D游戏中，向上箭头应该总是表示向屏幕上方移动。（我也见过一些不这样做的例子，形式不好，难以使用，不够优雅）。3D或者2D的驾驶类游戏，向上箭头总是表示前进。如果你用小键盘，num lock开启或关闭应该不造成影响。用户想玩游戏，立刻就玩，不愿意在开始之前还要记得按Num Lock键。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在很多种游戏中，可能会有一大堆玩家可以快速切换的武器。可获取的武器不应超过10种（超过的话就太多了）并且其中一些还要使用相同的弹药。切换武器的键可以是键盘上方的数字键1～0。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 如果游戏中需要开门，应该用空格键。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 开火或者攻击，应该用control键或者空格。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; F1应该用来开启联机帮助，通常是操作说明。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 有自动地图的游戏，TAB键用来开启，或者把它切换到屏幕显示。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Shift键用来跑动，Caps Lock用来慢速移动，alt键用来扫射（译者注：原文strafing）&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Escape键退回到游戏主菜单。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; F2到F12可以用来实现你指定的功能。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 在一个FPS（第一人称射击）游戏中，PageUp和PageDown应该允许你向上或者向下看（如果游戏支持这类动作。同时，应该有一个键让你返回原来的视角）。可能也有跳(X)，蹲下(C)，可能还有备用的开火键(Z)。同时，如果你有超过5种武器，你可能需要一个键切换回你上次的武器（一般是backspace键)。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后，当你把这些按键定义为游戏中不同行为的默认键之后，你应该制作一个用户按键配置选项，配置可以被保存，读取，以及回复到默认状态。&lt;BR&gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/panic/aggbug/4347.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Panic</dc:creator><title>[翻译]模块化编程：一种不使用类的方法第一部分</title><link>http://blog.vckbase.com/panic/archive/2005/03/30/4235.html</link><pubDate>Wed, 30 Mar 2005 13:32:00 GMT</pubDate><guid>http://blog.vckbase.com/panic/archive/2005/03/30/4235.html</guid><wfw:comment>http://blog.vckbase.com/panic/comments/4235.html</wfw:comment><comments>http://blog.vckbase.com/panic/archive/2005/03/30/4235.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://blog.vckbase.com/panic/comments/commentRss/4235.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/panic/services/trackbacks/4235.html</trackback:ping><description>&amp;nbsp; 
&lt;CENTER&gt;模块化编程：一种不使用类的方法第一部分&lt;BR&gt;&lt;SPAN class=author&gt;作者：&amp;nbsp;&lt;A href="mailto:godofarson@aol.com"&gt;Dan Arson&lt;/A&gt;&lt;BR&gt;译者：&lt;A href="http://blog.vckbase.com/panic/"&gt;&lt;a title="Panic" HREF="/panic/" &gt;panic&lt;/a&gt;&lt;/A&gt;&lt;/SPAN&gt;&lt;/CENTER&gt;
&lt;H1&gt;&lt;FONT face=宋体 color=#0000ff size=2&gt;译者序：这是一篇特殊的文章，它提供一种不使用类的模块化编程方法，对大部分开发者来说，也许并不值得关注，个人觉得，这篇文章适合那些已经习惯了class的存在，但却被迫在无class的平台（比如纯C）工作的程序员。&lt;/FONT&gt;&lt;/H1&gt;
&lt;P&gt;原文链接：&lt;A href="http://www.gamedev.net/reference/articles/article1066.asp"&gt;http://www.gamedev.net/reference/articles/article1066.asp&lt;/A&gt;&lt;/P&gt;
&lt;H1&gt;这是写给谁的?&lt;/H1&gt;
&lt;P&gt;这个系列是为那些认为C++的类是得不偿失的人写的。我打算为类的某些特性提供可以选择的传统一些的实现。这不是一篇讨论稿，所以我不解释为什么不使用类，我假设你已经阅读了很多说明类的缺陷的材料并且决定尽可能的避免使用它。这个系列文章的格式可能会和这一篇很像；首先提出一个我们可模拟的类的特性，然后提出实现的方法，紧接着是使用这种方法的限制条件。&lt;/P&gt;
&lt;H1&gt;对象实例&lt;/H1&gt;
&lt;P&gt;我曾经了解到基于类的编程对比传统的模块化编程的优点之一是允许存在对象的多个完全独立的对象而后者无法支持。这是这个系列的第一篇文章要推翻的话题。首先，我们看一下一个简单的class:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;&lt;PRE&gt;&lt;DIV class=code&gt;
// let's say this is in c_example.h
class C_Example
{
public:&lt;P&gt;&lt;/P&gt;
   void Displayxy( void );
   void Setx( int xValue );
   void Sety( int yValue );

private:
   int x;
   int y;
};

// and this is in c_example.cpp
#include "c_example.h"

void C_Example::Displayxy( void )
{
   cout &amp;lt;&amp;lt; "x == " &amp;lt;&amp;lt; x &amp;lt;&amp;lt; '\n' &amp;lt;&amp;lt; "y == " &amp;lt;&amp;lt; y &amp;lt;&amp;lt; '\n';
}

void C_Example::Setx( int xValue )
{
   x = xValue;
}

void C_Example::Sety( int yValue )
{
   y = yValue;
}

&lt;/DIV&gt;&lt;/PRE&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;现在, 声明和使用多个实例，看起来类似这样:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;&lt;PRE&gt;&lt;DIV class=code&gt;
#include "c_example.h"

void main( void )
{
   // create two instances of C_Example
   C_Example inst1;
   C_Example inst2;

   // set x and y for the first instance
   inst1.Setx( 5 );
   inst1.Sety( 4 );

   // set x and y for the second instance
   inst2.Setx( 8 );
   inst2.Sety( 9 );

   // display contents of each
   inst1.Displayxy();
   inst2.Displayxy();
}

&lt;/DIV&gt;&lt;/PRE&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;我想指出的是，这种方法极端愚蠢和无效, 我举这个例子以确保可以专心讨论我们的方法. 现在，看标准的模块版本:&lt;/P&gt;
&lt;BLOCKQUOTE&gt;&lt;PRE&gt;&lt;DIV class=code&gt;
// this might be in m_example.h
void Example_Displayxy( void );
void Example_Setx( int xValue );
void Example_Sety( int yValue );

// and this might be in m_example.cpp
#include "m_example.h"

static int _x;
static int _y;

void Example_Displayxy( void )&lt;P&gt;&lt;/P&gt;
{
   cout &amp;lt;&amp;lt; "x == " &amp;lt;&amp;lt; _x &amp;lt;&amp;lt; '\n' &amp;lt;&amp;lt; "y == " &amp;lt;&amp;lt; _y &amp;lt;&amp;lt; '\n';
}

void Example_Setx( int xValue )
{
   _x = xValue;
}

void Example_Sety( int yValue )
{
   _y = yValue;
}

// now to use the module:
#include "m_example.h"

void main( void )
{
   // you cannot declare multiple instances with this approach.
   // there is only one instance, which is the content of "m_example.cpp".
   Example_Setx( 5 );
   Example_Sety( 4 );

   Example_Displayxy();
}

&lt;/DIV&gt;&lt;/PRE&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;现在有意思的地方到了。上面的例子明显的揭示了大部分人使用类作为唯一的支持多实例对象方法的原因。我要用的不同的方法使用数组容纳成员变量。&lt;/P&gt;
&lt;BLOCKQUOTE&gt;&lt;PRE&gt;&lt;DIV class=code&gt;
// the new "m_example.h"

// the m_example data type
typedef unsigned int M_Example;

// module interface functions
M_Example Example_CreateInstance( void );
void Example_Displayxy( M_Example objInst );
void Example_Setx( M_Example objInst, int xValue );
void Example_Sety( M_Example objInst, int yValue );

// the new "m_example.cpp"
#include "m_example.h"

#define _MAXOBJECTS 128
struct _PRIVATE
{
   int x;
   int y;
}static _private[ _MAXOBJECTS];

M_Example _curInstance = 0;

M_Example Example_CreateInstance( void )
{
   _curInstance&lt;FONT face="Times,Times New Roman"&gt;++;
   return( _curInstance - 1 );
}

void Example_Displayxy( M_Example objInst )
{
   cout &amp;lt;&amp;lt; "x == " &amp;lt;&amp;lt; _private[objInst].x &amp;lt;&amp;lt; '\n' &amp;lt;&amp;lt; "y == " &amp;lt;&amp;lt; _private[objInst].y &amp;lt;&amp;lt; '\n';
}

void Example_Setx( M_Example objInst, int xValue )
{
   _private[objInst].x = xValue;
}

void Example_Sety( M_Example objInst, int yValue )
{
   _private[objInst].y = yValue;
}

// and to use the new module:
#include "m_example.h"

void main( void )
{
   // with the new module you can create more than one instance:
   // create two instances of M_Example
   M_Example inst1 = Example_CreateInstance();
   M_Example inst2 = Example_CreateInstance();

   // set x and y for the first instance
   Example_Setx( inst1, 5 );
   Example_Sety( inst1, 4 );

   // then set x and y for the second instance
   Example_Setx( inst2, 8 );
   Example_Sety( inst2, 9 );

   // display the contents of both
   Example_Displayxy( inst1 );
   Example_Displayxy( inst2 );
}

&lt;/FONT&gt;&lt;/DIV&gt;&lt;/PRE&gt;&lt;/BLOCKQUOTE&gt;
&lt;H1&gt;&lt;FONT face="Times,Times New Roman"&gt;结论: 好的和不好的&lt;/FONT&gt;&lt;/H1&gt;
&lt;P&gt;&lt;FONT face="Times,Times New Roman"&gt;正如你所看到的，无论你怎么认为，一个模块的多个实例是可行的。用这种方法，一个具有_private资格的结构体数组取代了关键字private，这里它是真正私有的，不会像在类定义里那样出现在接口列表中。数据类型M_Example其实只是一个int作为_private数组的索引ID。当你用Example_CreateInstance创建一个对象的时候，你只是保存了数组的一个位置。我确信你立刻就明白了这个方法的两个缺点。首先，对象的数量被限制在_MAXOBJECTS。其次，你可以创建一个对象，但是显然不能销毁它。好，那就用链表好了。我只使用数组是为了让思路清晰，但是你要使用这种方法，唯一的选择是链表。好，这就是要讨论的。任何问题，注解，热情，或者新的文章想法可以直接发送到&lt;A href="mailto:godofarson@aol.com"&gt;godofarson@aol.com&lt;/A&gt;。希望这篇文章能激发你的思维，如果有请告诉我。&lt;BR&gt;.&lt;/FONT&gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/panic/aggbug/4235.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Panic</dc:creator><title>[翻译]程序风格的要素－C++风格指南</title><link>http://blog.vckbase.com/panic/archive/2005/03/30/4232.html</link><pubDate>Wed, 30 Mar 2005 12:41:00 GMT</pubDate><guid>http://blog.vckbase.com/panic/archive/2005/03/30/4232.html</guid><wfw:comment>http://blog.vckbase.com/panic/comments/4232.html</wfw:comment><comments>http://blog.vckbase.com/panic/archive/2005/03/30/4232.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://blog.vckbase.com/panic/comments/commentRss/4232.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/panic/services/trackbacks/4232.html</trackback:ping><description>&amp;nbsp; 
&lt;TABLE cellSpacing=0 cellPadding=3 width="100%" border=0&gt;
&lt;TBODY&gt;
&lt;TR&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;TABLE cellSpacing=0 cellPadding=5 width="100%" border=0&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;STRONG&gt;&lt;FONT color=#003e98 size=4&gt;程序风格的要素－C++风格指南&lt;/FONT&gt;&lt;/STRONG&gt;&lt;BR&gt;&lt;/FONT&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;B&gt;&lt;FONT color=#0000ff&gt;作者：&lt;/FONT&gt;&lt;A href="mailto:nkipp@cs.vt.edu"&gt;&lt;FONT color=#0000ff&gt;Neill Kipp&lt;/FONT&gt;&lt;/A&gt;&lt;/B&gt; &lt;BR&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#00983e size=3&gt;&lt;FONT color=#0000ff size=2&gt;&lt;STRONG&gt;译者：&lt;A href="http://blog.vckbase.com/panic/"&gt;&lt;a title="Panic" HREF="/panic/" &gt;panic&lt;/a&gt;&lt;/A&gt; 2005年3月30日&lt;BR&gt;&lt;/STRONG&gt;译者序：这是一篇写于1996年1月23日的文章，到现在已经有9个年头了，很陈旧，有可能跟不上形势，但是有些东西仍然值得现在的开发者学习，我翻译这篇文字仅供读者参考。&lt;BR&gt;原文链接：&lt;A href="http://www.gamedev.net/reference/articles/article708.asp"&gt;http://www.gamedev.net/reference/articles/article708.asp&lt;/A&gt;&lt;BR&gt;&lt;/FONT&gt;&lt;BR&gt;&lt;STRONG&gt;文件&lt;/STRONG&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;头文件有".h"后缀。头文件包含类(class)，结构(struct)，和联合(union)的声明，枚举(enum)的声明，#define，typedef。&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;实现文件有一个".cc" (UNIX) 或者".cpp" (Windows, DOS)后缀。实现文件包括函数和方法的实现。&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;在头文件和源代码文件中安排一个页眉。页眉可以包含标题，作者，日期，和一些工程的信息，比如这个文件是配合整个工程的。&lt;BR&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#00983e size=3&gt;&lt;B&gt;一些名字&lt;/B&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;B&gt;通用C++字符的名字：&lt;BR&gt;（注：这些都是符号的英文原名，目前并没有完全标准化的汉语词汇对应，所以后面的翻译只是个人建议）&lt;/B&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;TABLE width="80%" align=center border=1&gt;
&lt;TBODY&gt;
&lt;TR&gt;
&lt;TD width=10&gt;&lt;FONT face=verdana size=2&gt;{&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD width=300&gt;&lt;FONT face=verdana size=2&gt;open brace, open curly&amp;nbsp;左花括号&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;}&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;close brace, close curly 右花括号&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;(&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;open parenthesis, open paren 左圆括号&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;)&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;close parenthesis, close paren 右圆括号&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;[&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;open bracket 左方括号&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;]&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;close bracket 右方括号&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;.&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;period, dot 句号，点&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;!&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;exclamation point, bang, not 叹号，否&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;|&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;bar, vertical-bar, or, or-bar (actually a "vertical virgule") 竖线，按位或&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;&amp;amp;&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;ampersand, and, reference, ref 和，按位与，引用，取地址&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;*&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;asterisk, multiply, star, pointer 星号，乘号，星，指针&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;/&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;slash, divide 斜线，除号&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;//&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;slash-slash, comment 双斜线，注释符&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;#&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;pound 井号 （宏：#，参考&lt;!--StartFragment --&gt;&lt;FONT size=3&gt; &lt;/FONT&gt;&lt;A id=_f2931c9ffacb075f_HomePageDays_DaysList__ctl1_DayItem_DayList__ctl2_TitleUrl HREF="/panic/archive/2005/03/27/4096.html"&gt;把符号转化为字符串的宏技巧&lt;/A&gt;&amp;nbsp;）&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;\&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;backslash, (sometimes "escape") 反斜线，（有时候做转义符）（还有一个：续行符）&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;
&lt;TR&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;~&lt;/FONT&gt;&lt;/TD&gt;
&lt;TD&gt;&lt;FONT face=verdana size=2&gt;tilde 按位取反&lt;/FONT&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;基本类型&amp;nbsp;"char" 通常发音是"charcoal."的首音节。有时念作 "care"&amp;nbsp;或者 "car." &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#00983e size=3&gt;&lt;B&gt;名字和排版&lt;/B&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#800040 size=2&gt;&lt;B&gt;命名约定的名字&lt;/B&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;interspersed_underscores lowercaseMixedCapital&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CapitalMixedCapital&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ALL_UPPERCASE &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 中间下划线&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 小写混合（首字母）大写&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;（首字母）大写混合（首字母）大写&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 全部大写&lt;BR&gt;&lt;/FONT&gt;&lt;/UL&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#800040 size=2&gt;&lt;B&gt;命名约定的应用&lt;/B&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;
&lt;LI&gt;enumeration_item_name 枚举，小写加下划线&lt;/LI&gt;
&lt;LI&gt;variableName 变量，小写前缀加首字母大写后缀&lt;/LI&gt;
&lt;LI&gt;TypeName, ClassName, MethodName() 类型名，类名，方法名，首字母大写前后缀。&lt;/LI&gt;
&lt;LI&gt;UnixFileName.cc&amp;nbsp; Unix/Linux文件名：每个单词首字母大写&lt;/LI&gt;
&lt;LI&gt;dosfn.cpp&amp;nbsp; windows/dos文件名：全部小写&lt;/LI&gt;
&lt;LI&gt;POUND_DEFINES 宏定义，全部大写。&lt;/LI&gt;&lt;/FONT&gt;&lt;/UL&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#800040 size=2&gt;&lt;B&gt;自成档代码（也就是没有文档，仅靠注释和代码说明的源代码文件）&lt;/B&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;
&lt;LI&gt;程序中为每个名字使用完整拼写.&amp;nbsp;&amp;nbsp;&lt;/LI&gt;&lt;/FONT&gt;&lt;/UL&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#800040 size=2&gt;&lt;B&gt;避免直接使用数字(Magic number)&lt;/B&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;
&lt;LI&gt;不允许出现除了0（有时也包括1）之外的数字常量. 使用常变量或者宏定义（#defines）. &lt;/LI&gt;&lt;/FONT&gt;&lt;/UL&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#800040 size=2&gt;&lt;B&gt;空白&lt;/B&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;
&lt;LI&gt;空格(按空格键得到) &lt;/LI&gt;
&lt;LI&gt;新行(按回车键得到) &lt;/LI&gt;
&lt;LI&gt;制表符(tab)&amp;nbsp;(用8个空格代替) &lt;/LI&gt;&lt;/FONT&gt;&lt;/UL&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#800040 size=2&gt;&lt;B&gt;空白和排版&lt;/B&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;
&lt;LI&gt;左花括号之后, 每行缩进4个空格直到对应的右花括号出现. &lt;/LI&gt;
&lt;LI&gt;如果if, while,&amp;nbsp;或 for 后面没有跟花括号, 下一行缩进两个空格. &lt;/LI&gt;
&lt;LI&gt;冒号结尾的语句，反向缩进两个空格(public, case). &lt;/LI&gt;
&lt;LI&gt;保留字(if, else, class, struct) 前后要加1个空格除非已经因为新行或者特殊标点做了缩进. &lt;/LI&gt;
&lt;LI&gt;运算符和比较符前后要有一个空格&amp;nbsp;(除了!之外). &lt;/LI&gt;
&lt;LI&gt;指针变量&amp;nbsp;(&amp;amp;,*) 声明的时候要前后加一个空格. &lt;/LI&gt;
&lt;LI&gt;指针变量&amp;nbsp;(&amp;amp;,*) 在表达式中，前面（不是后面）要加一个空格llowed) . &lt;/LI&gt;
&lt;LI&gt;左圆括号后要加一个空格. &lt;/LI&gt;&lt;/FONT&gt;&lt;/UL&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;/FONT&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#800040 size=2&gt;&lt;B&gt;换行&lt;/B&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;
&lt;LI&gt;在下面这些关键字后的左花括号后要换行: class, struct, union, enum, method, function (而不是: if, else, do, for, while, switch --- 这些的花括号后只要1个空格.) &lt;/LI&gt;
&lt;LI&gt;方法(method),函数( function), if, else, do, for, while, switch的右花括号后要换行. &lt;/LI&gt;
&lt;LI&gt;class, struct, union的右花括号后要换行并插入新空行。.（原文有写Semi-colon,不理解含义）&lt;/LI&gt;
&lt;LI&gt;左花括号后要换行. &lt;/LI&gt;&lt;/FONT&gt;&lt;/UL&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#800040 size=2&gt;&lt;B&gt;注释&lt;/B&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;
&lt;LI&gt;注释总是从当前缩进开始&amp;nbsp;"//"&amp;nbsp;然后紧接一个空格. &lt;/LI&gt;
&lt;LI&gt;注释中不允许其他注释. &lt;/LI&gt;
&lt;LI&gt;注释要加在注释的对象之后. （译者注：原文&lt;!--StartFragment --&gt; &lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;Comments always preceed the construct they address&lt;/FONT&gt; ）&lt;/LI&gt;
&lt;LI&gt;注释中使用完整语句. &lt;/LI&gt;
&lt;LI&gt;用于声明的时候，注释可以使用祈使句. &lt;/LI&gt;&lt;/FONT&gt;&lt;/UL&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;上面这些，是你的代码看起来舒服的指南，也是你的代码更具可读性的指南. &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#00983e size=3&gt;&lt;B&gt;头文件示例&lt;/B&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;BLOCKQUOTE&gt;&lt;PRE&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT face="Courier New, fixedsys" color=#000088 size=2&gt;
//  MODULE NAME: ClassName.h
//      PROJECT: CS1344-1,2 Course Notes
//       AUTHOR: Neill Kipp
//         DATE: January 1, 1996
//  DESCRIPTION: This file presents examples of naming and 
//  indentation style in a C++ class declaration.  This title
//  information is minimal.

// The following prevents files from being included
// twice.  It is a naming exception designed to emulate a file name
// (period is not a name character; underscore is).
#ifndef ClassName_h
#define ClassName_h

// This directive includes the superclass declaration.
#include "super.h"

// This directive includes another class declaration.
#include "other.h"

// The comment for an enumeration declaration precedes the declaration.
enum OverflowState
{
    // Each item's comment precedes it at the same indentation as the item.
    no_overflow,
    
    // Follow the last item with a comma;
    // it helps avoid syntax errors when adding or rearranging items.
    overflow_occurred,
};

// This class shows how naming conventions and comments are used in a
// simple class declaration.  Whitespace precedes and follows reserved
// words (like "public").

class ClassName
{ 
    // After a brace, indent four spaces.
    // The description of the variable "memberData" goes here.
    int memberData;

    // If a line ends in single colon, reverse-indent two spaces.
  public:

    // The constructor gives initial values to member data.
    ClassName();

    // The destructor guarantees clean deallocation.
    // The tilde (~) is part of the method name.  It is not an operator.
    ~ClassName();

    // This method increments the member variable by the value in "howMuch" 
    // and returns TRUE if overflow is detected (FALSE otherwise).  Method 
    // comments tell what the method does, what the arguments are,
    // and what the method returns.
    OverflowState IncrementMemberVariable( int howMuch);
    
    // Prints message about overflow.
    void ShowOverflow( OverflowState overflow);
};


#endif

&lt;/FONT&gt;&lt;/FONT&gt;&lt;/PRE&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#00983e size=3&gt;&lt;B&gt;源代码文件示例&lt;/B&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;BLOCKQUOTE&gt;&lt;PRE&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT face="Courier New, fixedsys" color=#000088 size=2&gt;
//  MODULE NAME: ClassName.cc
//      PROJECT: CS1344-1,2 Course Notes
//       AUTHOR: Neill Kipp
//         DATE: January 1, 1996
//  DESCRIPTION: This file presents examples of naming and 
//  indentation style in a C++ class implementation.  This title
//  information is minimal.

// This directive includes header information for the "ClassName" class.
#include "ClassName.h"

ClassName::
ClassName()
{
    // Initialize member data (statement comments are in the imperative,
    // and preceed the statement).  Suggestion: write the comments first, then
    // write the code.
    memberData = 0;
}

// The return type appears on the first line,
// followed by the class name colon-colon on the second,
// and finally the method name on the last.  Then a newline, an open brace
// and then indent.  Notice the space after the open parenthesis.  It helps
// the eye catch the type name.
OverflowState
ClassName::
IncrementMemberVariable( int howMuch)
{
    // Check the overflow condition.
    if ( TOO_BIG - memberVariable &amp;gt; howMuch) {
 // If overflow, return that overflow occurred.
 return overflow_occurred;
    } else {
 // Otherwise, return overflow is ok.
 return overflow_none;
    }
}

// This code implements the ShowOverflow method.
void
ClassName::
ShowOverflow( OverflowState overflow)
{
    // Switch is a reserved word.  It is followed by a space.
    switch ( overflow) {

 // Lines ending in a colon reverse indent two spaces.
      case no_overflow:
 // Display message about no overflow.
 cout &amp;lt;&amp;lt; "No overflow occurred.\n";
 break;

      case overflow_occurred:
 // Display message that overflow occurred.
 cout &amp;lt;&amp;lt; "Warning: overflow occurred.\n";
 break;
    }
}

&lt;/FONT&gt;&lt;/FONT&gt;&lt;/PRE&gt;&lt;/BLOCKQUOTE&gt;
&lt;P&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT color=#00983e size=3&gt;&lt;B&gt;其他例子&lt;/B&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/P&gt;
&lt;BLOCKQUOTE&gt;&lt;PRE&gt;&lt;FONT face="Verdana, Tahoma, Arial" size=2&gt;&lt;FONT face="Courier New, fixedsys" color=#000088 size=2&gt;
// Note the spacing and indentation in the for statement.
for ( whichItem = 0; whichItem &amp;lt; BIG_NUMBER; whichItem++) {
    DoSomething( whichItem);
}

// Bang is not followed by a space.
while ( !SemaphoreOK()) {
    DoWaitForSemaphore( LONG_TIME);
}

&lt;/FONT&gt;&lt;/FONT&gt;&lt;/PRE&gt;&lt;/BLOCKQUOTE&gt;
&lt;P align=center&gt;&amp;nbsp;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;img src ="http://blog.vckbase.com/panic/aggbug/4232.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Panic</dc:creator><title>[翻译]在A*寻路中使用二叉堆</title><link>http://blog.vckbase.com/panic/archive/2005/03/28/4144.html</link><pubDate>Mon, 28 Mar 2005 09:47:00 GMT</pubDate><guid>http://blog.vckbase.com/panic/archive/2005/03/28/4144.html</guid><wfw:comment>http://blog.vckbase.com/panic/comments/4144.html</wfw:comment><comments>http://blog.vckbase.com/panic/archive/2005/03/28/4144.html#Feedback</comments><slash:comments>65</slash:comments><wfw:commentRss>http://blog.vckbase.com/panic/comments/commentRss/4144.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/panic/services/trackbacks/4144.html</trackback:ping><description>&lt;P&gt;在A*寻路中使用二叉堆&lt;/P&gt;
&lt;P&gt;作者：Patrick Lester（2003年4月11日更新）&lt;BR&gt;译者：Panic 2005年3月28日&lt;/P&gt;
&lt;P&gt;译者序：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这一篇文章，是&amp;#8220;A* Pathfinding for Beginners.&amp;#8221;，也就是我翻译的另一篇文章《&lt;A class=titlelink id=Editor_Results_rprSelectionList__ctl44_Hyperlink1 href="/panic/archive/2005/03/20/3778.html"&gt;A*寻路初探&lt;/A&gt; 》的补充，在这篇文章里，作者再一次展现了他阐述复杂话题的非凡能力，用通俗易懂的语句清晰的解释了容易让人迷惑的问题。还是那句话，如果你看了这篇文章仍然无法领会作者的意图，那只能怪我的翻译太蹩脚了。请参考原文做进一步的理解。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这里讲解的二叉堆，其实是以堆的形式存在的二叉树，这个特殊的结构把A*算法对开启列表的排序需求演绎的出神入化，毫无疑问是A*的最佳拍档。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 最后，希望这篇文章对你有所帮助。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;BR&gt;原文链接：&lt;A href="http://www.policyalmanac.org/games/binaryHeaps.htm"&gt;http://www.policyalmanac.org/games/binaryHeaps.htm&lt;/A&gt;&lt;BR&gt;&lt;BR&gt;以下是翻译的正文：&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;这篇文章是我的主题文章&amp;#8220;A* Pathfinding for Beginners.&amp;#8221;的补充。在读这篇文章之前，你应该先读那一篇文章，或者对A*做透彻的理解。&lt;/P&gt;
&lt;P&gt;A*算法中最缓慢的部分就是在开启列表中寻找F值最低的节点或者方格。取决于地图的大小，你可能有十几，成百甚至上千的节点需要在某个时候使用A*搜索。无需多讲，反复搜索这么大的列表会严重拖慢整个过程。然而，这些时间在极大程度上受你存储列表的方式影响。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;有序和无序的开启列表：简单的方法&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;最简单的方法就是顺序存储每个节点，然后每次需要提取最低耗费元素的时候都遍历整个列表。这提供可快速的插入速度，但是移除速度可能是最慢的，因为你需要检查每个元素才能够确定哪个才是F值最低的。&lt;/P&gt;
&lt;P&gt;通常你可以保持你列表处于有序状态来提升效率。这花费了稍微多一点的预处理时间，因为你每次插入新元素都必须把他们放在恰当的位置。不过移除元素倒是很快。你只要移除第一个元素就可以了，它一定是F值最低的。&lt;/P&gt;
&lt;P&gt;有很多方法可以保持你的数据有序（选择排序，冒泡排序，快速排序，等等）并且你可以用你最熟悉的搜索引擎找到这方面的文章。不过我们至少可以先提几种想法。最简单的方法可能是，当你需要添加新元素的时候，从列表开始的地方，依次比较每个元素的F值和要插入的F值的大小。一旦找到一个相等或者更高的F值，你就可以把新元素插入到列表中那个元素的前面。取决于你使用的计算机于亚，使用class或者struct实现的链表可能是不错的方法。&lt;/P&gt;
&lt;P&gt;这种方法可以通过保持列表中所有元素的平均值来得到改进，使用这个平均值来决定是从头（如上所说）还是从尾开始处理。总的说来，比平均F值低的新元素将被从头开始处理，而比平均F值高的则从末尾开始。这种方法可以节省一半的时间。&lt;/P&gt;
&lt;P&gt;复杂一些，但是更快的方法是把这一想法提高到新的层次使用快速排序，它基本上是从比较新元素和列表中间元素的F值开始。如果新元素的F值低，你接着把它和1/4处元素进行比较，如果还是更低你就比较它和1/8处的元素，如此这般，不断的折半你的列表并且比较，直到找到合适的位置。这个描述很简单，你可能会想到网上寻找快速排序的更多资料。这比至此描述的任何方法都快。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;二叉堆&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;二叉堆和刚才说的快速排序很像，经常被那些苛求A*速度的人使用。根据我的经验，二叉堆平均提高寻路速度2-3倍，对于包含大量节点的地图(也就是说100&amp;#215;100节点或者更多）效果更明显。友情提醒，然而二叉堆很难处理，除非你使用含有大量节点的地图，速度至关重要，否则不值得为它头痛。&lt;/P&gt;
&lt;P&gt;文章其他的部分深入说明了二叉堆和它在A*算法中的用途。如果你对我的文章存有疑惑，在文章末尾进一步阅读的小节中提供了更多的观点。&lt;/P&gt;
&lt;P&gt;仍然有兴趣？好，我们继续。。。&lt;/P&gt;
&lt;P&gt;在有序列表中，每个元素都按照由低到高或由高到低的顺序保存在恰当的位置。这很有用，但是还不够。事实上，我们并不关心数字127是否比128在更低的位置上。我们只是想让F值最低的元素能放在列表顶端以便容易访问。列表的其他部分即使是混乱的也不必在意。列表的其他部分只有在我们需要另一个F值最低的元素的时候，才有必要保持有序。&lt;/P&gt;
&lt;P&gt;基本上，我们真正需要的是一个&amp;#8220;堆&amp;#8221;，确切的说，是个二叉堆。二叉堆是一组元素，其中最大或者最小（取决于需要）的元素在堆顶端。既然我们要寻找F值最小的元素，我们就把它放在堆顶端。这个元素有两个子节点，每个的F值等于，或者略高于这个元素。每个子节点又有两个子节点，他们又有和他们相等或略高的子节点。。。依次类推。这里是一个堆可能的样子：&lt;BR&gt;&lt;IMG height=57 src="/images/vckbase_com/panic/binary1.gif" width=116 border=0&gt;&lt;/P&gt;
&lt;P&gt;注意，F值最低的元素(10)在最顶端，第二低的元素(20)是它的一个子节点。可是，其后就没有任何疑问了。在这个特定的二叉堆里，第三低的元素是24，它离堆顶有两步的距离，它比30小，但是30却在左侧离堆顶一步之遥的地方。简单的堆放，其他的元素在堆的哪个位置并不重要，每个单独的元素只需要和它的父节点相等或者更高，而和它的两个子节点相比，更低或者相等，这就可以了。这些条件在这里都完全符合，所以这是个有效的二叉堆。&lt;/P&gt;
&lt;P&gt;很好，你可能会想，这的确有趣，但是如何把它付诸实施呢？嗯，关于二叉堆的一个有趣的事实是，你可以简单的把它存储在一个一维数组中。&lt;/P&gt;
&lt;P&gt;在这个数组中，堆顶端的元素应该是数组的第一个元素(是下标1而不是0)。两个子节点会在2和3的位置。这两个节点的4个子节点应该在4－7的位置。&lt;BR&gt;&lt;IMG height=108 src="/images/vckbase_com/panic/binary2.jpg" width=203 border=0&gt;&lt;/P&gt;
&lt;P&gt;总的来说，任何元素的两个子节点可以通过把当前元素的位置乘以2（得到第一个子节点）和乘2加1（得到第二个子节点）来得到。就这样，例如堆中第三个元素（数值是20）的两个子节点，可以在位置2*3 = 6和2*3 +1 = 7这两个位置找到。那两个位置上的数字非别是30和24，当你查看堆的时候就能理解。&lt;/P&gt;
&lt;P&gt;你其实不必要知道这些，除了表明堆中没有断层之外知道这些没有任何价值。7个元素，就完整的填满了一个三层堆的每一层。然而这并不是必要的。为了让我们的堆有效，我们只需要填充最底层之上的每一行。最底层自身可以是任意数值的元素，同时，新的元素按照从左到右的顺序添加。这篇文章描述的方法就是这样做的，所以你不必多虑。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;往堆中添加新元素&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;当我们实际在寻路算法中使用二叉堆的时候，还需要考虑更多，但是现在我们只是学习一下如何使用二叉堆。我跳过这部分以便更容易理解基本的东西。我会在文章后面的部分给出处理这一切的完整公式，但了解这些细节仍然十分重要。&lt;/P&gt;
&lt;P&gt;大致的，为了往堆里添加元素，我们把它放在数组的末尾。然后和它在 当前位置/2 处的父节点比较，分数部分被圆整。如果新元素的F值更低，我们就交换这两个元素。然后我们比较这个元素和它的新父节点，在 （当前位置）/2 ，小数部分圆整，的地方。如果它的F值更低，我们再次交换。我们重复这个过程直到这个元素不再比它的父节点低，或者这个元素已经到达顶端，处于数组的位置1。&lt;/P&gt;
&lt;P&gt;我们来看如何把一个F值为17的元素添加到已经存在的堆中。我们的堆里现在有7个元素，新元素将被添加到第8个位置。这就是堆看起来的样子，新元素被加了下划线。&lt;/P&gt;
&lt;P&gt;10 30 20 34 38 30 24 &lt;U&gt;17&lt;/U&gt;&lt;/P&gt;
&lt;P&gt;接下来我们比较它和它的父节点，在 8/2 也就是 4的位置上。位置4当前元素的F值是34。既然17比34低，我们交换两元素的位置。现在我们的堆看起来是这样的:&lt;/P&gt;
&lt;P&gt;10 30 20 &lt;U&gt;17&lt;/U&gt; 38 30 24 34&lt;/P&gt;
&lt;P&gt;然后我们把它和新的父节点比较。因为我们在位置4，我们就把它和 4/2 = 2 这个位置上的元素比较。那个元素的F值是30。因为17比30低，我们再次交换，现在堆看起来是这样的：&lt;/P&gt;
&lt;P&gt;10 &lt;U&gt;17&lt;/U&gt; 20 30 38 30 24 34&lt;/P&gt;
&lt;P&gt;接着我们比较它和新的父节点。现在我们在第二个位置，我们把它和 2/2 = 1，也就是堆顶端的比较。这次，17不比10更低，我们停止，堆保持成现在的样子。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;从堆中删除元素&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;从堆中删除元素是个类似的过程，但是差不多是反过来的。首先，我们删除位置1的元素，现在它空了。然后，我们取堆的最后一个元素，移动到位置1。在堆中，这是结束的条件。以前的末元素被加了下划线。&lt;/P&gt;
&lt;P&gt;&lt;U&gt;34&lt;/U&gt; 17 20 30 38 30 24&lt;/P&gt;
&lt;P&gt;然后我们比较它和两个子节点，它们分别在位置(当前位置*2)和(当前位置* 2 + 1)。如果它比两个子节点的F值都低，就保持原位。反之，就把它和较低的子节点交换。那么，在这里，该元素的两个子节点的位置在 1 * 2 = 2和 1*2 + 1 = 3。显然，34不比任何一个子节点低，所以我们把它和较低的子节点，也就是17，交换。结果看起来是这样：&lt;/P&gt;
&lt;P&gt;17 &lt;U&gt;34&lt;/U&gt; 20 30 38 30 24&lt;/P&gt;
&lt;P&gt;接着我们把它和新的子节点比较，它们在 2*2 = 4，和2*2 + 1 = 5的位置上。它不比任何一个子节点低，所以我们把它和较低的一个子节点交换（位置4上的30）。现在是这样：&lt;/P&gt;
&lt;P&gt;17 30 20 &lt;U&gt;34&lt;/U&gt; 38 30 24&lt;/P&gt;
&lt;P&gt;最后一次，我们比较它和新的子节点。照例，子节点在位置 4*2 = 8和4*2+1 = 9的位置上。但是那些位置上并没有元素，因为列表没那么长。我们已经到达了堆的底端，所以我们停下来。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;二叉堆为什么这么快？&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;现在你知道了堆基本的插入和删除方法，你应该明白为什么它比其他方法，比如说插入排序更快。假设你有个有1000个节点的开启列表，在一格有很多节点的相当大的地图上，这不是不可能（记住，即使是100&amp;#215;100的地图，上面也有10,000个节点）。如果你使用插入排序，从起点开始，到找到新元素恰当的位置，在把新元素插入之前，平均需要做500次比较。&lt;/P&gt;
&lt;P&gt;使用二叉堆，你从底端开始，可能只要1－3次比较就能把新元素插入到正确的位置。你还需要9次比较用来从开启列表中移除一个元素，同时保持堆仍然有序。在A*中，你通常每次只需要移除一个元素(F值最低的元素)，在任意位置添加0到5个新节点(就像主文章里描述的2D寻路)。这总共花费的时间大约是同样数量节点进行插入排序的1%。差别随你地图的增大(也就是节点更多)呈几何增长。地图越小，就越没优势，这也是为什么你的地图和节点越少，二叉堆的价值就越低的原因。&lt;/P&gt;
&lt;P&gt;顺便，使用二叉堆并不意味着你的寻路算法会快100倍。在下面还讲了一些棘手的问题。额外的，A*不仅仅是为开启列表排序。然而，根据我的经验，用二叉堆在大部分场合可以提高2－3倍的速度，更长的路径，速度提高的更多。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;创建开启列表数组&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;现在我们了解了二叉堆，那么如何使用它呢？首先要做的是构造我们的一维数组。为此，我们先要确定它的大小。一般来说，列表大小不会超过地图上的节点总数（在最坏的情况下，我们搜索整个地图寻找路径）。在一个方形二维地图中，就如我的主文章中描述的，我们的节点不超过 地图宽 &amp;#215; 地图高。那么我们的一维数组就是那个大小。在这个例子里，我们叫这个数组 openList()。堆最顶端的元素被存储在openList(1)，第二个元素在openList(2)，依此类推。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;使用指针&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;现在我们有了正确大小的数组，几乎可以开始用来寻路了。不过，在进一步的添加或者删除操作之前，我们再看看原始的堆。&lt;BR&gt;&lt;IMG height=108 src="/images/vckbase_com/panic/binary3.jpg" width=203 border=0&gt;&lt;/P&gt;
&lt;P&gt;现在，它只是个F值的列表，而且已经被正确安排。但是我们忽略了一个重要的元素。是的，我们有一系列的F值按顺序保存在堆里，但是我们没有他们代表哪一格的任何线索。基本上，我们只是知道10是堆中最低的F值。但那指的是那个格子？&lt;/P&gt;
&lt;P&gt;为了解决这个问题，我们必须改变数组中元素的值。我们不储存排序好的F值，取而代之的是保存关联到地图网格的唯一标志。我的方法是为每个新加入堆的元素创建一个唯一ID叫做squaresChecked。每次往开启列表中添加新元素，我们给squaresChecked增加1，并把它作为列表中新元素的唯一ID。第一个添加进列表的是#1，第二个是#2，依此类推。&lt;/P&gt;
&lt;P&gt;最后，我们把具体的F值存储在单独的一维数组中，我把它叫做 Dcost()。和开启列表相同，我们把它的大小定为(地图宽 x 地图高)。我们同时存储节点的x和y坐标在类似的一维数组中，叫做 openX() 和 openY()。看起来就像下面的图：&lt;BR&gt;&lt;IMG height=182 src="/images/vckbase_com/panic/binary4.jpg" width=308 border=0&gt;&lt;/P&gt;
&lt;P&gt;尽管这看起来有点复杂，但它和前面讲的堆是相同的。只是储存的信息更多了。&lt;/P&gt;
&lt;P&gt;#5元素，有最低的F值10，仍然在堆顶，在一维数组的第一列。不过现在我们在堆里存储它的唯一ID 5，而不是它的F值。换句话说，openList(1) = 5。这个唯一数值用来查找元素的F值，以及地图x和y坐标。这个元素的F值是Fcost(5) = 10，x坐标是openX(5) = 12，y坐标是openY(5) = 22。&lt;/P&gt;
&lt;P&gt;顶端的元素有两个子节点，数值是2和6，他们的F值是30和20，分别存储在opneList()中2和3的位置，等等。基本上，我们的堆和以前相同，只是多了一些关于元素在地图上的位置，F值是多少，等等的信息。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;在堆中添加新元素(第二部分）&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;好，我们实际的把这种技术用在A*寻路的开启列表排序中。我们使用的技术和先前描述的大体相同。&lt;/P&gt;
&lt;P&gt;我们添加到开启列表中的第一个元素，一般是起始节点，被分配了一个数值是1的唯一ID，然后放入开启列表的#1位置。也就是 openList(1) = 1.我们还跟踪开启列表中元素的数量，现在也是1。我们把它保存在名为numberOfOpenListItems的变量里。&lt;/P&gt;
&lt;P&gt;当我们往开启列表中添加新元素的时候，首先我们计算G，H和F值，就如在主文章中所述。然后按照前面讲的方法把他们添加进开启列表。&lt;/P&gt;
&lt;P&gt;首先我们给新元素分配一格唯一 ID号，也就是squaresChecked变量的用途。每次我们添加一个新节点，就给这个变量加1，然后把它的数值分配给新节点。然后给numberOfOpenListItems加1。然后把它添加到开启列表的底部，所有这些可以翻译成：&lt;/P&gt;
&lt;P&gt;&amp;nbsp; squaresChecked = squaresChecked +1&lt;BR&gt;&amp;nbsp; numberOfOpenListItems = numberOfOpenListItems+1&lt;BR&gt;&amp;nbsp; openList(numberOfOpenListItems) = squaresChecked&lt;/P&gt;
&lt;P&gt;然后我们依次把它和父节点比较直到它到达正确的位置。这是这些操作的代码：&lt;/P&gt;
&lt;P&gt;&amp;nbsp; m = numberOfOpenListItems&lt;BR&gt;&amp;nbsp; While m &amp;lt;&amp;gt; 1 ;While item hasn't bubbled to the top (m=1)&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ;Check if child is &amp;lt;= parent. If so, swap them.&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; If Fcost(openList(m)) &amp;lt;= Fcost(openList(m/2)) Then&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; temp = openList(m/2)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; openList(m/2) = openList(m)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; openList(m) = temp&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; m = m/2 &lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Exit ;exit the while/wend loop&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; End If&lt;BR&gt;&amp;nbsp; Wend&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;从堆中删除元素(第二部分)&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;无疑，我们不能只建立堆，当不需要的时候，我们也要从堆中删除元素。特别的，在A*寻路中，我们在检查和切换到关闭列表之后，从堆顶需要删除F值最低的元素。&lt;/P&gt;
&lt;P&gt;如前所述，你从把末元素移动到堆顶开始，然后把堆中的元素总数减1。伪代码是这样：&lt;/P&gt;
&lt;P&gt;&amp;nbsp; openList(1) = openList(numberOfOpenListItems)&lt;BR&gt;&amp;nbsp; numberOfOpenListItems = numberOfOpenListItems - 1&lt;/P&gt;
&lt;P&gt;接着我们需要依次比较它和两个子节点的数值。如果它的F值更高，我们就把它和更低F值的子节点交换。然后我们把它和新的子节点比较（看它是否更低）。如果它的F值比两个子节点更高，我们把它和较低的一个交换。我们重复这个过程直到找到它的正确位置，这可能会一直持续到堆底，但并不是完全必要。伪代码看起来是这样：&lt;/P&gt;
&lt;P&gt;v = 1&lt;/P&gt;
&lt;P&gt;;Repeat the following until the item sinks to its proper spot in the binary heap.&lt;BR&gt;Repeat&lt;BR&gt;&amp;nbsp; u = v&lt;BR&gt;&amp;nbsp; If 2*u+1 &amp;lt;= numberOfOpenListItems ;if both children exist&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ;Select the lowest of the two children.&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; If Fcost(openList(&lt;FONT color=#ff0000&gt;u&lt;/FONT&gt;)) &amp;gt;= Fcost(openList(2*u))then v = 2*&lt;FONT color=#ff0000&gt;u ;SEE NOTE BELOW&lt;/FONT&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; If Fcost(openList(&lt;FONT color=#ff0000&gt;v&lt;/FONT&gt;)) &amp;gt;= Fcost(openList(2*u+1))then v = 2*u+1 ;&lt;FONT color=#ff0000&gt;SEE NOTE BELOW&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp; Else If 2*u &amp;lt;= numberOfOpenListItems ;if only child #1 exists&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ;Check if the F cost is greater than the child&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; If Fcost(openList(u)) &amp;gt;= Fcost(openList(2*u))then v = 2*u&lt;BR&gt;&amp;nbsp; End If&lt;/P&gt;
&lt;P&gt;&amp;nbsp; If u &amp;lt;&amp;gt; v then ; If parent's F &amp;gt; one or both of its children, swap them&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; temp = openList(u)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; openList(u) = openList(v)&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; openList(v) = temp&lt;BR&gt;&amp;nbsp; Else&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Exit ;if item &amp;lt;= both children, exit repeat/forever loop&lt;BR&gt;&amp;nbsp; End if&lt;BR&gt;Forever ;Repeat forever&lt;/P&gt;
&lt;P&gt;请注意两行代码中粗体(红色)的u和v的数值。在第二行，你应该使用 v而不是u，这不是很显而易见。这确保了你把它和较低的子节点交换。如果做错会造成不完整的堆，完全打乱你的寻路。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;对开启列表的元素重排序&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;就如在主文章中描述的，有时候你会发现现有的开启列表中的元素会改变。这种情况发生的时候，我们不必要取出这个元素重新来过。只要从当前位置开始，用它新的（更低的）F值和它的父节点比较。如果它的F值低到足以替换它的父节点，你就把它替换掉（不然你就会得到一个错误的堆，一切都完了）。一般，你使用和&amp;#8220;在堆中添加新元素&amp;#8221;的小节中相同的代码，并做额外处理如下：&lt;/P&gt;
&lt;P&gt;不幸的是，因为你的数据是在一个庞大，无序的堆里，你需要遍历整个堆查找先有开启列表中的元素。你主要要查找由openX(openList()) 和openY(openList())获取的确切坐标的格子，找到之后，你就可以从那一点开始，像往常那样做必要的比较和交换。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;最后的注解&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;好了，我希望你仍然能读懂，没有被搞昏头。如果你不着急，并且希望在自己的寻路算法中使用二叉堆，那么这就是我的建议。&lt;/P&gt;
&lt;P&gt;首先，把二叉堆放一放。把注意力放在你的A*寻路算法上，使用简单点的排序算法，保证算法正常工作没有bug。一开始你并不需要它很快速，你只需要它能工作。&lt;/P&gt;
&lt;P&gt;其次，在你把二叉堆添加到代码中之前，试着把二叉堆写成独立的功能，用适当的方法添加和删除元素。确保你写的程序中，可以看到整个过程中每一步的操作，也许可以把结果打印在屏幕上，如果你愿意。你还得包含一些中途退出的代码，以便在必要的时候结束整个流程。如果二叉堆写的不对，你很容易就会陷入到无限循环中。&lt;/P&gt;
&lt;P&gt;一旦你确信两个程序都运行无误，备份他们，然后开始把他们结合起来。除非你比我还聪明，否则一开始你难免遇到麻烦。输出都是些错误信息 并且/或者 看到寻路者因为bug走出各种怪异的方向。不过最终你会搞定一切。&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;进一步阅读&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;和以往一样，网上有很多其他的关于这个话题的好文章。这里有些入门的。第一篇用图示。第二篇说明了怎么用一个简单的数组（如果我的说明很麻烦的话）实现二叉堆。第三篇探讨了寻路算法中堆的一般用途。&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * &lt;A href="http://www.onthenet.com.au/~grahamis/int2008/week11/lect11.html"&gt;http://www.onthenet.com.au/~grahamis/int2008/week11/lect11.html&lt;/A&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * &lt;A href="http://www.purists.org/pqueue/"&gt;http://www.purists.org/pqueue/&lt;/A&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * &lt;A href="http://theory.stanford.edu/~amitp/GameProgramming/ImplementationNotes.html#S5"&gt;http://theory.stanford.edu/~amitp/GameProgramming/ImplementationNotes.html#S5&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;最后，你可能想看看我的寻路代码，这里可以找到。它和文中的伪代码相互照应，注释详尽，有C++和Blitz两个版本，Blitz是一个比其他大多数都容易理解的语言。不使用C++的程序员会很容易理解Basic版本的代码。&lt;/P&gt;
&lt;P&gt;好，就这么多。欢迎对这个（公认）复杂的话题提出看法，可以这样联系我：&lt;BR&gt;&lt;IMG height=26 src="/images/vckbase_com/panic/mail2.jpg" width=211 border=0&gt;&lt;/P&gt;
&lt;P&gt;现在，照例，祝你好运。&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;译者参考文章：&lt;A id=_dbd54e7a9d345896_HomePageDays_DaysList__ctl5_DayItem_DayList__ctl0_TitleUrl href="/panic/archive/2005/03/20/3778.html"&gt;A*寻路初探 GameDev.net&lt;/A&gt;&lt;BR&gt;二叉堆的实现，参考：&lt;A id=_fae607e2a6be4404_HomePageDays_DaysList__ctl0_DayItem_DayList__ctl0_TitleUrl HREF="/panic/archive/2006/06/19/20869.html" dragover="true"&gt;二叉堆的模板代码－－续&amp;#8220;关于一道算法题《编写算法，从10亿个浮点数当中，选出其中最大的10000个》&amp;#8221;&lt;/A&gt;&lt;/P&gt;&lt;img src ="http://blog.vckbase.com/panic/aggbug/4144.html" width = "1" height = "1" /&gt;</description></item><item><dc:creator>Panic</dc:creator><title>[翻译]A*寻路初探 GameDev.net</title><link>http://blog.vckbase.com/panic/archive/2005/03/20/3778.html</link><pubDate>Sun, 20 Mar 2005 14:45:00 GMT</pubDate><guid>http://blog.vckbase.com/panic/archive/2005/03/20/3778.html</guid><wfw:comment>http://blog.vckbase.com/panic/comments/3778.html</wfw:comment><comments>http://blog.vckbase.com/panic/archive/2005/03/20/3778.html#Feedback</comments><slash:comments>50</slash:comments><wfw:commentRss>http://blog.vckbase.com/panic/comments/commentRss/3778.html</wfw:commentRss><trackback:ping>http://blog.vckbase.com/panic/services/trackbacks/3778.html</trackback:ping><description>&lt;P&gt;A*寻路初探 GameDev.net&lt;/P&gt;
&lt;P&gt;作者： Patrick Lester&lt;BR&gt;译者：&lt;A href="http://blog.vckbase.com/panic"&gt;&lt;A title=Panic href="/panic/"&gt;&lt;A title=Panic href="/panic/"&gt;&lt;A title=Panic href="/panic/"&gt;&lt;A title=Panic href="/panic/"&gt;&lt;A title=Panic href="/panic/"&gt;&lt;A title=Panic href="/panic/"&gt;&lt;A title=Panic href="/panic/"&gt;&lt;A title=Panic HREF="/panic/"&gt;&lt;a title="Panic" href="http://blog.vckbase.com/panic/" &gt;Panic&lt;/a&gt;&lt;/A&gt;&lt;/A&gt;&lt;/A&gt;&lt;/A&gt;&lt;/A&gt;&lt;/A&gt;&lt;/A&gt;&lt;/A&gt;&lt;/A&gt; 2005年3月18日&lt;/P&gt;
&lt;P&gt;译者序：很久以前就知道了A*算法，但是从未认真读过相关的文章，也没有看过代码，只是脑子里有个模糊的概念。这次决定从头开始，研究一下这个被人推崇备至的简单方法，作为学习人工智能的开始。&lt;BR&gt;这篇文章非常知名，国内应该有不少人翻译过它，我没有查找，觉得翻译本身也是对自身英文水平的锻炼。经过努力，终于完成了文档，也明白的A*算法的原理。毫无疑问，作者用形象的描述，简洁诙谐的语言由浅入深的讲述了这一神奇的算法，相信每个读过的人都会对此有所认识（如果没有，那就是偶的翻译太差了--b）。&lt;BR&gt;现在是2005年7月19日的版本，应原作者要求，对文中的某些算法细节做了修改。&lt;BR&gt;原文链接：&lt;A href="http://www.gamedev.net/reference/articles/article2003.asp"&gt;http://www.gamedev.net/reference/articles/article2003.asp&lt;/A&gt;&lt;BR&gt;原作者文章链接：&lt;A href="http://www.policyalmanac.org/games/aStarTutorial.htm"&gt;http://www.policyalmanac.org/games/aStarTutorial.htm&lt;/A&gt;&lt;BR&gt;以下是翻译的正文。&lt;/P&gt;
&lt;P&gt;会者不难，A*(念作A星)算法对初学者来说的确有些难度。&lt;/P&gt;
&lt;P&gt;这篇文章并不试图对这个话题作权威的陈述。取而代之的是，它只是描述算法的原理，使你可以在进一步的阅读中理解其他相关的资料。&lt;/P&gt;
&lt;P&gt;最后，这篇文章没有程序细节。你尽可以用任意的计算机程序语言实现它。如你所愿，我在文章的末尾包含了一个指向例子程序的链接。 压缩包包括C++和Blitz Basic两个语言的版本，如果你只是想看看它的运行效果，里面还包含了可执行文件。&lt;/P&gt;
&lt;P&gt;我们正在提高自己。让我们从头开始。。。&lt;/P&gt;
&lt;P&gt;序：搜索区域&lt;/P&gt;
&lt;P&gt;假设有人想从A点移动到一墙之隔的B点，如下图，绿色的是起点A，红色是终点B，蓝色方块是中间的墙。&lt;/P&gt;
&lt;P&gt;&lt;IMG height=256 src="/images/vckbase_com/panic/image001.jpg" width=362 border=0&gt;&lt;BR&gt;[图1]&lt;/P&gt;
&lt;P&gt;你首先注意到，搜索区域被我们划分成了方形网格。像这样，简化搜索区域，是寻路的第一步。这一方法把搜索区域简化成了一个二维数组。数组的每一个元素是网格的一个方块，方块被标记为可通过的和不可通过的。路径被描述为从A到B我们经过的方块的集合。一旦路径被找到，我们的人就从一个方格的中心走向另一个，直到到达目的地。&lt;/P&gt;
&lt;P&gt;这些中点被称为&amp;#8220;节点&amp;#8221;。当你阅读其他的寻路资料时，你将经常会看到人们讨论节点。为什么不把他们描述为方格呢？因为有可能你的路径被分割成其他不是方格的结构。他们完全可以是矩形，六角形，或者其他任意形状。节点能够被放置在形状的任意位置－可以在中心，或者沿着边界，或其他什么地方。我们使用这种系统，无论如何，因为它是最简单的。&lt;/P&gt;
&lt;P&gt;开始搜索&lt;/P&gt;
&lt;P&gt;正如我们处理上图网格的方法，一旦搜索区域被转化为容易处理的节点，下一步就是去引导一次找到最短路径的搜索。在A*寻路算法中，我们通过从点A开始，检查相邻方格的方式，向外扩展直到找到目标。&lt;/P&gt;
&lt;P&gt;我们做如下操作开始搜索：&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp; 1，从点A开始，并且把它作为待处理点存入一个&amp;#8220;开启列表&amp;#8221;。开启列表就像一张购物清单。尽管现在列表里只有一个元素，但以后就会多起来。你的路径可能会通过它包含的方格，也可能不会。基本上，这是一个待检查方格的列表。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 2，寻找起点周围所有可到达或者可通过的方格，跳过有墙，水，或其他无法通过地形的方格。也把他们加入开启列表。为所有这些方格保存点A作为&amp;#8220;父方格&amp;#8221;。当我们想描述路径的时候，父方格的资料是十分重要的。后面会解释它的具体用途。&lt;BR&gt;&amp;nbsp;&amp;nbsp; 3，从开启列表中删除点A，把它加入到一个&amp;#8220;关闭列表&amp;#8221;，列表中保存所有不需要再次检查的方格。&lt;/P&gt;
&lt;P&gt;在这一点，你应该形成如图的结构。在图中，暗绿色方格是你起始方格的中心。它被用浅蓝色描边，以表示它被加入到关闭列表中了。所有的相邻格现在都在开启列表中，它们被用浅绿色描边。每个方格都有一个灰色指针反指他们的父方格，也就是开始的方格。&lt;/P&gt;
&lt;P&gt;&lt;IMG height=150 src="/images/vckbase_com/panic/image002.jpg" width=151 border=0&gt;&lt;BR&gt;[图2]&lt;/P&gt;
&lt;P&gt;接着，我们选择开启列表中的临近方格，大致重复前面的过程，如下。但是，哪个方格是我们要选择的呢？是那个F值最低的。&lt;/P&gt;
&lt;P&gt;路径评分&lt;/P&gt;
&lt;P&gt;选择路径中经过哪个方格的关键是下面这个等式：&lt;/P&gt;
&lt;P&gt;F = G + H&lt;/P&gt;
&lt;P&gt;这里：&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * G = 从起点A，沿着产生的路径，移动到网格上指定方格的移动耗费。&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * H = 从网格上那个方格移动到终点B的预估移动耗费。这经常被称为启发式的，可能会让你有点迷惑。这样叫的原因是因为它只是个猜测。我们没办法事先知道路径的长度，因为路上可能存在各种障碍(墙，水，等等)。虽然本文只提供了一种计算H的方法，但是你可以在网上找到很多其他的方法。&lt;/P&gt;
&lt;P&gt;我们的路径是通过反复遍历开启列表并且选择具有最低F值的方格来生成的。文章将对这个过程做更详细的描述。首先，我们更深入的看看如何计算这个方程。&lt;/P&gt;
&lt;P&gt;正如上面所说，G表示沿路径从起点到当前点的移动耗费。在这个例子里，我们令水平或者垂直移动的耗费为10，对角线方向耗费为14。我们取这些值是因为沿对角线的距离是沿水平或垂直移动耗费的的根号2（别怕），或者约1.414倍。为了简化，我们用10和14近似。比例基本正确，同时我们避免了求根运算和小数。这不是只因为我们怕麻烦或者不喜欢数学。使用这样的整数对计算机来说也更快捷。你不就就会发现，如果你不使用这些简化方法，寻路会变得很慢。&lt;/P&gt;
&lt;P&gt;既然我们在计算沿特定路径通往某个方格的G值，求值的方法就是取它父节点的G值，然后依照它相对父节点是对角线方向或者直角方向(非对角线)，分别增加14和10。例子中这个方法的需求会变得更多，因为我们从起点方格以外获取了不止一个方格。&lt;/P&gt;
&lt;P&gt;H值可以用不同的方法估算。我们这里使用的方法被称为曼哈顿方法，它计算从当前格到目的格之间水平和垂直的方格的数量总和，忽略对角线方向。然后把结果乘以10。这被成为曼哈顿方法是因为它看起来像计算城市中从一个地方到另外一个地方的街区数，在那里你不能沿对角线方向穿过街区。很重要的一点，我们忽略了一切障碍物。这是对剩余距离的一个估算，而非实际值，这也是这一方法被称为启发式的原因。想知道更多？你可以在这里找到方程和额外的注解。&lt;/P&gt;
&lt;P&gt;F的值是G和H的和。第一步搜索的结果可以在下面的图表中看到。F,G和H的评分被写在每个方格里。正如在紧挨起始格右侧的方格所表示的，F被打印在左上角，G在左下角，H则在右下角。&lt;/P&gt;
&lt;P&gt;&lt;IMG height=255 src="/images/vckbase_com/panic/image003.jpg" width=362 border=0&gt;&lt;BR&gt;[图3]&lt;/P&gt;
&lt;P&gt;现在我们来看看这些方格。写字母的方格里，G = 10。这是因为它只在水平方向偏离起始格一个格距。紧邻起始格的上方，下方和左边的方格的G值都等于10。对角线方向的G值是14。&lt;/P&gt;
&lt;P&gt;H值通过求解到红色目标格的曼哈顿距离得到，其中只在水平和垂直方向移动，并且