Unity廣度尋路演算法
阿新 • • 發佈:2021-01-11
由於專案中的連線點是邏輯上的點,採用的是圖的連線方式,所以採用廣度尋路的方法。路徑點命名規則如下:
用逗號隔開,第一位代表本名,後面跟的是可尋路徑點名字。
尋路方法如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FindWayPath { class Node { public string name;//真名 public string findName;//尋路名 public List<string> findWay;//尋路點 public Vector3 pos;//座標點 } private Dictionary<string, Node> allNode; public FindWayPath(Transform _tran) { allNode = new Dictionary<string, global::FindWayPath.Node>(); foreach (Transform idx in _tran) { Node _n = new Node(); _n.name = idx.name; _n.pos = idx.position; //拆分獲得尋路名與目標點 var _list = idx.name.Split(','); _n.findWay = new List<string>(); for (var i = 0; i < _list.Length; i++) { if (i == 0) _n.findName = _list[i]; else _n.findWay.Add(_list[i]); } allNode.Add(_n.findName,_n); } //Debug.LogError(allNode.Count); //foreach (var idx in allNode) Debug.Log(idx.Key); } private Node GetNodeInPath(string _key) { Node _value = null; allNode.TryGetValue(_key, out _value); return _value; } /// <summary> /// 獲得真名 /// </summary> public string GetPathName(string _key) { return GetNodeInPath(_key).name; } /// <summary> /// 獲得真實座標:通過假名 /// </summary> public Vector3 GetPathPos(string _key) { return GetNodeInPath(_key).pos; } #region 廣度尋路方法 /// <summary> /// 尋找路徑 /// </summary> public List<string> GetPathWayNew(string _start, string _end) { BreadthFindWay _findWay = new BreadthFindWay(allNode); _findWay.StartFind(_start, _end); return _findWay.endPath; } #endregion class BreadthFindWay { private Dictionary<string, Node> allNode; //廣度路徑表 private List<List<string>> allPath = new List<List<string>>(); // 最終路線圖 private List<string> endList = new List<string>(); public List<string> endPath = new List<string>(); //比照表 private List<string> bPath = new List<string>(); //是否結束 private bool isEnd = false; public BreadthFindWay(Dictionary<string,Node> _allNode) { allNode = _allNode; } /// <summary> /// 開始尋路 /// </summary> public void StartFind(string _start, string _end) { Node _nStart = GetNodeInPath(_start); var _path = new List<string>(); _path.Add(_nStart.findName); allPath.Add(_path); BreadthFind(_end); //將endList轉換為endPath foreach (var idx in endList) { endPath.Add(GetNodeInPath(idx).name); } Debug.LogError("尋路結束轉換"); } /// <summary> /// 廣度尋路 /// </summary> private void BreadthFind(string _end) { var _move = 0; while (!isEnd && _move < 10000) { _move++; var _length = allPath.Count; for (var _i = 0; _i < _length; _i++) { var _path = allPath[_i]; var _endNode = GetNodeInPath(_path[_path.Count-1]); var _num = 0; foreach (var idx in _endNode.findWay) { if (!bPath.Contains(idx)) { Debug.Log(idx); bPath.Add(idx); if (idx == _end) { endList = _path; endList.Add(_end); isEnd = true; Debug.Log("找到了終點"); return; } _num++; if (_num == 0) _path.Add(idx); else { var _list = new List<string>(); foreach (var _aa in _path) _list.Add(_aa); _list.Add(idx); allPath.Add(_list); } } } } } Debug.LogError("尋路超過10000步失敗"); } private Node GetNodeInPath(string _key) { Node _value = null; allNode.TryGetValue(_key, out _value); return _value; } } }
測試方法如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// 尋路測試 /// </summary> public class FindTest : MonoBehaviour { public string start = "hire"; public string end = "1"; public FindWayPath path; private MoveAI moveAI; //尋路路線 private List<string> findWayName = new List<string>(); public Transform findway; // Start is called before the first frame update void Start() { moveAI = gameObject.GetComponent<MoveAI>(); path = new FindWayPath(findway); findWayName.Clear(); findWayName = path.GetPathWayNew(start, end); //列印 var _str = ""; foreach (var idx in findWayName) _str += idx + "=>"; Debug.LogError(_str); // } // Update is called once per frame void Update() { if (moveAI.isArrived && findWayName.Count>0) { var _str = findWayName[0]; moveAI.ChangeTarget(findway.Find(_str).position); findWayName.Remove(_str); } } }
最終求出結果:
-------------------------2020/12/30-------------------------
上述尋路只能找出邏輯上的第一個點,而不能找到邏輯上的最近點。
優化方法如下:
1,加上判斷條件距離,以第一個找到的終點為判斷距離L,大於L的都被捨棄;
2,找到新的抵達終點路線後進行L大小判斷,如果新的比較少則替換;
3,對於找到同一個點的情況下進行本身距離判斷,並將路徑更新為較小的那個。
最終程式碼如下:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FindWayPath { class Node { public string name;//真名 public string findName;//尋路名 public List<string> findWay;//尋路點 public Vector3 pos;//座標點 } private Dictionary<string, Node> allNode; public FindWayPath(Transform _tran) { allNode = new Dictionary<string, global::FindWayPath.Node>(); foreach (Transform idx in _tran) { Node _n = new Node(); _n.name = idx.name; _n.pos = idx.position; //拆分獲得尋路名與目標點 var _list = idx.name.Split(','); _n.findWay = new List<string>(); for (var i = 0; i < _list.Length; i++) { if (i == 0) _n.findName = _list[i]; else _n.findWay.Add(_list[i]); } allNode.Add(_n.findName, _n); } //Debug.LogError(allNode.Count); //foreach (var idx in allNode) Debug.Log(idx.Key); } private Node GetNodeInPath(string _key) { Node _value = null; allNode.TryGetValue(_key, out _value); return _value; } /// <summary> /// 獲得真名 /// </summary> public string GetPathName(string _key) { return GetNodeInPath(_key).name; } /// <summary> /// 獲得真實座標:通過假名 /// </summary> public Vector3 GetPathPos(string _key) { return GetNodeInPath(_key).pos; } #region 廣度尋路方法 /// <summary> /// 尋找路徑 /// </summary> public List<string> GetPathWay(string _start, string _end) { BreadthFindWay _findWay = new BreadthFindWay(allNode); _findWay.StartFind(_start, _end); return _findWay.endPath; } #endregion class BreadthFindWay { //所有的路徑 class DotAll { public string endStr;//結尾終點 public float length;//路長 public List<string> path;//路徑 } private Dictionary<string, Node> allNode; //廣度路徑表 private List<DotAll> allPath = new List<DotAll>(); // 最終路線圖 private List<string> endList = new List<string>(); public List<string> endPath = new List<string>(); //比照表 private List<string> bPath = new List<string>(); //最小權重值:根據最終點確定 private float maxWeight = 10000f; public BreadthFindWay(Dictionary<string, Node> _allNode) { allNode = _allNode; } /// <summary> /// 開始尋路 /// </summary> public void StartFind(string _start, string _end) { Node _nStart = GetNodeInPath(_start); DotAll _dot = new DotAll(); _dot.path = new List<string>(); _dot.path.Add(_nStart.findName); _dot.endStr = _nStart.findName; allPath.Add(_dot); BreadthFind(_end); //將endList轉換為endPath foreach (var idx in endList) { endPath.Add(GetNodeInPath(idx).name); } Debug.LogError("尋路結束轉換"); } /// <summary> /// 廣度尋路 /// </summary> private void BreadthFind(string _end) { bool _isEnd = false; var _move = 0; while (!_isEnd && _move < 10000) { _move++; //當所有的子節點都沒有下一步的時候結束尋路 var _removeNum = 0; var _length = allPath.Count; Debug.Log("~~~~~~~第" + _move + "輪~~~~~~~" + _length); for (var _i = _length - 1; _i >= 0; _i--) { var _dot = allPath[_i]; var _endNode = GetNodeInPath(_dot.endStr); var _endName = _endNode.findName; if (_endName == _end) { _removeNum++; continue; } Debug.Log(_endName+" " + _dot.length + " " +maxWeight); if (!bPath.Contains(_endName)) { bPath.Add(_endName); if (_dot.length > maxWeight) { Debug.Log("因為路徑過長被捨棄" + _endName); allPath.Remove(_dot); } else { var _num = 0; foreach (var idx in _endNode.findWay) { //if (_endName == "83") Debug.LogError("~~~~~~~~" +idx+ (idx==_end)); if (idx.Contains("door") && idx != _end) continue; var _nI = GetNodeInPath(idx); var _le = GetVe2Dis(_nI.pos, _endNode.pos) + _dot.length; if (bPath.Contains(idx)) { //找到原路徑進行效驗長短,並決定去留 foreach (var _bIdx in allPath) { if (_bIdx.endStr == idx) { if (_bIdx.length > _le) { Debug.LogError("替換位置" + idx); _bIdx.path = new List<string>(); foreach (var _aa in _dot.path) _bIdx.path.Add(_aa); _bIdx.path.Add(idx); _bIdx.length = _le; } break; } } continue; } if (_le < maxWeight) { if (_endName == "83") PrintPath(_dot); if (_num == 0) { _dot.length = _le; _dot.endStr = idx; _dot.path.Add(idx); CorrectionEnd(idx, _end, _dot); //if (_endName == "83") PrintPath(_dot); } else { var _dThe = new DotAll(); _dThe.length = _le; _dThe.path = new List<string>(); foreach (var _aa in _dot.path) _dThe.path.Add(_aa); _dThe.path[_dThe.path.Count - 1] = idx; _dThe.endStr = idx; allPath.Add(_dThe); CorrectionEnd(idx, _end, _dThe); if (_endName == "83") PrintPath(_dThe); } _num++; } } } } else { //死點捨棄掉 allPath.Remove(_dot); Debug.Log("捨棄掉死點" + _endName); _removeNum++; } } if (_removeNum == _length || _length == 0) { _isEnd = true; } } //Debug.LogError("尋路超過10000步失敗"); } /// <summary> /// 校正終點 /// </summary> private bool CorrectionEnd(string _name,string _end,DotAll _dot) { if (_name == _end && _dot.length < maxWeight) { //更正終點路線 Debug.LogError("選入備選路徑"); maxWeight = _dot.length; endList.Clear(); foreach (var idx in _dot.path) endList.Add(idx); Debug.LogError("列印最終路徑"); PrintPath(_dot); return true; } return false; } private void PrintPath(DotAll _dot) { string _str = ""; foreach (var idx in _dot.path) { _str += "=>" + idx; } Debug.LogError(_str); } private Node GetNodeInPath(string _key) { Node _value = null; allNode.TryGetValue(_key, out _value); return _value; } private float GetVe2Dis(Vector3 _ve1,Vector3 _ve2) { return Vector2.Distance(GetVe2(_ve1), GetVe2(_ve2)); } private Vector2 GetVe2(Vector3 _ve) { return new Vector2(_ve.x, _ve.z); } } }