參考鏈接:
https://www.cnblogs.com/yangyxd/articles/5447889.html
一.原理
1.將場景簡化,分割為一個個正方形格子,這些格子稱之為節點(node),從一個節點到另一個節點的距離稱之為代價(cost)。一個節點與水平/垂直方向的相鄰節點的代價是1,與對角節點的代價是1.4。這里引用公式f = g + h,f表示該節點的總代價,g表示該節點與上一路徑節點的代價,h表示該節點與目標節點的代價。
2.需要兩個列表,開啟列表(openList)和關閉列表(closeList)。開啟列表用來記錄需要考慮的節點,關閉列表用來記錄不會再考慮的節點。
3.在開啟列表中添加起始節點。
4.在開啟列表中找到總代價最低的節點,然后從開啟列表中移除該節點,從關閉列表中添加該節點,把與該節點相鄰的可通行的節點添加到開啟列表,並且更新這些節點的代價。
5.循環第四步,如果當前節點等於目標節點,則退出循環。
二.實現
FindWayNode.cs
1 using UnityEngine; 2 3 public class FindWayNode { 4 5 public bool isObstacle;//是否是障礙物 6 public Vector3 scenePos;//場景位置 7 public int x, y;//坐標 8 9 public int gCost;//與起始點的距離 10 public int hCost;//與目標點的距離 11 public int fCost { 12 get { return gCost + hCost; } 13 }//總距離 14 15 public FindWayNode parentNode;//父節點 16 17 public FindWayNode(bool isObstacle, Vector3 scenePos, int x, int y) 18 { 19 this.isObstacle = isObstacle; 20 this.scenePos = scenePos; 21 this.x = x; 22 this.y = y; 23 } 24 }
FindWayGrid.cs
1 using System.Collections.Generic; 2 using UnityEngine; 3 4 public class FindWayGrid { 5 6 public int width;//格子水平方向個數 7 public int height;//格子垂直方向個數 8 public float nodeLength;//格子長度 9 10 private FindWayNode[,] findWayNodes;//格子數組 11 private float halfNodeLength;//格子長度的一半 12 private Vector3 startPos;//場景坐標起點 13 14 public FindWayGrid(int width, int height, float nodeLength = 1f) 15 { 16 this.width = width; 17 this.height = height; 18 this.nodeLength = nodeLength; 19 20 findWayNodes = new FindWayNode[width, height]; 21 halfNodeLength = nodeLength / 2; 22 startPos = new Vector3(-width / 2 * nodeLength + halfNodeLength, 0, -height / 2 * nodeLength + halfNodeLength); 23 24 for (int x = 0; x < width; x++) 25 { 26 for (int y = 0; y < height; y++) 27 { 28 Vector3 pos = CoordinateToScenePos(x, y); 29 findWayNodes[x, y] = new FindWayNode(false, pos, x, y); 30 } 31 } 32 } 33 34 //坐標轉場景坐標 35 public Vector3 CoordinateToScenePos(int x, int y) 36 { 37 Vector3 pos = new Vector3(startPos.x + x * nodeLength, startPos.y, startPos.z + y * nodeLength); 38 return pos; 39 } 40 41 //根據場景坐標獲取節點 42 public FindWayNode GetNode(Vector3 pos) 43 { 44 int x = (int)(Mathf.RoundToInt(pos.x - startPos.x) / nodeLength); 45 int y = (int)(Mathf.RoundToInt(pos.z - startPos.z) / nodeLength); 46 x = Mathf.Clamp(x, 0, width - 1); 47 y = Mathf.Clamp(y, 0, height - 1); 48 return GetNode(x, y); 49 } 50 51 //根據坐標獲取節點 52 public FindWayNode GetNode(int x, int y) 53 { 54 return findWayNodes[x, y]; 55 } 56 57 //獲取相鄰節點列表 58 public List<FindWayNode> GetNearbyNodeList(FindWayNode node) 59 { 60 List<FindWayNode> list = new List<FindWayNode>(); 61 for (int i = -1; i <= 1; i++) 62 { 63 for (int j = -1; j <= 1; j++) 64 { 65 if (i == 0 && j == 0) 66 { 67 continue; 68 } 69 int x = node.x + i; 70 int y = node.y + j; 71 if (x >= 0 && x < width && y >= 0 && y < height) 72 { 73 list.Add(findWayNodes[x, y]); 74 } 75 } 76 } 77 return list; 78 } 79 80 //獲取兩個節點之間的距離 81 int GetDistance(FindWayNode nodeA, FindWayNode nodeB) 82 { 83 int countX = Mathf.Abs(nodeA.x - nodeB.x); 84 int countY = Mathf.Abs(nodeA.y - nodeB.y); 85 if (countX > countY) 86 { 87 return 14 * countY + 10 * (countX - countY); 88 } 89 else 90 { 91 return 14 * countX + 10 * (countY - countX); 92 } 93 } 94 95 //找出起點到終點的最短路徑 96 public List<FindWayNode> FindWay(Vector3 startPos, Vector3 endPos) 97 { 98 FindWayNode startNode = GetNode(startPos); 99 FindWayNode endNode = GetNode(endPos); 100 101 List<FindWayNode> openList = new List<FindWayNode>(); 102 List<FindWayNode> closeList = new List<FindWayNode>(); 103 openList.Add(startNode); 104 105 while (openList.Count > 0) 106 { 107 FindWayNode nowNode = openList[0]; 108 109 //選擇花費最低的 110 for (int i = 0; i < openList.Count; i++) 111 { 112 if (openList[i].fCost <= nowNode.fCost && 113 openList[i].hCost < nowNode.hCost) 114 { 115 nowNode = openList[i]; 116 } 117 } 118 119 openList.Remove(nowNode); 120 closeList.Add(nowNode); 121 122 //找到目標節點 123 if (nowNode == endNode) 124 { 125 return GeneratePath(startNode, endNode); 126 } 127 128 List<FindWayNode> nearbyNodeList = GetNearbyNodeList(nowNode); 129 for (int i = 0; i < nearbyNodeList.Count; i++) 130 { 131 FindWayNode node = nearbyNodeList[i]; 132 //如果是牆或者已經在關閉列表中 133 if (node.isObstacle || closeList.Contains(node)) 134 { 135 continue; 136 } 137 //計算當前相鄰節點與開始節點的距離 138 int gCost = nowNode.gCost + GetDistance(nowNode, node); 139 //如果距離更小,或者原來不在打開列表 140 if (gCost < node.gCost || !openList.Contains(node)) 141 { 142 //更新與開始節點的距離 143 node.gCost = gCost; 144 //更新與結束節點的距離 145 node.hCost = GetDistance(node, endNode); 146 //更新父節點為當前選定的節點 147 node.parentNode = nowNode; 148 //加入到打開列表 149 if (!openList.Contains(node)) 150 { 151 openList.Add(node); 152 } 153 } 154 } 155 } 156 157 return null; 158 } 159 160 //生成路徑 161 public List<FindWayNode> GeneratePath(FindWayNode startNode, FindWayNode endNode) 162 { 163 List<FindWayNode> nodeList = new List<FindWayNode>(); 164 if (endNode != null) 165 { 166 FindWayNode tempNode = endNode; 167 while (tempNode != startNode) 168 { 169 nodeList.Add(tempNode); 170 tempNode = tempNode.parentNode; 171 } 172 nodeList.Reverse();//反轉路徑 173 } 174 return nodeList; 175 } 176 }
TestFindWay.cs
1 using UnityEngine; 2 using System.Collections; 3 using System.Collections.Generic; 4 5 public class TestFindWay : MonoBehaviour { 6 7 public Transform startTra;//起點tra 8 public Transform endTra;//終點tra 9 public Transform floorTra;//地板tra 10 11 public GameObject obstacleGridPrefab;//障礙物格子 12 public GameObject pathGridPrefab;//路徑格子 13 public LayerMask obstacleLayer;//障礙物所在的層 14 15 private FindWayGrid findWayGrid; 16 17 private GameObject obstacleRootGo;//障礙物格子的父go 18 private GameObject pathRootGo;//路徑格子的父go 19 private List<GameObject> pathGridGoList;//路徑格子go列表 20 21 void Start () 22 { 23 MeshFilter meshFilter = floorTra.GetComponent<MeshFilter>(); 24 int width = Mathf.CeilToInt(meshFilter.mesh.bounds.size.x) * (int)floorTra.localScale.x; 25 int height = Mathf.CeilToInt(meshFilter.mesh.bounds.size.z) * (int)floorTra.localScale.z; 26 27 findWayGrid = new FindWayGrid(width, height); 28 29 obstacleRootGo = new GameObject("ObstacleRoot"); 30 pathRootGo = new GameObject("PathRoot"); 31 pathGridGoList = new List<GameObject>(); 32 33 ShowObstacle(); 34 } 35 36 void Update () 37 { 38 List<FindWayNode> nodeList = findWayGrid.FindWay(startTra.position, endTra.position); 39 if (nodeList != null) 40 { 41 ShowPath(nodeList); 42 } 43 } 44 45 //展示路徑 46 public void ShowPath(List<FindWayNode> list) 47 { 48 for (int i = 0; i < list.Count; i++) 49 { 50 if (i < pathGridGoList.Count) 51 { 52 pathGridGoList[i].transform.position = list[i].scenePos; 53 pathGridGoList[i].SetActive(true); 54 } 55 else 56 { 57 GameObject go = Instantiate(pathGridPrefab); 58 go.transform.SetParent(pathRootGo.transform); 59 go.transform.position = list[i].scenePos; 60 pathGridGoList.Add(go); 61 } 62 } 63 64 for (int i = list.Count; i < pathGridGoList.Count; i++) 65 { 66 pathGridGoList[i].SetActive(false); 67 } 68 } 69 70 //展示障礙物 71 public void ShowObstacle() 72 { 73 int width = findWayGrid.width; 74 int height = findWayGrid.height; 75 float halfNodeLength = findWayGrid.nodeLength / 2; 76 77 for (int x = 0; x < width; x++) 78 { 79 for (int y = 0; y < height; y++) 80 { 81 FindWayNode node = findWayGrid.GetNode(x, y); 82 bool isObstacle = Physics.CheckSphere(node.scenePos, halfNodeLength, obstacleLayer); 83 if (isObstacle) 84 { 85 GameObject go = GameObject.Instantiate(obstacleGridPrefab, node.scenePos, Quaternion.identity) as GameObject; 86 go.transform.SetParent(obstacleRootGo.transform); 87 } 88 node.isObstacle = isObstacle; 89 } 90 } 91 } 92 }
三.使用
把TestFindWay.cs掛上,然后對public變量進行拖拽賦值即可。效果如下:

