找了份新工做以後,忙的要死,都沒時間鼓搗博客了,深深的感覺到資本家的剝削,端午節連糉子都沒有,天天下班累得跟條鹹魚同樣(可能就是)。html
恰好最近忙裏偷閒,就來寫寫unity在2D下的AStar尋路算法。算法
地圖用untiy的tilemap來貼。dom
大概的效果,沒有去找好看的圖片,將就弄點顏色表示:orm
黑色表示障礙,綠色表示路徑,開頭和結尾也是用的綠色,好懶o(╥﹏╥)ohtm
原理和詳細解釋,仍是參考的這位國外的大神:blog
https://www.redblobgames.com/pathfinding/a-star/introduction.html圖片
解說以下:get
A*算法其實能夠理解爲是貪心算法和廣度優先搜索算法的結合體。博客
廣度優先搜索算法,每次均可以找到最短的路徑,每走一步都會記下起點到當前點的步數,優勢是絕對能找到最短的路徑,缺點就是地圖越大計算量會變得很巨大。it
貪心算法,每次都是走當前點距離終點最近的格子,在沒有障礙的狀況下效率很高,可是若是有障礙的話,就很繞路。
A*算法結合二者,計算當前走過的步數 與 當前點到終點的距離 之和做爲走格子的依據,優勢就是當有障礙物時,能找到最短距離而且計算量沒有廣度優先搜索大,沒有障礙物時,效率和貪心算法同樣高。
其實代碼量沒多少,直接貼出來了,具體也不解釋,看註釋吧,我好懶。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Tilemaps; public class MapBehaviour : MonoBehaviour { public Vector2Int mapSize;//地圖尺寸 public Tilemap tilemap; public Tile normalTile;//白色tile public Tile obstacleTile;//黑色tile public Tile pathTile;//綠色tile public int obstacleCount;//要生成的障礙物數量 public Vector3Int startPos;//起點 public Vector3Int endPos;//終點 private bool hasStartPosSet;//是否設置了起點 private bool hasEndPosSet;//是否設置了終點 private Dictionary<Vector3Int, int> search = new Dictionary<Vector3Int, int>();//要進行的查找任務 private Dictionary<Vector3Int, int> cost = new Dictionary<Vector3Int, int>();//起點到當前點的消耗 private Dictionary<Vector3Int, Vector3Int> pathSave = new Dictionary<Vector3Int, Vector3Int>();//保存回溯路徑 private List<Vector3Int> hadSearch = new List<Vector3Int>();//已經查找過的座標 private List<Vector3Int> obstacle = new List<Vector3Int>();//障礙物座標 private void Start() { CreateNormalTiles(); CreateObstacleTiles(); } private void Update() { if (Input.GetMouseButtonDown(0)) { if (!hasStartPosSet)//第一次點擊設置起點 { startPos = tilemap.WorldToCell(Camera.main.ScreenToWorldPoint(Input.mousePosition)); tilemap.SetTile(startPos, pathTile); hasStartPosSet = true; } else if (!hasEndPosSet)//第二次點擊設置終點 { endPos = tilemap.WorldToCell(Camera.main.ScreenToWorldPoint(Input.mousePosition)); tilemap.SetTile(endPos, pathTile); hasEndPosSet = true; AStarSearchPath(); } else//重置 { hasStartPosSet = false; hasEndPosSet = false; foreach (var item in pathSave) { tilemap.SetTile(item.Key, normalTile); } search.Clear(); cost.Clear(); pathSave.Clear(); hadSearch.Clear(); } } } //建立白色地圖 public void CreateNormalTiles() { for (int i = 0; i < mapSize.x; i++) { for (int j = 0; j < mapSize.y; j++) { Vector3Int position = new Vector3Int(i, j, 0); tilemap.SetTile(position, normalTile); } } } //建立黑色障礙 public void CreateObstacleTiles() { List<Vector3Int> blankTiles = new List<Vector3Int>(); for (int i = 0; i < mapSize.x; i++) { for (int j = 0; j < mapSize.y; j++) { blankTiles.Add(new Vector3Int(i, j, 0)); } } for (int i = 0; i < obstacleCount; i++) { int index = Random.Range(0, blankTiles.Count); Vector3Int obstaclePos = blankTiles[index]; blankTiles.RemoveAt(index); obstacle.Add(obstaclePos); tilemap.SetTile(obstaclePos, obstacleTile); } } //AStar算法查找 public void AStarSearchPath() { //初始化 search.Add(startPos, GetHeuristic(startPos, endPos)); cost.Add(startPos, 0); hadSearch.Add(startPos); pathSave.Add(startPos, startPos); while (search.Count > 0) { Vector3Int current = GetShortestPos();//獲取任務列表裏的最少消耗的那個座標 if (current.Equals(endPos)) break; List<Vector3Int> neighbors = GetNeighbors(current);//獲取當前座標的鄰居 foreach (var next in neighbors) { if (!hadSearch.Contains(next)) { cost.Add(next, cost[current] + 1);//計算當前格子的消耗,其實就是上一個格子加1步 search.Add(next, cost[next] + GetHeuristic(next, endPos));//添加要查找的任務,消耗值爲當前消耗加上當前點到終點的距離 pathSave.Add(next, current);//保存路徑 hadSearch.Add(next);//添加該點爲已經查詢過 } } } if (pathSave.ContainsKey(endPos)) ShowPath(); else print("No road"); } //獲取周圍可用的鄰居 private List<Vector3Int> GetNeighbors(Vector3Int target) { List<Vector3Int> neighbors = new List<Vector3Int>(); Vector3Int up = target + Vector3Int.up; Vector3Int right = target + Vector3Int.right; Vector3Int left = target - Vector3Int.right; Vector3Int down = target - Vector3Int.up; //Up if (up.y < mapSize.y && !obstacle.Contains(up)) { neighbors.Add(up); } //Right if (right.x < mapSize.x && !obstacle.Contains(right)) { neighbors.Add(target + Vector3Int.right); } //Left if (left.x >= 0 && !obstacle.Contains(left)) { neighbors.Add(target - Vector3Int.right); } //Down if (down.y >= 0 && !obstacle.Contains(down)) { neighbors.Add(target - Vector3Int.up); } return neighbors; } //獲取當前位置到終點的消耗 private int GetHeuristic(Vector3Int posA, Vector3Int posB) { return Mathf.Abs(posA.x - posB.x) + Mathf.Abs(posA.y - posB.y); } //獲取任務字典裏面最少消耗的座標 private Vector3Int GetShortestPos() { KeyValuePair<Vector3Int, int> shortest = new KeyValuePair<Vector3Int, int>(Vector3Int.zero, int.MaxValue); foreach (var item in search) { if (item.Value < shortest.Value) { shortest = item; } } search.Remove(shortest.Key); return shortest.Key; } //顯示查找完成的路徑 private void ShowPath() { print(pathSave.Count); Vector3Int current = endPos; while (current != startPos) { Vector3Int next = pathSave[current]; tilemap.SetTile(current, pathTile); current = next; } } }
其實沒什麼難點,主要是理解,每次取出來計算下一步的點,是字典裏面最少消耗值的那個點,而後每一個點的消耗值都是,起點到當前的步數與當前到終點的距離(專業名詞叫曼哈頓距離Manhattan distance2333)之和。
完結。
歡迎交流,轉載註明出處!