unity A*尋路 (三)A*算法
這里我就不解釋A*算法
如果你還不知道A*算法
網上有很多簡單易懂的例子
我發幾個我看過的鏈接
http://www.rzrgm.cn/lipan/archive/2010/07/01/1769420.html
https://zhuanlan.zhihu.com/p/24112879
我這里就當你會A*算法
三角網格的A*算法尋路
需要用到多邊形方法
這里我引入了一個Polygon庫

在一個工具類中調用這個庫文件
如果你想自己寫這些邏輯或者有更好的庫 可以替換
using System.Collections.Generic; using Polygon; namespace AStar { public class AStarTools { /// <summary> /// 判斷點在哪個三角形中 /// </summary> /// <param name="point"></param> /// <param name="allAStarTriangle"></param> /// <returns></returns> public static Triangle GetAStarTriangleByPoint(Ponit point, List<Triangle> allAStarTriangle) { if (allAStarTriangle == null) { return new Triangle(); } Point Point = new Point(point.x, point.z); for (int i = 0; i < allAStarTriangle.Count; i++) { Triangle AStarTriangle = allAStarTriangle[i]; List<Point> pointList = new List<Point>(); pointList.Add(new Point(AStarTriangle.a.x, AStarTriangle.a.z)); pointList.Add(new Point(AStarTriangle.b.x, AStarTriangle.b.z)); pointList.Add(new Point(AStarTriangle.c.x, AStarTriangle.c.z)); if (Utility.CheckPointInPolygon(Point, new Polygon.Polygon(pointList))) { return AStarTriangle; } } return new Triangle(); } } }
算法需要用到一些數據 起點 終點 導航網格信息 等等
我統一保存在一個類中
using UnityEngine; namespace AStar { public class AStarData { /// <summary> /// 開始. /// </summary> public Ponit start; /// <summary> /// 終點. /// </summary> public Ponit end; /// <summary> /// 導航網格信息 /// </summary> public NavMeshInfo navMeshInfo; /// <summary> /// 起點所在的三角形 /// </summary> public Triangle startPlace; /// <summary> /// 終點所在的三角形 /// </summary> public Triangle endPlace; /// <summary> /// 起點新建三角形 /// </summary> public Triangle startAStarTriangle; /// <summary> /// 終點新建三角形 /// </summary> public Triangle endAStarTriangle; public bool Init(Ponit start, Ponit end, NavMeshInfo navMeshInfo) { this.start = start; this.end = end; this.navMeshInfo = navMeshInfo; startAStarTriangle = new Triangle(start, start, start); endAStarTriangle = new Triangle(end, end, end); startPlace = AStarTools.GetAStarTriangleByPoint(start, navMeshInfo.allTriangle); endPlace = AStarTools.GetAStarTriangleByPoint(end, navMeshInfo.allTriangle); if (startPlace == new Triangle()) { Debug.Log("起點不在導航網格信息中"); return false; } if (endPlace == new Triangle()) { Debug.Log("終點不在導航網格信息中"); return false; } return true; } } }
最后是我寫的A*算法
using System.Collections.Generic; using System.Linq; namespace AStar { public delegate void AStarCallback(List<Ponit> list); public class Pathfinding { /// <summary> /// 數據 /// </summary> AStarData aStarData; /// <summary> /// 待計算列表. /// </summary> List<Triangle> openList; /// <summary> /// 關閉列表. /// </summary> List<Triangle> closeList; /// <summary> /// 父級索引. /// </summary> Dictionary<Triangle, Triangle> parentIndexes; /// <summary> /// G值. /// </summary> Dictionary<Triangle, float> triangle_G; /// <summary> /// H值. /// </summary> Dictionary<Triangle, float> triangle_H; /// <summary> /// F值. /// </summary> Dictionary<Triangle, float> triangle_F; /// <summary> /// 回調. /// </summary> AStarCallback callback; public void Start(Ponit start, Ponit end, NavMeshInfo navMeshInfo, AStarCallback callback) { if (callback == null) { return; } aStarData = new AStarData(); if (aStarData.Init(start, end, navMeshInfo) == false) { return; } this.callback = callback; openList = new List<Triangle>(); closeList = new List<Triangle>(); parentIndexes = new Dictionary<Triangle, Triangle>(); triangle_G = new Dictionary<Triangle, float>(); triangle_H = new Dictionary<Triangle, float>(); triangle_F = new Dictionary<Triangle, float>(); Core(); } /// <summary> /// 核心 /// </summary> void Core() { openList.Add(aStarData.startAStarTriangle); //開始尋路 尋路到終點結束 while (!openList.Contains(aStarData.endAStarTriangle) && openList.Count != 0) { Triangle AStarTriangle = GetOpenListMin(); openList.Remove(AStarTriangle); closeList.Add(AStarTriangle); Explore(AStarTriangle); } List<Triangle> list = new List<Triangle>(); Triangle T = aStarData.endAStarTriangle; while (parentIndexes.ContainsKey(T) && parentIndexes[T] != null) { list.Add(T); T = parentIndexes[T]; } Callback(list); } /// <summary> /// 獲取待計算列表中F值最小的一個. /// </summary> /// <returns></returns> Triangle GetOpenListMin() { Triangle AStarTriangle = openList[0]; for (int i = 0; i < openList.Count; i++) { if (GetDictionaryValue(triangle_F, AStarTriangle) > GetDictionaryValue(triangle_F, openList[i])) { AStarTriangle = openList[i]; } } return AStarTriangle; } /// <summary> /// 獲取當前三角形周圍的三角形. /// </summary> /// <param name="current"></param> /// <param name="end"></param> /// <param name="map"></param> void Explore(Triangle current) { //獲取當前三角形所有的鄰邊三角形 List<Triangle> list = GetRuoundAStarTriangle(current, aStarData); for (int i = 0; i < list.Count; i++) { Triangle AStarTriangle = list[i]; //去掉當前三角形 if (AStarTriangle == current) { continue; } //去掉已經關閉的三角形 if (closeList.Contains(AStarTriangle)) { continue; } //如果不在待檢測的集合 則加入待檢測集合 if (!openList.Contains(AStarTriangle)) { SetParentIndexes(AStarTriangle, current); SetDictionaryValue(triangle_G, AStarTriangle, GetG(AStarTriangle, current)); SetDictionaryValue(triangle_H, AStarTriangle, GetH(AStarTriangle)); openList.Add(AStarTriangle); } //如果在待檢測的集合 則判斷是否修改父級 else { float G = GetDictionaryValue(triangle_G, AStarTriangle); float H = GetDictionaryValue(triangle_H, AStarTriangle); float F = GetG(AStarTriangle, current) + GetH(AStarTriangle); if (G + H > F) { SetParentIndexes(AStarTriangle, current); SetDictionaryValue(triangle_G, AStarTriangle, GetG(AStarTriangle, current)); SetDictionaryValue(triangle_H, AStarTriangle, GetH(AStarTriangle)); openList.Remove(AStarTriangle); } } } } /// <summary> /// 獲取G值. /// </summary> /// <param name="grid"></param> /// <param name="parent"></param> /// <returns></returns> float GetG(Triangle grid, Triangle parent) { float distance = Ponit.Distance(grid.centroid, parent.centroid); distance += GetDictionaryValue(triangle_G, parent); return distance; } /// <summary> /// 獲取H值. /// </summary> /// <param name="grid"></param> /// <param name="end"></param> /// <returns></returns> float GetH(Triangle grid) { float distance = Ponit.Distance(grid.centroid, aStarData.end); return distance; } /// <summary> /// 添加父級索引. /// </summary> /// <param name="current"></param> /// <param name="parent"></param> void SetParentIndexes(Triangle current, Triangle parent) { if (parentIndexes.ContainsKey(current)) { parentIndexes[current] = parent; } else { parentIndexes.Add(current, parent); } } /// <summary> /// 回調 /// </summary> /// <param name="listAStarTriangle"></param> void Callback(List<Triangle> listAStarTriangle) { if (callback == null || listAStarTriangle == null) { return; } listAStarTriangle.Reverse(); List<Ponit> list = new List<Ponit>(); //進距離移動 不超過一個三角形 if (listAStarTriangle.Count == 1) { list.Add(aStarData.end); callback(list); return; } for (int i = 0; i < listAStarTriangle.Count; i++) { list.Add(listAStarTriangle[i].centroid); } callback(list); } /// <summary> /// 獲取周圍的三角形 /// </summary> /// <param name="current"></param> /// <returns></returns> public static List<Triangle> GetRuoundAStarTriangle(Triangle current, AStarData aStarData) { //該點為開始點 則獲取該點三角重心來取值 if (current.a == aStarData.start && current.b == aStarData.start) { current = aStarData.startPlace; } //獲取三角形所有的鄰邊三角形 List<Triangle> list = new List<Triangle>(); list.AddRange(GetListAStarTriangle(current.a, aStarData.navMeshInfo.pointIndexes)); list.AddRange(GetListAStarTriangle(current.b, aStarData.navMeshInfo.pointIndexes)); list.AddRange(GetListAStarTriangle(current.c, aStarData.navMeshInfo.pointIndexes)); //去掉重復的三角形 list = list.Distinct().ToList(); if (list.Contains(aStarData.endPlace)) { list.Add(aStarData.endAStarTriangle); } return list; } /// <summary> /// 獲取頂點對應的三角形列表. /// </summary> /// <param name="ponit</param> /// <param name="map"></param> /// <returns></returns> public static List<Triangle> GetListAStarTriangle(Ponit ponit, Dictionary<Ponit, List<Triangle>> pointIndexes) { List<Triangle> list = null; if (pointIndexes == null) { return list; } if (pointIndexes.ContainsKey(ponit)) { list = pointIndexes[ponit]; } return list; } /// <summary> /// 設置字典的值. /// </summary> /// <param name="data"></param> /// <param name="key"></param> /// <param name="value"></param> public static void SetDictionaryValue(Dictionary<Triangle, float> data, Triangle key, float value) { if (data == null) { return; } if (data.ContainsKey(key)) { data[key] = value; } else { data.Add(key, value); } } /// <summary> /// 獲取字典的值. /// </summary> /// <param name="data"></param> /// <param name="key"></param> /// <returns></returns> public static float GetDictionaryValue(Dictionary<Triangle, float> data, Triangle key) { if (data == null) { return 0; } if (!data.ContainsKey(key)) { data.Add(key, 0); } return data[key]; } } }
我們創建一個Capsule游戲對象
讓他成為我們需要移動的主角
掛上簡單的移動代碼
using UnityEngine; using System.Collections; using System.Collections.Generic; using AStar; public delegate void CharacterMoveCallback(); /// <summary> /// 角色移動 /// </summary> public class CharacterMove : MonoBehaviour { /// <summary> /// 目標 /// </summary> public Vector3 target; /// <summary> /// 速度 /// </summary> public float speed = 2; /// <summary> /// 目標距離 /// </summary> public float distance = 0.05f; /// <summary> /// 移動經過的點 /// </summary> List<Vector3> points; /// <summary> /// 回調 /// </summary> CharacterMoveCallback callback; private void Awake() { target = transform.position; } public void Move(Ponit vector3, CharacterMoveCallback callback = null) { target = Ponit.Convert(vector3); this.callback = callback; } public void Move(List<Ponit> points, CharacterMoveCallback callback = null) { this.points = new List<Vector3>(); for (int i = 0; i < points.Count; i++) { this.points.Add(Ponit.Convert(points[i])); } this.callback = callback; GetTarget(); } void GetTarget() { if (points != null && points.Count >= 1) { target = points[0]; points.Remove(points[0]); Debug.Log("前進點 :" + target); } } void Callback() { if (callback != null) { callback(); callback = null; } } void Update() { //當前位置與目標距離小于間距 移動停止 if (Vector3.Distance(target, transform.position) < distance) { Callback(); return; } //獲取移動向量; Vector3 vector = target - transform.position; vector = vector.normalized; //移動 transform.position += vector * Time.deltaTime * speed; //如果到達目標點則替換下一個目標 if (Vector3.Distance(target, transform.position) < distance) { GetTarget(); } } }
再掛上一個簡單的開始測試代碼
using UnityEngine; using AStar; using System.Collections.Generic; public class test : MonoBehaviour { NavMeshInfo navMeshInfo; void Start() { NavMeshLoad navMeshLoad = new NavMeshLoad(); navMeshInfo = navMeshLoad.Load(Application.dataPath + "/AStar/obj/test.obj"); } private void Update() { if (Input.GetMouseButtonUp(0)) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit;// if (Physics.Raycast(ray, out hit))//函數是對射線碰撞的檢測 { Vector3 Point = hit.point;//得到碰撞點的坐標 Debug.Log("開始點: " + transform.position); Debug.Log("目標點: " + Point); Debug.Log("目標點對象: " + hit.transform.name); Vector3 start = transform.position; Vector3 end = Point; NavMeshHit tmpNavMeshHit; if (NavMesh.SamplePosition(start, out tmpNavMeshHit, 5.0f, NavMesh.AllAreas)) { start = tmpNavMeshHit.position; } Debug.Log("修改后 開始點: " + start); StartNavigation(start, end, navMeshInfo); } } } public void StartNavigation(Vector3 start, Vector3 end, NavMeshInfo navMeshInfo) { if (navMeshInfo == null) { return; } Pathfinding pathfinding = new Pathfinding(); Ponit startPonit = new Ponit(start); Ponit endPonit = new Ponit(end); pathfinding.Start(startPonit, endPonit, navMeshInfo, (List<Ponit> list) => { CharacterMove controller = GetComponent<CharacterMove>(); controller.Move(list, () => { Debug.Log("尋路結束"); }); }); } }
運行 點擊地面 就可以看到Capsule尋路自動行走了
工程下載:
鏈接: https://pan.baidu.com/s/1qY_zUrqIHB6W4K8wC3PxWQ 密碼: iipb

浙公網安備 33010602011771號