第一次翻閱A星算法的文章,是爲了弄清楚A星在遊戲開發中的地位。當是在腦海中沒有造成它的算法模型,也苦於沒有一個自定義軌跡且又能展現每一步的細節的demo,因而本身動手寫了一個測試demo。後來又由於效率的問題接觸到了JPS,因而又實現了一版JPS的邏輯,後分別整理成文章發佈。html
這期間大約經歷了一個月時間,這段時間有不少人給我講,這些內容網上有不少,不必費那麼大的功夫。
但~ 我以爲,作一個領域的研究要有錙銖必較的心態,這纔是作程序開發本該有的素養。
從市場的角度來想,這個行業會不斷的有新鮮血液注入,因此翻閱的需求就一直存在,可否被看到可能只是一個機率的問題。git
但~ 在其餘方面,我也收穫了不少,好比我的網站的創建,博客的編輯發佈,以及找到了適合本身寫博文的工具鏈……github
Jps,Jump Point Search,跳點搜索,也有人稱之爲「拐點尋路」。Jps可追溯到2011年,由兩位澳大利亞的教授提出,有興趣的能夠翻閱一下原做者論文,github Harabor, Daniel Damir, and Alban Grastien. "Online Graph Pruning for Pathfinding On Grid Maps." AAAI. 2011.算法
Jps在A Star算法模型的基礎之上,優化了搜索後繼節點的操做。A星的處理是把周邊能搜索到的格子,加進OpenList,而後在OpenList中彈出最小值……。JPS也是這樣的操做,但相對於A星來講,JPS操做OpenList的次數不多,它會先用一種更高效的方法來搜索須要加進OpenList的點,而後在OpenList中彈出最小值……工具
先看兩個圖來對A星和JPS的差別有個簡單的認識。gitlab
M.Time 表示操做 openset 和 closedset 的時間
G.Time 表示搜索後繼節點的時間
A*大約有 58%的時間在操做 openset 和 closedset,42%時間在搜索後繼節點
JPS 大約 14%時間在操做 openset 和 closedset,86%時間在搜索後繼節點。測試
到這裏咱們已經知道若是,JPS保留了一些A星的算法模型,因此,在理解A星算法模型的基礎之上,再來閱讀JPS的算法模型,可能會事半功倍。
若是你還不理解A星的算法模型,能夠是這翻閱如下幾個連接。
維基百科-A* search algorithm
A星尋路算法介紹-莫水千流-博客園
A Star Algorithm總結與實現
A Star算法總結與實現(附Demo)優化
尋路過程當中須要保存有效點的集合,分爲可探索點集合openList,已探索點集合closeList網站
同A星的概念 g爲起點通過其餘點到當前點的代價和,h爲到目標點的代價,f爲當前點的與起點終點間價值的和即f=g+h。.net
節點 x 的8個鄰居中有障礙,且 x 的父節點 p 通過x 到達 n 的距離代價比不通過 x 到達的 n 的任意路徑的距離代價小,則稱 n 是 x 的強迫鄰居。
乍一看以爲這個定義有點難理解,舉一個簡單的栗子,下面的邏輯是代碼邏輯斷定強迫linkup。
在JPS的搜索中,強迫鄰居的判斷能夠分爲兩種,一是水平搜索方向上,一是對角搜索方向上。
先介紹水平方向上,以下圖(7,10)爲起點,向右進行橫向搜索。當搜索到(9,10)時,檢測到(9,11)是障礙點,(10,11)是可行走點,所以(9,10)會被認定爲跳躍點,而(10,11)是(9,10)的強迫鄰居。
同理,(9,9)是障礙點,(10,9)是可行走點,所以(9,10)會被認爲是跳躍點,(10,9)是(9,10)的強迫鄰居。
再就是對角方向上的搜索,(7,10)是搜索起點,對右下角的(8,9)進行判斷。
(8,9)左側(7,9)是障礙點且(8,8)是可行走點的狀況下,若(7,8)是可行走點,則認爲(7,8)就是強迫鄰居。
同理,若(8,10)是障礙點,(9,9)是可行走點的狀況下,若是(9,10)是可行走點 則認爲(9,10)是強迫鄰居。
簡而言之,其實強迫鄰居的判斷就是兩種狀況,一是橫向判斷,一是對角判斷。
通俗一點的講就是在路徑上改變移動方向的點就是跳躍點。
以淺藍色爲起點,深藍色爲終點。有透明度的格子表明該格子被搜索過(有可能會被重複搜索),有FGH值的格子表明有跳躍點(終點會被認爲是一個特殊的跳躍點)。
起點與終點之間,無障礙狀況下
起點與終點之間,有直線障礙的狀況下
起點與終點之間有U型障礙的狀況下
大體的流程應該就是這樣
JPS算法裏只有跳點纔會被加入openlist裏,排除了大量沒必要要的點,最後找出來的最短路徑也是由跳點組成。這也是 JPS/JPS+ 高效的主要緣由。
橫向縱向的格子的單位消耗爲10,對角單位消耗爲14。
定義一個OpenList,用於存儲和搜索當前最小值的格子。
定義一個CloseList,用於標記已經處理過的格子,以防止重複搜索。
def 獲取鄰居點 if 當前點是起點 返回當前點九宮格內的非障礙點 elseif 當前點與父節點是對角向 判斷並添加相對位置右方的鄰居點 判斷並添加相對位置下方的鄰居點 判斷並添加相對位置對角的鄰居點 判斷並添加相對位置左下角的強迫鄰居 判斷並添加相對位置左上角的強迫鄰居 elseif 當前點與其父節點是橫向 判斷並添加相對位置右方的鄰居點 判斷並添加相對位置上方的強迫鄰居 判斷並添加相對位置下方的強迫鄰居 elseif 當前點與父節點是縱向 同橫向邏輯,判斷並處理下方,左右向強迫鄰居 def 遞歸尋找跳躍點 if 傳入點是終點 返回終點 if 傳入朝向是對角向 if 傳入點存在強迫鄰居 返回此傳入點 if (遞歸尋找跳躍點 傳入點:橫向+1 朝向:橫向)結果不爲空 返回此傳入點 if (遞歸尋找跳躍點 傳入點:縱向+1 朝向:縱向)結果不爲空 返回此傳入點 elseif 橫向 if 上下方有強迫鄰居 返回此傳入點 elseif 縱向 if 左右方有強迫鄰居 返回此傳入點 返回 遞歸尋找跳躍點 傳入點:橫向+1,縱向+1 朝向 對角 def Main 起點加進OpenList中 While(OpenList.Count > 0): 從OpenList中取出F值最小的點並設置爲當前點 把當前點加進CloseList 鄰居點s = 獲取鄰居點(當前點) for 鄰居點s 跳躍點 = 遞歸尋找跳躍點(鄰居點) if 跳躍點再也不CloseList中 計算並設置當前點與跳躍點的G值 計算並設置當前點與跳躍點的H值 計算並設置跳躍點的F值 將當前點設置爲跳躍點的父節點 若是鄰居點在OpenList中 計算當前值的G與該鄰居點的G值 若是G值比該鄰居點的G值小 將當前點設置爲該鄰居點的父節點 更新該鄰居點的GF值 若不在 計算並設置當前點與該鄰居點的G值 計算並設置當前點與該鄰居點的H值 計算並設置該鄰居點的F值 將當前點設置爲該鄰居點的父節
public List<Point> GetNeighbors(Point point) { var points = new List<Point>(); Point parent = point.ParentPoint; if (parent == null) { //獲取此點的鄰居 //起點則parent點爲null,遍歷鄰居非障礙點加入。 for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { if (x == 0 && y == 0) continue; if (IsWalkable(x + point.X, y + point.Y)) { points.Add(new Point(x + point.X, y + point.Y)); } } } return points; } //非起點鄰居點判斷 int xDirection = Mathf.Clamp(point.X - parent.X, -1, 1); int yDirection = Mathf.Clamp(point.Y - parent.Y, -1, 1); if (xDirection != 0 && yDirection != 0) { //對角方向 bool neighbourForward =IsWalkable(point.X, point.Y + yDirection); bool neighbourRight =IsWalkable(point.X + xDirection, point.Y); bool neighbourLeft =IsWalkable(point.X - xDirection, point.Y); bool neighbourBack =IsWalkable(point.X, point.Y - yDirection); if (neighbourForward) { points.Add(new Point(point.X, point.Y + yDirection)); } if (neighbourRight) { points.Add(new Point(point.X + xDirection, point.Y)); } if ((neighbourForward || neighbourRight) && IsWalkable(point.X + xDirection, point.Y + yDirection)) { points.Add(new Point(point.X + xDirection, point.Y + yDirection)); } //強迫鄰居的處理 if (!neighbourLeft && neighbourForward) { if (IsWalkable(point.X - xDirection, point.Y + yDirection)) { points.Add(new Point(point.X - xDirection, point.Y + yDirection)); } } if (!neighbourBack && neighbourRight) { if (IsWalkable(point.X + xDirection, point.Y - yDirection)) { points.Add(new Point(point.X + xDirection, point.Y - yDirection)); } } } else { if (xDirection == 0) { //縱向 if (IsWalkable(point.X, point.Y + yDirection)) { points.Add(new Point(point.X, point.Y + yDirection)); //強迫鄰居 if (!IsWalkable(point.X + 1, point.Y) &&IsWalkable(point.X + 1, point.Y + yDirection)) { points.Add(new Point(point.X + 1, point.Y + yDirection)); } if (!IsWalkable(point.X - 1, point.Y) &&IsWalkable(point.X - 1, point.Y + yDirection)) { points.Add(new Point(point.X - 1, point.Y + yDirection)); } } } else { //橫向 if (IsWalkable(point.X + xDirection, point.Y)) { points.Add(new Point(point.X, point.Y + yDirection)); //強迫鄰居 if (!IsWalkable(point.X, point.Y + 1) &&IsWalkable(point.X + xDirection, point.Y + 1)) { points.Add(new Point(point.X + xDirection, point.Y + 1)); } if (!IsWalkable(point.X, point.Y - 1) &&IsWalkable(point.X + xDirection, point.Y - 1)) { points.Add(new Point(point.X + xDirection, point.Y - 1)); } } } } return points; }
private Point Jump(int curPosx, int curPosY, int xDirection, int yDirection, int depth, Point end) { if (!IsWalkable(curPosx, curPosY)) return null; CallSearch(curPosx, curPosY); //遞歸最大深度 || 搜索到終點 if (depth == 0 || (end.X == curPosx && end.Y == curPosY)) return new Point(curPosx, curPosY); //對角向 if (xDirection != 0 && yDirection != 0) { if ((IsWalkable(curPosx + xDirection, curPosY - yDirection) && !IsWalkable(curPosx, curPosY - yDirection)) || (IsWalkable(curPosx - xDirection, curPosY + yDirection) && !IsWalkable(curPosx - xDirection, curPosY))) { return new Point(curPosx, curPosY); } //橫向遞歸尋找強迫鄰居 if (Jump(curPosx + xDirection, curPosY, xDirection, 0, depth - 1, end) != null) { return new Point(curPosx, curPosY); } //縱向向遞歸尋找強迫鄰居 if (Jump(curPosx, curPosY + yDirection, 0, yDirection, depth - 1, end) != null) { return new Point(curPosx, curPosY); } } else if (xDirection != 0) { //橫向 if ((IsWalkable(curPosx + xDirection, curPosY + 1) && !IsWalkable(curPosx, curPosY + 1)) || (IsWalkable(curPosx + xDirection, curPosY - 1) && !IsWalkable(curPosx, curPosY - 1))) { return new Point(curPosx, curPosY); } } else if (yDirection != 0) { //縱向 if ((IsWalkable(curPosx + 1, curPosY + yDirection) && !IsWalkable(curPosx + 1, curPosY)) || (IsWalkable(curPosx - 1, curPosY + yDirection) && !IsWalkable(curPosx - 1, curPosY))) { return new Point(curPosx, curPosY); } } return Jump(curPosx + xDirection, curPosY + yDirection, xDirection, yDirection, depth - 1, end); }
protected int CalcG(Point start, Point point) { int distX = Math.Abs(point.X - start.X); int distY = Math.Abs(point.Y - start.Y); int G = 0; if (distX > distY) G = 14 * distY + 10 * (distX - distY); else G = 14 * distX + 10 * (distY - distX); int parentG = point.ParentPoint != null ? point.ParentPoint.G : 0; return G + parentG; } protected int CalcH(Point end, Point point) { int step = Math.Abs(point.X - end.X) + Math.Abs(point.Y - end.Y); return step * 10; }