A*尋路初探 GameDev.net

A*尋路初探 GameDev.net

MulinB按:經典的智能尋路算法,一個老外寫的很透徹很清晰,很容易讓人理解神祕的A*算法。如下是一箇中文翻譯版。html

image

A*尋路初探 GameDev.netios

做者: Patrick Lester程序員

譯者:Panic 2005年3月18日算法

譯者序

好久之前就知道了A*算法,可是從未認真讀過相關的文章,也沒有看過代碼,只是腦子裏有個模糊的概念。此次決定從頭開始,研究一下這個被人推崇備至的簡單方法,做爲學習人工智能的開始。數組


這篇文章很是知名,國內應該有很多人翻譯過它,我沒有查找,以爲翻譯自己也是對自身英文水平的鍛鍊。通過努力,終於完成了文檔,也明白的A*算法的原理。毫無疑問,做者用形象的描述,簡潔詼諧的語言由淺入深的講述了這一神奇的算法,相信每一個讀過的人都會對此有所認識(若是沒有,那就是偶的翻譯太差了--b)。安全

如今是2005年7月19日的版本,應原做者要求,對文中的某些算法細節作了修改。數據結構

英文原文

原文連接:http://www.gamedev.net/page/resources/_/technical/artificial-intelligence/a-pathfinding-for-beginners-r2003app

原做者文章連接:http://www.policyalmanac.org/games/aStarTutorial.htm編輯器

如下是翻譯的正文

會者不難,A*(念做A星)算法對初學者來講的確有些難度。學習

這篇文章並不試圖對這個話題做權威的陳述。取而代之的是,它只是描述算法的原理,使你能夠在進一步的閱讀中理解其餘相關的資料。

最後,這篇文章沒有程序細節。你盡能夠用任意的計算機程序語言實現它。如你所願,我在文章的末尾包含了一個指向例子程序的連接。 壓縮包包括C++和Blitz Basic兩個語言的版本,若是你只是想看看它的運行效果,裏面還包含了可執行文件。

咱們正在提升本身。讓咱們從頭開始。。。

序:搜索區域

假設有人想從A點移動到一牆之隔的B點,以下圖,綠色的是起點A,紅色是終點B,藍色方塊是中間的牆。

2011070118152022 image

[圖1]

你首先注意到,搜索區域被咱們劃分紅了方形網格。像這樣,簡化搜索區域,是尋路的第一步。這一方法把搜索區域簡化成了一個二維數組。數組的每個元素是網格的一個方塊,方塊被標記爲可經過的和不可經過的。路徑被描述爲從A到B咱們通過的方塊的集合。一旦路徑被找到,咱們的人就從一個方格的中心走向另外一個,直到到達目的地。

這些中點被稱爲「節點」。當你閱讀其餘的尋路資料時,你將常常會看到人們討論節點。爲何不把他們描述爲方格呢?由於有可能你的路徑被分割成其餘不是方格的結構。他們徹底能夠是矩形,六角形,或者其餘任意形狀。節點可以被放置在形狀的任意位置-能夠在中心,或者沿着邊界,或其餘什麼地方。咱們使用這種系統,不管如何,由於它是最簡單的。

開始搜索

正如咱們處理上圖網格的方法,一旦搜索區域被轉化爲容易處理的節點,下一步就是去引導一次找到最短路徑的搜索。在A*尋路算法中,咱們經過從點A開始,檢查相鄰方格的方式,向外擴展直到找到目標。

開始搜索

咱們作以下操做開始搜索:
   1,從點A開始,而且把它做爲待處理點存入一個「開啓列表」。開啓列表就像一張購物清單。儘管如今列表裏只有一個元素,但之後就會多起來。你的路徑可能會經過它包含的方格,也可能不會。基本上,這是一個待檢查方格的列表。
   2,尋找起點周圍全部可到達或者可經過的方格,跳過有牆,水,或其餘沒法經過地形的方格。也把他們加入開啓列表。爲全部這些方格保存點A做爲「父方格」。當咱們想描述路徑的時候,父方格的資料是十分重要的。後面會解釋它的具體用途。
   3,從開啓列表中刪除點A,把它加入到一個「關閉列表」,列表中保存全部不須要再次檢查的方格。

