JPS(Jump Point Search)寻路及实现代码分析
JPS(Jump Point Search)尋路
參考資料:2018騰訊移動(dòng)游戲技術(shù)評(píng)審標(biāo)準(zhǔn)與實(shí)踐案例
https://blog.csdn.net/yuxikuo_1/article/details/50406651
http://blog.sina.com.cn/s/blog_4a5c75d40102wo5l.html
https://github.com/SkylerAlvarez/JumpPointSearch
文章目錄
- JPS(Jump Point Search)尋路
- 一、什么是Jump Point Search?
- 二、尋路流程
- 一.定義
- 二.規(guī)則
- 三.舉例
- 三、實(shí)現(xiàn)重點(diǎn)代碼分析
一、什么是Jump Point Search?
JPS 又名跳點(diǎn)搜索算法(Jump Point Search),是由澳大利亞兩位教授于 2011年提出的基于 Grid 格子的尋路算法。JPS算法在保留A算法的框架的同時(shí),進(jìn)一步優(yōu)化了 A算法尋找后繼節(jié)點(diǎn)的操作。
個(gè)人理解,A*是尋找所有的當(dāng)前點(diǎn)的鄰居,會(huì)有頻繁的點(diǎn)集合加入和刪除到點(diǎn)集合操作,jps的優(yōu)點(diǎn)是會(huì)根據(jù)當(dāng)前點(diǎn)的方向及當(dāng)前點(diǎn)周圍鄰居的特點(diǎn)進(jìn)行選擇某些特殊的點(diǎn)才執(zhí)行加入和刪除到點(diǎn)集合操作。
JPS類比于KMP算法,有個(gè)共同點(diǎn)在于把重復(fù)多余的計(jì)算通過數(shù)據(jù)的特性省略,kmp中的next數(shù)組是對子字符串的特性的記錄,在匹配時(shí)根據(jù)這些特性跳過多余的計(jì)算。jps也是根據(jù)鄰居點(diǎn)的特性跳過其他多余的點(diǎn)。
這個(gè)也是多數(shù)優(yōu)化算法的一個(gè)方式。
從論文中的圖也可以看出這個(gè)特點(diǎn),M.Time 表示操作 openset 和 closedset 的時(shí)間,G.Time 表示搜索后繼節(jié)點(diǎn)的時(shí)間??梢?A*大約有 58%的時(shí)間在操作 openset 和 closedset,42%時(shí)間在搜索后繼節(jié)點(diǎn);而 JPS 大約 14%時(shí)間在操作 openset 和 closedset,86%時(shí)間在搜索后繼節(jié)點(diǎn)。
二、尋路流程
PS:本文定義無法直接走到斜對角,比如(1,1)點(diǎn)無法直接走到(2,2)點(diǎn),需要的話也可以,要改點(diǎn)邏輯\color{red}{PS:本文定義無法直接走到斜對角,比如(1,1)點(diǎn)無法直接走到(2,2)點(diǎn),需要的話也可以,要改點(diǎn)邏輯}PS:本文定義無法直接走到斜對角,比如(1,1)點(diǎn)無法直接走到(2,2)點(diǎn),需要的話也可以,要改點(diǎn)邏輯
一.定義
1.點(diǎn):當(dāng)前點(diǎn)為:current,鄰居點(diǎn)為neighbor,上一點(diǎn)parent
2.點(diǎn)集合:尋路過程中需要保存有效點(diǎn)的集合,分為可探索點(diǎn)集合openset,已探索點(diǎn)集合closedset。
3.路徑權(quán)值:gCost為起點(diǎn)經(jīng)過其他點(diǎn)到當(dāng)前點(diǎn)的代價(jià)和,hCost為到目標(biāo)點(diǎn)的代價(jià),fCost為當(dāng)前點(diǎn)的與起點(diǎn)終點(diǎn)間價(jià)值的和即fCost=gCost+hCost。
4.強(qiáng)迫鄰居 (forced neighbour):如果點(diǎn) neighbor 是 current 的鄰居,并且點(diǎn) neighbor 的鄰居有阻擋(不可行走的格子),并且從 parent、current、neighbor 的路徑長度比其他任何從 parent到neighbor 且不經(jīng)過 current 的路徑短,其中 parent為路徑中 current 的前一個(gè)點(diǎn),則 neighbor 為 current 的強(qiáng)迫鄰居,current 為 neighbor 的跳點(diǎn))
5.跳點(diǎn)(jump point):(1)如果點(diǎn) y 是起點(diǎn)或目標(biāo)點(diǎn),則 y 是跳點(diǎn),(2)如果 y 有鄰居且是強(qiáng)迫鄰居則 y 是跳點(diǎn), 從上文強(qiáng)迫鄰居的定義來看 neighbor 是強(qiáng)迫鄰居,current 是跳點(diǎn),二者的關(guān)系是伴生的,(3)如果 parent到 y 是對角線移動(dòng),并且 y 經(jīng)過水平或垂直方向移動(dòng)可以到達(dá)跳點(diǎn),則 y 是跳點(diǎn)。
個(gè)人理解:附近有障礙才有強(qiáng)迫鄰居,跳點(diǎn)及強(qiáng)迫鄰居都可以改變當(dāng)前的方向,有強(qiáng)迫鄰居就是跳點(diǎn)??次淖掷斫獠槐?#xff0c;可以看圖,主要特點(diǎn)是在當(dāng)前點(diǎn)旁邊有障礙,并且障礙上方可到達(dá)。就有強(qiáng)迫鄰居。就是圖中標(biāo)黑點(diǎn)部分。
二.規(guī)則
規(guī)則一,JPS 搜索跳點(diǎn)的過程中,如果直線方向(為了和對角線區(qū)分,直線方向代表水平方向、垂直方向,下文所說的直線均為水平方向和垂直方向)、對角線方向都可以移動(dòng),則首先在直線方向搜索跳點(diǎn),再在對角線方向搜索跳點(diǎn)。
規(guī)則二,(1)如果從 parent到current 是直線移動(dòng),n 是 current 的鄰居,若有從 parent到 n 的路徑不經(jīng)過 current且路徑長度小于或等于從 parent經(jīng)過 x 到 n 的路徑,則走到 current 后下一個(gè)點(diǎn)不會(huì)走到 n;(2)如果從 parent(到 current是對角線移動(dòng),n 是 current的鄰居,若有從 parent到n 的路徑不經(jīng)過 current且路徑長度小于從 parent經(jīng)過 current 到 n 的路徑,則走到 current 后下一個(gè)點(diǎn)不會(huì)走到 n。
規(guī)則三,只有跳點(diǎn)才會(huì)加入 openset,因?yàn)樘c(diǎn)會(huì)改變行走方向,而非跳點(diǎn)不會(huì)改變行走方向,最后尋找出來的路徑點(diǎn)也只會(huì)是跳點(diǎn)集合的子集。
繼續(xù)看圖
簡單概括,假如方向是往右上角方向,則判斷右上方,上方,右方,上尋找跳點(diǎn),如果鄰居中有強(qiáng)迫鄰居,當(dāng)前點(diǎn)是跳點(diǎn)。
垂直方向則同理。
三.舉例
5*5 的網(wǎng)格,黑色代表阻擋區(qū),S 為起點(diǎn),E 為終點(diǎn)。JPS 要尋找從 S 到 E 的最短路徑,首先初始化將 S 加入 openset。從 openset 取出 F 值最小的點(diǎn) S,并從 openset 刪除,加入 closedset,S 的當(dāng)前方向?yàn)榭?#xff0c;則沿八個(gè)方向?qū)ふ姨c(diǎn),在該圖中只122有下、右、右下三個(gè)方向可走,但向下遇到邊界,向右遇到阻擋,因此都沒有找到跳點(diǎn),然后沿右下方向?qū)ふ姨c(diǎn),在 G 點(diǎn),根據(jù)上文定義二的第(3)條,parent(G)為 S,praent(G)到 S 為對角線移動(dòng),并且 G 經(jīng)過垂直方向移動(dòng)(向下移動(dòng))可以到達(dá)跳點(diǎn) I,因此 G 為跳點(diǎn) ,將 G 加入 openset。從 openset 取出F 值最小的點(diǎn) G,并從 openset 刪除,加入 closedset,因?yàn)?G 當(dāng)前方向?yàn)閷蔷€方向(從 S 到 G 的方向),因此在右、下、右下三個(gè)方向?qū)ふ姨c(diǎn),在該圖中只有向下可走,因此向下尋找跳點(diǎn),根據(jù)上文定義二的第(2)條找到跳點(diǎn) I,將I 加入 openset。從 openset 取出 F 值最小的點(diǎn) I,并從 openset 刪除,加入closedset,因?yàn)?I 的當(dāng)前方向?yàn)橹本€方向(從 G 到 I 的方向),在 I 點(diǎn)時(shí) I 的左后方不可走且左方可走,因此沿下、左、左下尋找跳點(diǎn),但向下、左下都遇到邊界,只有向左尋找到跳點(diǎn) Q(根據(jù)上文定義二的第(2)條)),因此將 Q 加入openset。從openset取出F值最小的點(diǎn)Q,并從openset刪除,加入closedset,因?yàn)?Q 的當(dāng)前方向?yàn)橹本€方向,Q 的左后方不可走且左方可走,因此沿右、左、左上尋找跳點(diǎn),但向右、左上都遇到邊界,只有向左尋找到跳點(diǎn) E(根據(jù)上文定義二的第(1)條)),因此將 E 加入 openset。從 openset 取出 F 值最小的點(diǎn)E,因?yàn)?E 是目標(biāo)點(diǎn),因此尋路結(jié)束,路徑是 S、G、I、Q、E。
出處:2018騰訊移動(dòng)游戲技術(shù)評(píng)審標(biāo)準(zhǔn)與實(shí)踐案例
三、實(shí)現(xiàn)重點(diǎn)代碼分析
截取重點(diǎn)實(shí)現(xiàn)代碼解析:
//Node包含路徑代價(jià),因?yàn)閛penset每次從中取fCost中最小值得點(diǎn),openset采用堆的結(jié)構(gòu),每次加入點(diǎn)后自動(dòng)堆排序public int gCost;public int hCost;public int fCost{get{return gCost + hCost;}}-----------------------------------------------//比較fCost,C#中的基本類型都提供了默認(rèn)的比較算法,C#可以調(diào)用比較算法為基本類型的數(shù)組進(jìn)行排序。若希望對自建類進(jìn)行比較或排序,那么可以使用IComparable<T>和IComparer<T>接口。openSet需要用到堆類型數(shù)據(jù)public interface IHeapItem<T> : IComparable<T>{int HeapIndex{get; set;}}//自定義比較函數(shù),public int CompareTo(Node nodeToCompare){int compare = fCost.CompareTo(nodeToCompare.fCost);if (compare == 0){compare = hCost.CompareTo(nodeToCompare.hCost);}return -compare;}//二叉堆排序主要功能private void _SortUp(T item){int parentIndex = (item.HeapIndex - 1) / 2;while (true){T parentItem = _items[parentIndex];if (item.CompareTo(parentItem) > 0)_Swap(item, parentItem);elsebreak;parentIndex = (item.HeapIndex - 1) / 2;}}private void _SortDown(T item){while (true){int childLeftIndex = (item.HeapIndex * 2) + 1;int childRightIndex = (item.HeapIndex * 2) + 2;int swapIndex = 0;if (childLeftIndex < _currentItemCount){swapIndex = childLeftIndex;if (childRightIndex < _currentItemCount)if (_items[childLeftIndex].CompareTo(_items[childRightIndex]) < 0)swapIndex = childRightIndex;if (item.CompareTo(_items[swapIndex]) < 0)_Swap(item, _items[swapIndex]);elsereturn;}elsereturn;}}------------------------------------//獲取最短路徑openSet.Add(_startNode);openSetContainer.Add(_startNode);while (openSet.Count > 0){currentNode = openSet.RemoveFirst();openSetContainer.Remove(_startNode);............}//先將起點(diǎn)加入openSet,再取出fCost最小值點(diǎn),即堆第一個(gè)值。//獲取此點(diǎn)的鄰居,//起點(diǎn)則parent點(diǎn)為null,遍歷鄰居非障礙點(diǎn)加入。if (parentNode == null){for (int x = -1; x <= 1; x++){for (int y = -1; y <= 1; y++){if (x == 0 && y == 0)continue;if (IsWalkable(x + currentNode.x, y + currentNode.y)){neighbours.Add(_grid[x + currentNode.x, y + currentNode.y]);}}}}//非起點(diǎn)鄰居點(diǎn)判斷int xDirection = Mathf.Clamp(currentNode.x - parentNode.x, -1, 1);int yDirection = Mathf.Clamp(currentNode.y - parentNode.y, -1, 1);//判斷是否水平方向if (xDirection != 0 && yDirection != 0){//assumes positive direction for variable namingbool neighbourUp = IsWalkable(currentNode.x, currentNode.y + yDirection);bool neighbourRight = IsWalkable(currentNode.x + xDirection, currentNode.y);bool neighbourLeft = IsWalkable(currentNode.x - xDirection, currentNode.y);bool neighbourDown = IsWalkable(currentNode.x, currentNode.y - yDirection);// 當(dāng)前方向上可走點(diǎn)判斷,if (neighbourUp)neighbours.Add(_grid[currentNode.x, currentNode.y + yDirection]);if (neighbourRight)neighbours.Add(_grid[currentNode.x + xDirection, currentNode.y]);if (neighbourUp || neighbourRight)if (IsWalkable(currentNode.x + xDirection, currentNode.y + yDirection))neighbours.Add(_grid[currentNode.x + xDirection, currentNode.y + yDirection]);//是否有強(qiáng)迫鄰居if (!neighbourLeft && neighbourUp)if (IsWalkable(currentNode.x - xDirection, currentNode.y + yDirection))neighbours.Add(_grid[currentNode.x - xDirection, currentNode.y + yDirection]);if (!neighbourDown && neighbourRight)if (IsWalkable(currentNode.x + xDirection, currentNode.y - yDirection))neighbours.Add(_grid[currentNode.x + xDirection, currentNode.y - yDirection]);}else{//y水平方向if (xDirection == 0){if (IsWalkable(currentNode.x, currentNode.y + yDirection)){neighbours.Add(_grid[currentNode.x, currentNode.y + yDirection]);if (!IsWalkable(currentNode.x + 1, currentNode.y))if (IsWalkable(currentNode.x + 1, currentNode.y + yDirection))neighbours.Add(_grid[currentNode.x + 1, currentNode.y + yDirection]);if (!IsWalkable(currentNode.x - 1, currentNode.y))if (IsWalkable(currentNode.x - 1, currentNode.y + yDirection))neighbours.Add(_grid[currentNode.x - 1, currentNode.y + yDirection]);}}else{//x水平方向if (IsWalkable(currentNode.x + xDirection, currentNode.y)){neighbours.Add(_grid[currentNode.x + xDirection, currentNode.y]);if (!IsWalkable(currentNode.x, currentNode.y + 1))neighbours.Add(_grid[currentNode.x + xDirection, currentNode.y + 1]);if (!IsWalkable(currentNode.x, currentNode.y - 1))neighbours.Add(_grid[currentNode.x + xDirection, currentNode.y - 1]);}}}------------------------------------------------------------------------------//根據(jù)可走鄰居,判斷是否滿足跳點(diǎn)條件,假設(shè)可走到鄰居,判斷是否可以到達(dá)跳點(diǎn)。//如果是斜方向,有強(qiáng)迫鄰居,直接返回。if ((!_grid.IsWalkable(currentNode.x - xDirection, currentNode.y) && _grid.IsWalkable(currentNode.x - xDirection, currentNode.y + yDirection)) ||(!_grid.IsWalkable(currentNode.x, currentNode.y - yDirection) && _grid.IsWalkable(currentNode.x + xDirection, currentNode.y - yDirection))){return currentNode;}//遞歸判斷,斜方向,水平垂直方向可走,沒有強(qiáng)迫鄰居,繼續(xù)斜方向?qū)ふ姨c(diǎn)。有則返回當(dāng)前點(diǎn)。Node nextHorizontalNode = _grid.GetNodeFromIndex(currentNode.x + xDirection, currentNode.y);Node nextVerticalNode = _grid.GetNodeFromIndex(currentNode.x, currentNode.y + yDirection);if (_Jump(nextHorizontalNode, currentNode, xDirection, 0) != null || _Jump(nextVerticalNode, currentNode, 0, yDirection) != null){if (!_forced){UnityEngine.Debug.Log(currentNode);Node temp = _grid.GetNodeFromIndex(currentNode.x + xDirection, currentNode.y + yDirection);if (temp != null && _grid.showDebug)UnityEngine.Debug.DrawLine(new Vector3(currentNode.x, 1, currentNode.y), new Vector3(temp.x, 1, temp.y), Color.green, Mathf.Infinity);return _Jump(temp, currentNode, xDirection, yDirection);}else{return currentNode;}}//如果水平方向移動(dòng),沒有強(qiáng)迫鄰居,繼續(xù)查找下一個(gè)點(diǎn)if (xDirection != 0){//if ((_grid.IsWalkable(currentNode.x + xDirection, currentNode.y + 1) && !_grid.IsWalkable(currentNode.x, currentNode.y + 1)) ||(_grid.IsWalkable(currentNode.x + xDirection, currentNode.y - 1) && !_grid.IsWalkable(currentNode.x, currentNode.y - 1))){_forced = true;return currentNode;}}else{if ((_grid.IsWalkable(currentNode.x + 1, currentNode.y + yDirection) && !_grid.IsWalkable(currentNode.x + 1, currentNode.y)) ||(_grid.IsWalkable(currentNode.x - 1, currentNode.y + yDirection) && !_grid.IsWalkable(currentNode.x - 1, currentNode.y))){_forced = true;return currentNode;}}Node nextNode = _grid.GetNodeFromIndex(currentNode.x + xDirection, currentNode.y + yDirection);if (nextNode!= null && _grid.showDebug)UnityEngine.Debug.DrawLine(new Vector3(currentNode.x, 1, currentNode.y),new Vector3(nextNode.x, 1, nextNode.y), Color.green, Mathf.Infinity);return _Jump(nextNode, currentNode, xDirection, yDirection);//設(shè)置返回跳點(diǎn)的cost值,并加入openset。并更新堆排序int newGCost = currentNode.gCost + _GetDistance(currentNode, node);if (newGCost < node.gCost || !openSetContainer.Contains(node)){node.gCost = newGCost;node.hCost = _GetDistance(node, _targetNode);node.parent = currentNode;if (!openSetContainer.Contains(node)){openSetContainer.Add(node);openSet.Add(node);}else{openSet.UpdateItem(node);}}---------------------------------------------------------//判斷距離,因?yàn)閏pu計(jì)算*和+速度比較快,所以在計(jì)算gCost和hCost值通過*和+采取近似值計(jì)算,格子和邊的類比長度為,10,14(邊如果是1,斜邊為1.41....),根據(jù)xy上不同距離,進(jìn)行映射。private int _GetDistance(Node a, Node b){int distX = Mathf.Abs(a.x - b.x);int distY = Mathf.Abs(a.y - b.y);if (distX > distY)return 14 * distY + 10 * (distX - distY);return 14 * distX + 10 * (distY - distX);}//2018騰訊移動(dòng)游戲技術(shù)評(píng)審標(biāo)準(zhǔn)與實(shí)踐案例中提到的jps算法優(yōu)化部分在本項(xiàng)目中未更新總結(jié)
以上是生活随笔為你收集整理的JPS(Jump Point Search)寻路及实现代码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (精华)2020年7月16日 vue F
- 下一篇: 互联网行测笔试题之最头疼的找规律