1. 程式人生 > 其它 >Unity廣度尋路演算法

Unity廣度尋路演算法

技術標籤:U3D演算法Unity廣度尋路

由於專案中的連線點是邏輯上的點,採用的是圖的連線方式,所以採用廣度尋路的方法。路徑點命名規則如下:

用逗號隔開,第一位代表本名,後面跟的是可尋路徑點名字。

尋路方法如下:

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);
        }
    }
}