在這一點,你應該造成如圖的結構。在圖中,暗綠色方格是你起始方格的中心。它被用淺藍色描邊,以表示它被加入到關閉列表中了。全部的相鄰格如今都在開啓列表中,它們被用淺綠色描邊。每一個方格都有一個灰色指針反指他們的父方格,也就是開始的方格。

2011070118162777

[圖2]

接着,咱們選擇開啓列表中的臨近方格,大體重複前面的過程,以下。可是,哪一個方格是咱們要選擇的呢?是那個F值最低的。

路徑評分

選擇路徑中通過哪一個方格的關鍵是下面這個等式:

F = G + H

這裏:
    * G = 從起點A,沿着產生的路徑,移動到網格上指定方格的移動耗費。
    * H = 從網格上那個方格移動到終點B的預估移動耗費。這常常被稱爲啓發式的,可能會讓你有點迷惑。這樣叫的緣由是由於它只是個猜想。咱們沒辦法事先知道路徑的長度,由於路上可能存在各類障礙(牆,水,等等)。雖然本文只提供了一種計算H的方法,可是你能夠在網上找到不少其餘的方法。

咱們的路徑是經過反覆遍歷開啓列表而且選擇具備最低F值的方格來生成的。文章將對這個過程作更詳細的描述。首先,咱們更深刻的看看如何計算這個方程。

正如上面所說,G表示沿路徑從起點到當前點的移動耗費。在這個例子裏,咱們令水平或者垂直移動的耗費爲10,對角線方向耗費爲14。咱們取這些值是由於沿對角線的距離是沿水平或垂直移動耗費的的根號2(別怕),或者約1.414倍。爲了簡化,咱們用10和14近似。比例基本正確,同時咱們避免了求根運算和小數。這不是隻由於咱們怕麻煩或者不喜歡數學。使用這樣的整數對計算機來講也更快捷。你不就就會發現,若是你不使用這些簡化方法,尋路會變得很慢。

既然咱們在計算沿特定路徑通往某個方格的G值,求值的方法就是取它父節點的G值,而後依照它相對父節點是對角線方向或者直角方向(非對角線),分別增長14和10。例子中這個方法的需求會變得更多,由於咱們從起點方格之外獲取了不止一個方格。

H值能夠用不一樣的方法估算。咱們這裏使用的方法被稱爲曼哈頓方法,它計算從當前格到目的格之間水平和垂直的方格的數量總和,忽略對角線方向。而後把結果乘以10。這被成爲曼哈頓方法是由於它看起來像計算城市中從一個地方到另一個地方的街區數,在那裏你不能沿對角線方向穿過街區。很重要的一點,咱們忽略了一切障礙物。這是對剩餘距離的一個估算,而非實際值,這也是這一方法被稱爲啓發式的緣由。想知道更多?你能夠在這裏找到方程和額外的註解。

F的值是G和H的和。第一步搜索的結果能夠在下面的圖表中看到。F,G和H的評分被寫在每一個方格里。正如在緊挨起始格右側的方格所表示的,F被打印在左上角,G在左下角,H則在右下角。

 2011070118171423

[圖3]

如今咱們來看看這些方格。寫字母的方格里,G = 10。這是由於它只在水平方向偏離起始格一個格距。緊鄰起始格的上方,下方和左邊的方格的G值都等於10。對角線方向的G值是14。

H值經過求解到紅色目標格的曼哈頓距離獲得,其中只在水平和垂直方向移動,而且忽略中間的牆。用這種方法,起點右側緊鄰的方格離紅色方格有3格距離,H值就是30。這塊方格上方的方格有4格距離(記住,只能在水平和垂直方向移動),H值是40。你大體應該知道如何計算其餘方格的H值了~。

每一個格子的F值,仍是簡單的由G和H相加獲得

繼續搜索

爲了繼續搜索,咱們簡單的從開啓列表中選擇F值最低的方格。而後,對選中的方格作以下處理:

   4,把它從開啓列表中刪除,而後添加到關閉列表中。
   5,檢查全部相鄰格子。跳過那些已經在關閉列表中的或者不可經過的(有牆,水的地形,或者其餘沒法經過的地形),把他們添加進開啓列表,若是他們還不在裏面的話。把選中的方格做爲新的方格的父節點。
   6,若是某個相鄰格已經在開啓列表裏了,檢查如今的這條路徑是否更好。換句話說,檢查若是咱們用新的路徑到達它的話,G值是否會更低一些。若是不是,那就什麼都不作。
      另外一方面,若是新的G值更低,那就把相鄰方格的父節點改成目前選中的方格(在上面的圖表中,把箭頭的方向改成指向這個方格)。最後,從新計算F和G的值。若是這看起來不夠清晰,你能夠看下面的圖示。

