揭祕在召喚師峽谷中移動路徑選擇邏輯?

在遊戲中,當咱們須要讓角色移動到指定位置時,只須要鼠標輕輕的一點就能夠完成這簡單的步驟,系統會當即尋找離角色最近的一條路線html

但是,這背後的行爲邏輯又有什麼奧祕呢? 你會怎麼寫這個尋路算法呢?算法

通常咱們遇到這種路徑搜索問題,你們首先能夠想到的是廣度優先搜索算法(Breadth First Search)、還有深度優先(Depth First Search)弗洛伊德(Floyd迪傑斯特拉(Dij)等等這些很是著名的路徑搜索算法,可是在絕大多數狀況下這些算法面臨的缺點就暴露了出來:時間複雜度比較高windows

因此,大部分環境裏咱們用到的是一個名叫A* (A star)的搜索算法工具

(點擊圖片瀏覽動圖)優化

說到最短路徑呢,咱們就不得不提到廣度優先遍歷(BFS),它是一個萬能算法,它不僅僅能夠用在 尋路或者搜索的問題上。windows的系統工具:畫板 中的油漆桶就是其比較典型一個的例子spa

這裏對路徑搜索作一個比較簡潔的示例3d

假設咱們是在一個網格上面進行最短路徑的搜索code

咱們只能上下左右移動,不能夠穿越障礙物。算法的目的是爲了能讓你尋找到一條從起點到站點的最短路徑htm

假設每次均可以上下左右朝4個方向進行移動blog

算法在每一輪遍歷後會標記這一輪探索過的方塊稱爲邊界(Frontier,就是這些綠色的方塊。

 (點擊圖片瀏覽動圖)

而後算法呢會循環往復的從這些邊界方塊開始,朝他們上下左右四個方向進行探索,直到算法遍歷到了終點方塊纔會中止。而最短路徑呢就是算法以前一次探索過的路徑。爲了獲得算法探索過的整條路徑呢,咱們能夠在搜索的過程當中順勢記錄下路徑的來向。

好比這裏方塊上的白色箭頭就表明了以前方塊的位置

在每一次探索路徑的時候,咱們要作的也只是額外的記錄下這個信息

要注意,全部探索過的路徑咱們須要將它們標記成灰色,表明它們已經被訪問過「,這樣子算法就不會重複探索已經走過的路徑了。

廣度優先算法顯然能夠幫助咱們找到最短路徑,不過呢它有點傻,由於它對路徑的尋找是沒有方向性的,它會向各個方向探測過去。

最壞的狀況多是找到終點須要遍歷整個地圖,所以很不智能,咱們須要一個更加高效的算法。

就是本次咱們要介紹的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*算法一樣能夠找到正確的最短路徑

最爲關鍵的是,它搜尋的方塊個數明顯比廣度優先遍歷少不少,所以也就更高效。

理解了算法的基本原理後,接下來就是上代碼了,這裏我直接引用redblobgamesPython代碼實現,由於人家實在寫的太好了!

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

點擊關注,第一時間瞭解華爲雲新鮮技術~