摘要:在遊戲中,只須要鼠標輕輕的一點,系統會當即尋找離角色最近的一條路線。這背後的行爲邏輯又有什麼奧祕呢?
做者:JohnserfSeedhtml
在遊戲中,當咱們須要讓角色移動到指定位置時,只須要鼠標輕輕的一點就能夠完成這簡單的步驟,系統會當即尋找離角色最近的一條路線。算法
但是,這背後的行爲邏輯又有什麼奧祕呢? 你會怎麼寫這個尋路算法呢?windows
通常咱們遇到這種路徑搜索問題,你們首先能夠想到的是廣度優先搜索算法(Breadth First Search)、還有深度優先(Depth First Search)、弗洛伊德(Floyd)、迪傑斯特拉(Dij)等等這些很是著名的路徑搜索算法,可是在絕大多數狀況下這些算法面臨的缺點就暴露了出來:時間複雜度比較高。工具
因此,大部分環境裏咱們用到的是一個名叫A* (A star)的搜索算法優化
![](http://static.javashuo.com/static/loading.gif)
說到最短路徑呢,咱們就不得不提到廣度優先遍歷(BFS),它是一個萬能算法,它不僅僅能夠用在 尋路或者搜索的問題上。windows的系統工具:畫板 中的油漆桶就是其比較典型一個的例子。spa
這裏對路徑搜索作一個比較簡潔的示例3d
假設咱們是在一個網格上面進行最短路徑的搜索htm
咱們只能上下左右移動,不能夠穿越障礙物。算法的目的是爲了能讓你尋找到一條從起點到站點的最短路徑blog
假設每次均可以上下左右朝4個方向進行移動排序
算法在每一輪遍歷後會標記這一輪探索過的方塊稱爲邊界(Frontier),就是這些綠色的方塊。
![](http://static.javashuo.com/static/loading.gif)
而後算法呢會循環往復的從這些邊界方塊開始,朝他們上下左右四個方向進行探索,直到算法遍歷到了終點方塊纔會中止。而最短路徑呢就是算法以前一次探索過的路徑。爲了獲得算法探索過的整條路徑呢,咱們能夠在搜索的過程當中順勢記錄下路徑的來向。
好比這裏方塊上的白色箭頭就表明了以前方塊的位置
在每一次探索路徑的時候,咱們要作的也只是額外的記錄下這個信息
要注意,全部探索過的路徑咱們須要將它們標記成灰色,表明它們「已經被訪問過「,這樣子算法就不會重複探索已經走過的路徑了。
廣度優先算法顯然能夠幫助咱們找到最短路徑,不過呢它有點傻,由於它對路徑的尋找是沒有方向性的,它會向各個方向探測過去。
最壞的狀況多是找到終點須要遍歷整個地圖,所以很不智能,咱們須要一個更加高效的算法。
就是本次咱們要介紹的A * (A star)搜索算法
A* Search Algorithm
」A*搜索算法「也被叫作「啓發式搜索」
與廣度優先不一樣的是,咱們在每一輪循環的時候不會去探索全部的邊界方塊(Frontier),而會去選擇當前「代價(cost)」最低的方塊進行探索。
這裏的「代價」就頗有意思了,也是A*算法智能的地方。
咱們能夠把這裏的代價分紅兩部分,一部分是「當前路程代價(可表示成f-cost)」:好比你從起點出發一共走過多少個格子,f-cost就是幾。
另外一部分是「預估代價(可表示成g-cost)」:用來表示從當前方塊到再終點方塊大概須要多少步,預估預估因此它不是一個精確的數值,也不表明從當前位置出發就必定會走那麼遠的距離,不過咱們會用這個估計值來指導算法去優先搜索更有但願的路徑。
最經常使用到的「預估代價」有歐拉距離(Euler Distance)「,就是兩點之間的直線距離(x1 - x2)^2 + (y1 - y2)^2
固然還有更容易計算的「曼哈頓距離(Manhattan Distance)」,就是兩點在豎直方向和水平方向上的距離總和|x1 - x2|+|y1 - y 2|
曼哈頓距離不用開方,速度快,因此在A* 算法中咱們能夠用它來充當g-cost。
接下來,咱們只要把以前講到的這兩個代價相加就得出了總代價:f-cost + g-cost。
而後在探索方塊中,優先挑選總代價最低的方塊進行探索,這樣子就會少走不少彎路
並且搜索到的路徑也必定是最短路徑。
在第一輪循環中,算法對起點周圍的四個方塊進行探索,並計算出「當前代價」和「預估代價」。
好比這裏的1表明從起步到當前方塊走了1步
這裏的4表明着方塊到終點的曼哈頓距離,在這四個邊界方塊中,右邊方塊代價最低,所以在下一輪循環中會優先對它進行搜尋
在下一輪循環中,咱們已一樣的方式計算出方塊的代價,發現最右邊的方塊價值依然最低,所以在下一輪的循環中,咱們對它進行搜尋
算法就這樣子循環往復下去,直到搜尋到終點爲止
增長一下方塊的數量級,A*算法一樣能夠找到正確的最短路徑
最爲關鍵的是,它搜尋的方塊個數明顯比廣度優先遍歷少不少,所以也就更高效。
理解了算法的基本原理後,接下來就是上代碼了,這裏我直接引用redblobgames的Python代碼實現,由於人家實在寫的太好了!
def heuristic(a, b): #Manhattan Distance (x1, y1) = a (x2, y2) = b return abs(x1 - x2) + abs(y1 - y2) def a_star_search(graph, start, goal): frontier = PriorityQueue() frontier.put(start, 0) came_from = {} cost_so_far = {} came_from[start] = None cost_so_far[start] = 0 while not frontier.empty(): current = frontier.get() if current = goal: break for next in graph.neighbors(current): new_cost = cost_so_far[current] + graph.cost(current, next) if next not in cost_so_far or new_cost < cost_so_far[next]: cost_so_far[next] = new_cost priority = new_cost + heuristic(goal, next) frontier.put(next, priority) came_from[next] = current return came_from, cost_so_far
先來看看最上面幾行,frontier中存放了咱們這一輪探測過的全部邊界方塊(以前圖中那些綠色的方塊)
frontier = PriorityQueue()
PriorityQueue表明它是一個優先隊列,就是說它可以用「代價」自動排序,並每次取出」代價「最低的方塊
frontier.put(start, 0)
隊列裏面呢咱們先存放一個元素,就是咱們的起點
came_from = {}
接下來的的 came_from 是一個從當前方塊到以前的映射,表明路徑的來向
cost_so_far = {}
這裏的 cost_so_far 表明了方塊的「當前代價」
came_from[start] = None cost_so_far[start] = 0
這兩行將起點的 came_from 置空,並將起點的當前代價設置成0,這樣子就能夠保證算法數據的有效性
while not frontier.empty(): current = frontier.get()
接下來,只要 frontier 這個隊列不爲空,循環就會一直執行下去,每一次循環,算法從優先隊列裏抽出代價最低的方塊
if current = goal: break
而後檢測這個方塊是否是終點塊,若是是算法結束,不然繼續執行下去
for next in graph.neighbors(current):
接下來,算法會對這個方塊上下左右的相鄰塊,也就是循環中 next 表示的方塊進行以下操做
new_cost = cost_so_far[current] + graph.cost(current, next)
算法會先去計算這個 next 方塊的「新代價」,它等於以前代價 加上從 current 到 next 塊的代價
因爲咱們用的是網格,因此後半部分是 1
if next not in cost_so_far or new_cost < cost_so_far[next]:
而後只要 next 塊沒有被檢測過,或者 next 當前代價比以前的要低
frontier.put(next, priority)
咱們就直接把他加入到優先隊列,而且這裏的總代價priority等於「當前代價」加上」預估代價「
priority = new_cost + heuristic(goal, next)
預估代價就是以前講到的「曼哈頓距離」
def heuristic(a, b): (x1, y1) = a (x2, y2) = b return abs(x1 - x2) + abs(y1 - y2)
以後程序就會進入下一次循環,重複執行以前的全部步驟
這段程序真的是寫的特別巧妙,可能比較難以理解但是多看幾遍說不定你就忽然靈光乍現了呢
拓展
若是把地圖拓展成網格形式(Grid),由於圖的節點太多,遍歷起來會很是的低效
因而咱們能夠吧網格地圖簡化成 節點更少的路標形式(WayPoints)
而後須要注意的是:這裏任意兩個節點之間的距離就再也不是1了,而是節點之間的實際距離
咱們還能夠用自上而向下分層的方式來存儲地圖
好比這個四叉樹(Quad Tree)
又或者像unity中使用的導航三角網(Navigation Mesh),這樣子算法的速度就會獲得進一步優化
另外,我還推薦redblobgames的教程
各類算法的可視化,以及清楚的看見各類算法的遍歷過程、中間結果
以及各類方法之間的比較,很是的直觀形象,對於算法的理解也頗有幫助。
參考:
[1]周小鏡. 基於改進A算法的遊戲地圖尋徑的研究[D].西南大學,2010.
[2]https://www.redblobgames.com/pathfinding/a-star/introduction.html
[3]https://en.wikipedia.org/wiki/A_search_algorithm