好了,讓咱們看看它是怎麼運做的。咱們最初的9格方格中,在起點被切換到關閉列表中後,還剩8格留在開啓列表中。這裏面,F值最低的那個是起始格右側緊鄰的格子,它的F值是40。所以咱們選擇這一格做爲下一個要處理的方格。在緊隨的圖中,它被用藍色突出顯示。

 2011070118175047

[圖4]

首先,咱們把它從開啓列表中取出,放入關閉列表(這就是他被藍色突出顯示的緣由)。而後咱們檢查相鄰的格子。哦,右側的格子是牆,因此咱們略過。左側的格子是起始格。它在關閉列表裏,因此咱們也跳過它。

其餘4格已經在開啓列表裏了,因而咱們檢查G值來斷定,若是經過這一格到達那裏,路徑是否更好。咱們來看選中格子下面的方格。它的G值是14。若是咱們從當前格移動到那裏,G值就會等於20(到達當前格的G值是10,移動到上面的格子將使得G值增長10)。由於G值20大於14,因此這不是更好的路徑。若是你看圖,就能理解。與其經過先水平移動一格,再垂直移動一格,還不如直接沿對角線方向移動一格來得簡單。

當咱們對已經存在於開啓列表中的4個臨近格重複這一過程的時候,咱們發現沒有一條路徑能夠經過使用當前格子獲得改善,因此咱們不作任何改變。既然咱們已經檢查過了全部鄰近格,那麼就能夠移動到下一格了。

因而咱們檢索開啓列表,如今裏面只有7格了,咱們仍然選擇其中F值最低的。有趣的是,此次,有兩個格子的數值都是54。咱們如何選擇?這並不麻煩。從速度上考慮,選擇最後添加進列表的格子會更快捷。這種致使了尋路過程當中,在靠近目標的時候,優先使用新找到的格子的偏好。但這可有可無。(對相同數值的不一樣對待,致使不一樣版本的A*算法找到等長的不一樣路徑。)

那咱們就選擇起始格右下方的格子,如圖。

 2011070118182054

[圖5]

此次,當咱們檢查相鄰格的時候,發現右側是牆,因而略過。上面一格也被略過。咱們也略過了牆下面的格子。爲何呢?由於你不能在不穿越牆角的狀況下直接到達那個格子。你的確須要先往下走而後到達那一格,循序漸進的走過那個拐角。(註解:穿越拐角的規則是可選的。它取決於你的節點是如何放置的。)

這樣一來,就剩下了其餘5格。當前格下面的另外兩個格子目前不在開啓列表中,因而咱們添加他們,而且把當前格指定爲他們的父節點。其他3格,兩個已經在關閉列表中(起始格,和當前格上方的格子,在表格中藍色高亮顯示),因而咱們略過它們。最後一格,在當前格的左側,將被檢查經過這條路徑,G值是否更低。沒必要擔憂,咱們已經準備好檢查開啓列表中的下一格了。

咱們重複這個過程,直到目標格被添加進關閉列表(註解),就如在下面的圖中所看到的。

 2011070118190796

[圖6]

注意,起始格下方格子的父節點已經和前面不一樣的。以前它的G值是28,而且指向右上方的格子。如今它的G值是20,指向它上方的格子。這在尋路過程當中的某處發生,當應用新路徑時,G值通過檢查變得低了-因而父節點被從新指定,G和F值被從新計算。儘管這一變化在這個例子中並不重要,在不少場合,這種變化會致使尋路結果的巨大變化。

那麼,咱們怎麼肯定這條路徑呢?很簡單,從紅色的目標格開始,按箭頭的方向朝父節點移動。這最終會引導你回到起始格,這就是你的路徑!看起來應該像圖中那樣。從起始格A移動到目標格B只是簡單的從每一個格子(節點)的中點沿路徑移動到下一個,直到你到達目標點。就這麼簡單。

 2011070118194389

[圖7]

A*方法總結

