1. 程式人生 > >人工智慧之搜尋策略-A*演算法入門

人工智慧之搜尋策略-A*演算法入門

現在你已經明白了基本的方法,自己用程式實現時還有一些事情要考慮.下面是我在使用C++和Blitz Basic實現時遇到的問題,但這些要點同樣適用於其它語言.
 
1.其它的元素(避免碰撞):如果你已經仔細看過我的樣例程式碼,你就會發現它完全忽略螢幕上的其它元素.不同的遊戲,這可能是可以接受,也可能不是.如果在尋徑演算法中考慮其它元素並讓它們自行運動,那麼我建議你只考慮停下的或者尋徑過程中鄰接的元素,把它們當前的位置當成不能通過的.對於那些移動的鄰接元素你可以避免碰撞找一個替代路徑.(詳解#2)
 
如果你選擇考慮正在運動元素和尋徑過程不鄰接的元素,你需要寫一個方法對他們在任意時間任意位置的情況做出預測來實現躲避.否則你可能得到一個鋸齒形路徑僅僅是為了躲避一個已經不在那個位置上的元素.
你當然還需要一些碰撞檢測程式碼,因為不管當時計算出來的路徑多好事情會隨著時間變化,當發生碰撞必須重新尋徑或者其它元素在運動而且不是迎頭相撞,等待直到路徑沒有障礙.
 
這些提示足以讓你開始了.如果你想了解更多,下面有一些有用的連結:
 
Steering Behavior for Autonomous Characters
:Craig Reynold在尋徑過程中的轉向處理略有不同,但它可以與尋徑演算法整合在一起實現更完整的避免碰撞系統.
The Long and Short of Steering in Computer Games: 一個有關轉向和尋徑的學術研究,這是一個PDF
Coordinated Unit Movement:對形成和基於組的運動兩部系列著作的第一部,作者是帝國時代的設計師Dave Pottinger
Implementing Coordinated Movement:Dave Pottinger兩部著作的第二部.
 
2.不同地貌的消耗
:本教程程式使用的地形有兩種:通行的和禁行的.但是如果一個格子是可以通過的,但是消耗特別大呢?沼澤,丘陵,地牢樓梯所有這些地形都可以通過但是比平地消耗較大.同樣路徑也會有一個成本比較小的.
 
這個問題通過計算G值時新增地形消耗就可以解決了.為那些地形簡單的新增一下消耗加成就可以.A*演算法裡面新增一下尋找最低消耗的操作就能很容易處理好.我用的例子裡面地形只有通行和禁行兩種因此A*就是尋找一個最短最直接的路徑.但在各種消耗不同的地形上最低消耗的路徑可能包含最遠距離的路徑,就像繞道而不是直接穿越沼澤.
 
還有一個要考慮的就是被專業人士稱作"影響對映"的東西.正如上面說的消耗不等的地形,你可以設計一個額外的加成系統應用到尋徑中.想象一下你有一個山地區域防守上面有很多防禦元素.每一次有人企圖通過都會被挫敗,如果你願意大可以建立一個地圖到處都是大屠殺.這將教會計算機選擇一條更安全的路徑.避免部隊僅僅因為最短就選擇一條路徑,因為它也可能更危險.還有一種可能性就是在移動元素的路徑旁邊使用威懾元素.A*演算法的一個缺點就是當一群元素都想到一個類似的目的地時,由於採取相同或者類似的路徑,路徑會出現比較嚴重的重疊.新增懲罰性到節點上有助於保證分離度,減少碰撞.不要把這些節點當成不能通過處理,因為你依然希望萬不得已多個元素還是可以擠在一條通道上.另外懲罰的原則是隻懲罰附近路徑上的元素而不是所有路徑,否則就會產生無處可躲的奇怪現象.並且懲罰節點出現的位置是沿著當前的路徑和之後要經過的路徑,而不是已近走過的路徑.
 
3.處理尚未探索到的區域:電腦總是知道如何移動,包括地圖還沒有探索開的情況,你玩過這樣的遊戲嗎?根據不同的遊戲,尋徑做得非常好.幸運的是,這是一個比較容易處理的問題.
答案是為每一個玩家和電腦對手建立一個單獨的"knownWalkability"陣列(每個玩家而不是每個元素--那將使用更多的記憶體).每一個數組都包含了玩家已經探索開的區域,地圖的其它部分假設是可以通過的,直到證明並非如此.使用這種方法,元素可以漫遊到死角,做出錯誤的選擇直到他們探索開周圍.一旦地圖被探索開,尋徑就可以正常工作了.
 
4.平滑路徑:儘管A*演算法會給你一個最短成本最低的路徑,但是它不會給你提供一條平滑的路徑.可以看一下我們圖7給出的最終路徑.那條路徑第一步就是其實點右下方的格子,我們的路徑能平滑一點從正下方開始麼?有幾種方法可以解決這一問題.計算路徑過程中一旦發現方向改變就增加懲罰值,同時修改G值.或者你可以在路徑計算之後,在鄰接的地方選擇一個節點使得路徑更為平滑.這個話題更多的討論請移步這裡檢視:
 Toward More Realistic Pathfindin
g,  來自Gamasutra.com 作者 Marco Pinter.
 
5.非方形搜尋區域:我們的例子使用的都是2D方形佈局.你不必拘泥於此.你可以使用不規則的地區.想一下棋盤遊戲Risk裡面的國家.你可以為類似那樣的遊戲想象一個尋徑場景.要做到這一點,你需要建立一個表來儲存國家的鄰接情況和從一個國家到另外一個國家的G值.你可能還需要一個方法來估算H值.剩下的就和上面例子的處理方式差不多了.除了使用鄰接區域,你要看一下鄰接國家,把鄰接國家也新增到open list中.類似的,你需要為一個固定的地形圖建立一個航點系統.航點通常是一條路徑上的關鍵點:比如隧道或者地牢.作為一個遊戲的設計者預先分配航點.如果兩個航點之間沒有障礙物可以直接相連就認為二者是"鄰接".正如Risk的例子,你會把這個鄰接資訊到一個查詢表中,並在構建open list過程中使用.你可能也會記錄G值(當然就是節點間的直線距離)和H值(節點到目的地的直線距離).其它過程照舊.
Amit patel 已經寫了一個簡短的文章來研究一些替代方案.另外一個例子是isometric RPG地圖使用的非方形搜尋區,移步檢視我的文章: Two-Tiered A* Pathfinding
 
6.速度相關:當開發自己的A*程式時,或者用我寫的那個程式,你最終會發現尋徑佔用了大量的CPU時間.特別是你有一定數量需要尋徑的元素而且地圖又夠大.如果讀過網路上一些文章,你會發現事實的確如此,甚至星際爭霸和帝國時代的設計者也深以為然.如果你發現由於尋徑導致遊戲變慢,下面有一些加速方法:
 考慮一個更小的地圖或者少一些影響尋徑的元素.
不要同時做多個尋徑,把他們放在一個佇列裡面分散到幾個遊戲週期進行.如果你的遊戲週期是40秒,沒有人會注意到.但是如果同時進行尋徑運算玩家會注意到遊戲變慢了.
在你的地圖上考慮使用較大的方形(不管你使用的到底是什麼圖形).這就大大減少了尋徑需要搜尋的節點總數.如果你野心勃勃,你可以設計出來兩套尋徑系統,在不同的路徑長度下使用.專業人士就是這樣做的,他們先使用大區域進行尋徑,然後切換到一個更精細的範圍進行尋徑.如果你對這個話題感興趣,請移步檢視我的文章: Two-Tiered A* Pathfinding
對於較長的路徑,考慮進入遊戲之前對路徑進行預計算.
考慮預處理地圖,找出那些地區是不能通過的,我們把這些地區稱為"孤島".在現實中,他們可以是島嶼或者任何其他從周圍無法進入的地區(比如被牆).A*演算法的一個缺點就是如果你告訴它需要尋找一個區域,它就會尋找整個地圖,直到找到每一個可以進入的節點已經處理放入了open list或者Closed list.這會浪費很多CPU時間.可以通過提前判斷是否可以進入來預防(比如洪水填充路徑或者類似的情況),記錄在陣列等類似的結構中,尋徑之前先進行這個檢查.
在一個擁擠的,迷宮式的環境中,考慮標記出節點不會導致任何地方成為死角.這些地區可以在地圖編輯中預先手工定製.或者你野心勃勃開發一個演算法可以自動識別識別這種區域.所有的死衚衕節點都會分配一個獨立的號碼標示.接著,你就可以放心地忽略掉尋徑中的死角,除非開始節點或者目的地節點就被圍在一個死衚衕中,你就停下來吧.
 
7.維護Open List:這實際上是A*演算法中最耗時的操作.每一次你訪問open list,你需要找到具有最低F值的格子.有很多方法可以做到這一點.你可以根據需要儲存路徑項或者遍歷整個表找到F值最小的格子.這很簡單但是真正尋徑過程中這是相當耗時的.可以維護一個SortedList然後每一次就取列表的第一個就是你想要的最小F值消耗節點.我寫程式的時候最初就使用這個方法.
 
這個方法在小地圖中表現不錯,但它不是最快的方案.嚴謹的A*演算法開發者為了加速會使用一種叫二叉堆(binary heap)的結構,現在我的程式碼就使用這個結構.就我的經驗,這種方法大多數情景中可以加快2-3倍,長路徑情況下速度呈幾何級數加快(快10倍以上).如果你有興趣瞭解更多二叉堆的內容,請看我的文章:Using Binary Heaps in A* Pathfinding.
 
另一個可能的瓶頸是你在尋徑過程中需要清空和維護的資料結構.我個人喜歡全都存放在陣列中.節點可以動態地,按照面對物件的方式建立,記錄,維護.我發現建立刪除物件需要消耗額外的時間這間接地影響了速度.如果你使用了陣列,每一次呼叫之間都要做清理.最後要做的就是每一次尋徑結束之後要全部進行初始化,特別是地圖特別大的時候.我通過使用二維陣列WhichList(x,y) 指示每一個節點是在open list還是closed list避免了這種情況.尋徑結束之後我並不初始化這個陣列而是每一次尋徑之後重置onClosedListOnOpenList的值,每次尋徑遞增5或者類似的值.使用這個方法,演算法就可以比較安全的忽略掉前一次尋徑的資料.我也把F,G,H值存放在陣列中.這樣我每一次就直接覆蓋已有值而不必尋徑結束之後清除整個陣列了.儲存二維陣列需要使用更多的記憶體,因此這裡有一個權衡,無論如何你都應該選擇一個自己感覺舒服的資料結構.