好,如今你已經看完了整個說明,讓咱們把每一步的操做寫在一塊兒:

   1,把起始格添加到開啓列表。
   2,重複以下的工做:
      a) 尋找開啓列表中F值最低的格子。咱們稱它爲當前格。
      b) 把它切換到關閉列表。
      c) 對相鄰的8格中的每個?
          * 若是它不可經過或者已經在關閉列表中,略過它。反之以下。
          * 若是它不在開啓列表中,把它添加進去。把當前格做爲這一格的父節點。記錄這一格的F,G,和H值。
          * 若是它已經在開啓列表中,用G值爲參考檢查新的路徑是否更好。更低的G值意味着更好的路徑。若是是這樣,就把這一格的父節點改爲當前格,而且從新計算這一格的G和F值。若是你保持你的開啓列表按F值排序,改變以後你可能須要從新對開啓列表排序。

      d) 中止,當你
          * 把目標格添加進了關閉列表(註解),這時候路徑被找到,或者
          * 沒有找到目標格,開啓列表已經空了。這時候,路徑不存在。
   3.保存路徑。從目標格開始,沿着每一格的父節點移動直到回到起始格。這就是你的路徑。

(:在這篇文章的較早版本中,建議的作法是當目標格(或節點)被加入到開啓列表,而不是關閉列表的時候中止尋路。這麼作會更迅速,並且幾乎老是能找到最短的路徑,但不是絕對的。當從倒數第二個節點到最後一個(目標節點)之間的移動耗費懸殊很大時-例如恰好有一條河穿越兩個節點中間,這時候舊的作法和新的作法就會有顯著不一樣。)

題外話

離題一下,見諒,值得一提的是,當你在網上或者相關論壇看到關於A*的不一樣的探討,你有時會看到一些被看成A*算法的代碼而實際上他們不是。要使用A*,你必須包含上面討論的全部元素--特定的開啓和關閉列表,用F,G和H做路徑評價。有不少其餘的尋路算法,但他們並非A*,A*被認爲是他們當中最好的。Bryan Stout在這篇文章後面的參考文檔中論述了一部分,包括他們的一些優勢和缺點。有時候特定的場合其餘算法會更好,但你必須很明確你在做什麼。好了,夠多的了。回到文章。

實現的註解

如今你已經明白了基本原理,寫你的程序的時候還得考慮一些額外的東西。下面這些材料中的一些引用了我用C++和Blitz Basic寫的程序,但對其餘語言寫的代碼一樣有效。

1.其餘單位(避免碰撞):

若是你剛好看了個人例子代碼,你會發現它徹底忽略了其餘單位。個人尋路者事實上能夠相互穿越。取決於具體的遊戲,這也許能夠,也許不行。若是你打算考慮其餘單位,但願他們能互相繞過,我建議你只考慮靜止或那些在計算路徑時臨近當前單位的單位,把它們當前的位置標誌爲可經過的。對於臨近的運動着的單位,你能夠經過懲罰它們各自路徑上的節點,來鼓勵這些尋路者找到不一樣的路徑(更多的描述見#2).

若是你選擇了把其餘正在移動而且遠離當前尋路單位的那些單位考慮在內,你將須要實現一種方法及時預測在什麼時候何地碰撞可能會發生,以便恰當的避免。不然你極有可能獲得一條怪異的路徑,單位忽然轉彎試圖避免和一個已經不存在的單位發生碰撞。

固然,你也須要寫一些碰撞檢測的代碼,由於不管計算的時候路徑有多完美,它也會因時間而改變。當碰撞發生時,一個單位必須尋找一條新路徑,或者,若是另外一個單位正在移動而且不是正面碰撞,在繼續沿當前路徑移動以前,等待那個單位離開。

這些提示大概可讓你開始了。若是你想了解更多,這裏有些你可能會以爲有用的連接:

    * 自治角色的指導行爲:Craig Reynold在指導能力上的工做和尋路有些不一樣,可是它能夠和尋路整合從而造成更完整的移動和碰撞檢測系統。
    * 電腦遊戲中的長短距指導:指導和尋路方面著做的一個有趣的考察。這是一個pdf文件。
    * 協同單位移動:一個兩部分系列文章的第一篇,內容是關於編隊和基於分組的移動,做者是帝國時代(Age of Empires)的設計者Dave Pottinger.
    * 實現協同移動:Dave Pottinger文章系列的第二篇。

2. 不一樣的地形損耗:

在這個教程和我附帶的程序中,地形只能是兩者之-可經過的和不可經過的。可是你可能會須要一些可經過的地形,可是移動耗費更高-沼澤,小山,地牢的樓梯,等等。這些都是可經過可是比平坦的開闊地移動耗費更高的地形。相似的,道路應該比天然地形移動耗費更低。

這個問題很容易解決,只要在計算任何地形的G值的時候增長地形損耗就能夠了。簡單的給它增長一些額外的損耗就能夠了。因爲A*算法已經按照尋找最低耗費的路徑來設計,因此很容易處理這種狀況。在我提供的這個簡單的例子裏,地形只有可經過和不可經過兩種,A*會找到最短,最直接的路徑。可是在地形耗費不一樣的場合,耗費最低的路徑也許會包含很長的移動距離-就像沿着路繞過沼澤而不是直接穿過它。

一種需額外考慮的狀況是被專家稱之爲「influence mapping」的東西(暫譯爲影響映射圖)。就像上面描述的不一樣地形耗費同樣,你能夠建立一格額外的分數系統,並把它應用到尋路的AI中。假設你有一張有大批尋路者的地圖,他們都要經過某個山區。每次電腦生成一條經過那個關口的路徑,它就會變得更擁擠。若是你願意,你能夠建立一個影響映射圖對有大量屠殺事件的格子施以不利影響。這會讓計算機更傾向安全些的路徑,而且幫助它避免老是僅僅由於路徑短(但可能更危險)而持續把隊伍和尋路者送到某一特定路徑。

另外一個可能得應用是懲罰周圍移動單位路徑上得節點。A*的一個底限是,當一羣單位同時試圖尋路到接近的地點,這一般會致使路徑交疊。覺得一個或者多個單位都試圖走相同或者近似的路徑到達目的地。對其餘單位已經「認領」了的節點增長一些懲罰會有助於你在必定程度上分離路徑,下降碰撞的可能性。然而,若是有必要,不要把那些節點當作不可經過的,由於你仍然但願多個單位可以一字縱隊經過擁擠的出口。同時,你只能懲罰那些臨近單位的路徑,而不是全部路徑,不然你就會獲得奇怪的躲避行爲例如單位躲避路徑上其餘已經不在那裏的單位。 還有,你應該只懲罰路徑當前節點和隨後的節點,而不該處理已經走過並甩在身後的節點。

3. 處理未知區域:

你是否玩過這樣的PC遊戲,電腦老是知道哪條路是正確的,即便它尚未偵察過地圖?對於遊戲,尋路太好會顯得不真實。幸運的是,這是一格能夠輕易解決的問題。

答案就是爲每一個不一樣的玩家和電腦(每一個玩家,而不是每一個單位--那樣的話會耗費大量的內存)建立一個獨立的「knownWalkability」數組,每一個數組包含玩家已經探索過的區域,以及被看成可經過區域的其餘區域,直到被證明。用這種方法,單位會在路的死端徘徊而且致使錯誤的選擇直到他們在周圍找到路。一旦地圖被探索了,尋路就像往常那樣進行。

4. 平滑路徑:

儘管A*提供了最短,最低代價的路徑,它沒法自動提供看起來平滑的路徑。看一下咱們的例子最終造成的路徑(在圖7)。最初的一步是起始格的右下方,若是這一步是直接往下的話,路徑不是會更平滑一些嗎?

有幾種方法來解決這個問題。當計算路徑的時候能夠對改變方向的格子施加不利影響,對G值增長額外的數值。也能夠換種方法,你能夠在路徑計算完以後沿着它跑一遍,找那些用相鄰格替換會讓路徑看起來更平滑的地方。想知道完整的結果,查看Toward More Realistic Pathfinding,一篇(免費,可是須要註冊)Marco Pinter發表在Gamasutra.com的文章

5. 非方形搜索區域:

在咱們的例子裏,咱們使用簡單的2D方形圖。你能夠不使用這種方式。你可使用不規則形狀的區域。想一想冒險棋的遊戲,和遊戲中那些國家。你能夠設計一個像那樣的尋路關卡。爲此,你可能須要創建一個國家相鄰關係的表格,和從一個國家移動到另外一個的G值。你也須要估算H值的方法。其餘的事情就和例子中徹底同樣了。當你須要向開啓列表中添加新元素的時候,不需使用相鄰的格子,取而代之的是從表格中尋找相鄰的國家。

相似的,你能夠爲一張肯定的地形圖建立路徑點系統,路徑點通常是路上,或者地牢通道的轉折點。做爲遊戲設計者,你能夠預設這些路徑點。兩個路徑點被認爲是相鄰的若是他們之間的直線上沒有障礙的話。在冒險棋的例子裏,你能夠保存這些相鄰信息在某個表格裏,當須要在開啓列表中添加元素的時候使用它。而後你就能夠記錄關聯的G值(可能使用兩點間的直線距離),H值(可使用到目標點的直線距離),其餘都按原先的作就能夠了。

Amit Patel 寫了其餘方法的摘要。另外一個在非方形區域搜索RPG地圖的例子,查看個人文章Two-Tiered A* Pathfinding。(譯者注:譯文:  A*分層尋路)

6. 一些速度方面的提示:

當你開發你本身的A*程序,或者改寫個人,你會發現尋路佔據了大量的CPU時間,尤爲是在大地圖上有大量對象在尋路的時候。若是你閱讀過網上的其餘材料,你會明白,即便是開發了星際爭霸或帝國時代的專家,這也迫不得已。若是你以爲尋路太過緩慢,這裏有一些建議也許有效:

    * 使用更小的地圖或者更少的尋路者。

    * 不要同時給多個對象尋路。取而代之的是把他們加入一個隊列,把尋路過程分散在幾個遊戲週期中。若是你的遊戲以40週期每秒的速度運行,沒人能察覺。可是當大量尋路者計算本身路徑的時候,他們會發覺遊戲速度忽然變慢。

    * 儘可能使用更大的地圖網格。這下降了尋路中搜索的總網格數。若是你有志氣,你能夠設計兩個或者更多尋路系統以便使用在不一樣場合,取決於路徑的長度。這也正是專業人士的作法,用大的區域計算長的路徑,而後在接近目標的時候切換到使用小格子/區域的精細尋路。若是你對這個觀點感興趣,查閱個人文章Two-Tiered A* Pathfinding。(譯者注:譯文 :A*分層尋路)

    * 使用路徑點系統計算長路徑,或者預先計算好路徑並加入到遊戲中。
    * 預處理你的地圖,代表地圖中哪些區域是不可到達的。我把這些區域稱做「孤島」。事實上,他們能夠是島嶼或其餘被牆壁包圍等沒法到達的任意區域。A*的下限是,當你告訴它要尋找通往那些區域的路徑時,它會搜索整個地圖,直到全部可到達的方格/節點都被經過開啓列表和關閉列表的計算。這會浪費大量的CPU時間。能夠經過預先肯定這些區域(好比經過flood-fill或相似的方法)來避免這種狀況的發生,用某些種類的數組記錄這些信息,在開始尋路前檢查它。
    * 在一個擁擠的相似迷宮的場合,把不能連通的節點看做死端。這些區域能夠在地圖編輯器中預先手動指定,或者若是你有雄心壯志,開發一個自動識別這些區域的算法。給定死端的全部節點能夠被賦予一個惟一的標誌數字。而後你就能夠在尋路過程當中安全的忽略全部死端,只有當起點或者終點剛好在死端的某個節點的時候才須要考慮它們。

7. 維護開啓列表:

這是A*尋路算法最重要的組成部分。每次你訪問開啓列表,你都須要尋找F值最低的方格。有幾種不一樣的方法實現這一點。你能夠把路徑元素隨意保存,當須要尋找F值最低的元素的時候,遍歷開啓列表。這很簡單,可是太慢了,尤爲是對長路徑來講。這能夠經過維護一格排好序的列表來改善,每次尋找F值最低的方格只須要選取列表的首元素。當我本身實現的時候,這種方法是個人首選。

在小地圖。這種方法工做的很好,但它並非最快的解決方案。更苛求速度的A*程序員使用叫作二叉堆的方法,這也是我在代碼中使用的方法。憑個人經驗,這種方法在大多數場合會快2~3倍,而且在長路經上速度呈幾何級數提高(10倍以上速度)。若是你想了解更多關於二叉堆的內容,查閱個人文章,Using Binary Heaps in A* Pathfinding。(譯者注:譯文:在A*尋路中使用二叉堆)

另外一個可能的瓶頸是你在屢次尋路之間清除和保存你的數據結構的方法。我我的更傾向把全部東西都存儲在數組裏面。雖然節點能夠以面向對象的風格被動態的產生,記錄和保存,我發現建立和刪除對象所增長的大量時間,以及多餘的管理層次減慢的整個過程的速度。可是,若是你使用數組,你須要在調用之間清理數據。這中情形你想作的最後一件事就是在尋路調用以後花點時間把一切歸零,尤爲是你的地圖很大的時候。

我經過使用一個叫作whichList(x,y)的二維數組避免這種開銷,數組的每一個元素代表了節點在開啓列表仍是在關閉列表中。嘗試尋路以後,我沒有清零這個數組。取而代之的是,我在新的尋路中重置onClosedList和onOpenList的數值,每次尋路兩個都+5或者相似其餘數值。這種方法,算法能夠安全的跳過前面尋路留下的髒數據。我還在數組中儲存了諸如F,G和H的值。這樣一來,我只需簡單的重寫任何已經存在的值而無需被清除數組的操做干擾。將數據存儲在多維數組中須要更多內存,因此這裏須要權衡利弊。最後,你應該使用你最駕輕就熟的方法。

8. Dijkstra的算法:

儘管A*被認爲是一般最好的尋路算法(看前面的「題外話」),仍是有一種另外的算法有它的可取之處-Dijkstra算法。Dijkstra算法和A*本質是相同的,只有一點不一樣,就是Dijkstra算法沒有啓發式(H值老是0)。因爲沒有啓發式,它在各個方向上平均搜索。正如你所預料,因爲Dijkstra算法在找到目標前一般會探索更大的區域,因此通常會比A*更慢一些。

爲何要使用Dijkstra算法?

那麼爲何要使用這種算法呢?由於有時候咱們並不知道目標的位置。好比說你有一個資源採集單位,須要獲取某種類型的資源若干。它可能知道幾個資源區域,可是它想去最近的那個。這種狀況,Dijkstra算法就比A*更適合,由於咱們不知道哪一個更近。用A*,咱們惟一的選擇是依次對每一個目標許路並計算距離,而後選擇最近的路徑。咱們尋找的目標可能會有不可勝數的位置,咱們只想找其中最近的,而咱們並不知道它在哪裏,或者不知道哪一個是最近的。

進一步的閱讀

好,如今你對一些進一步的觀點有了初步認識。這時,我建議你研究個人源代碼。包裏面包含兩個版本,一個是用C++寫的,另外一個用Blitz Basic。順便說一句,兩個版本都註釋詳盡,容易閱讀,這裏是連接。

    * 例子代碼: A* Pathfinder (2D) Version 1.9

若是你既不用C++也不用Blitz Basic,在C++版本里有兩個小的可執行文件。Blitz Basic能夠在從Blitz Basic網站免費下載的Blitz Basic 3D(不是Blitz Plus)演示版上運行。Ben O'Neill提供一個聯機演示能夠在這裏找到。

你也該看看如下的網頁。讀了這篇教程後,他們應該變得容易理解多了。

    * Amit的 A* 頁面:這是由Amit Patel製做,被普遍引用的頁面,若是你沒有事先讀這篇文章,可能會有點難以理解。值得一看。尤爲要看Amit關於這個問題的本身的見解。
    * Smart Moves:智能尋路:Bryan Stout發表在Gamasutra.com的這篇文章須要註冊才能閱讀。註冊是免費的並且比起這篇文章和網站的其餘資源,是很是物有所值的。Bryan用Delphi寫的程序幫助我學習A*,也是個人A*代碼的靈感之源。它還描述了A*的幾種變化。
    * 地形分析:這是一格高階,可是有趣的話題,Dave Pottinge撰寫,Ensemble Studios的專家。這傢伙參與了帝國時代和君王時代的開發。別期望看懂這裏全部的東西,可是這是篇有趣的文章也許會讓你產生本身的想法。它包含一些對mip-mapping,influence mapping以及其餘一些高級AI/尋路觀點。對"flood filling"的討論使我有了我本身的「死端」和「孤島」的代碼的靈感,這些包含在我Blitz版本的代碼中。

值得一看的網站

其餘一些值得一看的網站:

我一樣高度推薦下面這幾本書, 裏面有不少關於尋路和其餘AI話題的文章。 它們也附帶了實例代碼的CD。這些書我都買了。另外,若是你經過下面的連接購買了它們,我會從Amazon獲得幾個美分。:)

好了,這就是所有。若是你恰好寫一個運用這些觀點的程序,我想拜讀一下。你能夠這樣聯繫我:Posted Image

在那以前,好運!

相關文章
相關標籤/搜索