Unity3D FindPonitWay:通过路径点寻路,个人编写的简单易用小插件

13 篇文章 0 订阅

  最近开发的模拟经营游戏中,有人物寻路的需求。具体如下图所示,人物会沿着固定轨迹走到工作的地点,进行相应的行为逻辑。

由于人物基本按照直线进行行走,使用A*等寻路插件不太符合自己的需求。将大地图简化为路径点之后。画出路线图如下:

由图可分析出,我们只需要做到,不停的找离终点最近的相邻点,就可以实现寻路的目的。

理清思路之后,代码写起来就比较简单了;首先是新建一个路径类,处理该父节点下的所有路径:

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

    /// <summary>
    /// 寻找路径
    /// </summary>
    public List<string> GetPathWay(string _start,string _end)
    {
        List<string> _path = new List<string>();
        _path.Add(GetNodeInPath(_start).name);

        Node _nEnd = GetNodeInPath(_end);
        BStartFind(_start,_end,_nEnd.pos,_path);
        _path.Add(_nEnd.name);

        return _path;
    }

    private Node GetNodeInPath(string _key)
    {
        Node _value = null;
        allNode.TryGetValue(_key, out _value);
        return _value;
    }

    /// <summary>
    /// 伪B*算法:
    /// 优先找距离目标点最近的点    
    /// 待定:找到死点的话回退
    /// </summary> 
    private void BStartFind(string _start, string _end,Vector3 _pos,List<string> _path)
    {        
        var _sign = "";
        var _weight = 10000f;
        Node _nStart = GetNodeInPath(_start);
        var _findWay = _nStart.findWay;
        for (var i = 0; i < _findWay.Count; i++)
        {
            var _str = _findWay[i];
            //直接返回
            if (_str == _end)
            {
                _path.Add(_nStart.name);return;
            }

            //比较权重,挑出最合适的点
            var _n = GetNodeInPath(_str);
            var _num = Vector3.Distance(_n.pos,_pos);
            if (_num < _weight && !_path.Contains(_n.name))
            {
                _weight = _num;
                _sign = _n.findName;
                //Debug.LogError(_sign);
            }
        }

        Debug.Log(_sign);
        var _nn = GetNodeInPath(_sign);
        _path.Add(_nn.name);
        BStartFind(_nn.findName, _end, _pos, _path);

    }
}

  U3D截图如下,我是用名字来标记路径点本身与相邻路径点。用逗号隔开,首位为本身名字,之后为路径点名字:

最后我们来写个测试脚本测试下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 寻路测试
/// </summary>
public class FindTest : MonoBehaviour
{
    public FindWayPath path;

    public Transform findway;
    // Start is called before the first frame update
    void Start()
    {
        path = new FindWayPath(findway);
        var _path = path.GetPathWay("18","1");
        //打印
        var _str = "";
        foreach (var idx in _path) _str += idx + "=>";
        Debug.LogError(_str);
        //
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

测试结果和图示如下:

 

最后我会将demo放到我的链接中,使用2019.4.4以上版本都可打开测试。更多定制要求请私信联系。

 

------------------------------------------2020/12/15更新------------------------------------------

 

以上寻路未完成的就是,寻找到死路的时候,应该回退至上一节点,更改后代码如下:

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

    /// <summary>
    /// 寻找路径
    /// </summary>
    public List<string> GetPathWay(string _start,string _end)
    {
        List<string> _path = new List<string>();
        _path.Add(GetNodeInPath(_start).name);

        List<string> _abandonedPath = new List<string>();

        Node _nEnd = GetNodeInPath(_end);
        BStartFind(_start,_end,_nEnd.pos,_path,_abandonedPath);
        _path.Add(_nEnd.name);

        return _path;
    }

    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>
    /// 伪B*算法:
    /// 优先找距离目标点最近的点    
    /// 待定:找到死点的话回退
    /// </summary> 
    private void BStartFind(string _start, string _end,Vector3 _pos,List<string> _path, List<string> _aPath)
    {        
        var _sign = "";
        var _weight = 10000f;
        Node _nStart = GetNodeInPath(_start);
        var _findWay = _nStart.findWay;
        for (var i = 0; i < _findWay.Count; i++)
        {
            var _str = _findWay[i];
            //直接返回
            if (_str == _end)
            {
                _path.Add(_nStart.name);return;
            }
              
            //比较权重,挑出最合适的点
            var _n = GetNodeInPath(_str);

            if (!_path.Contains(_n.name) &&
                !_n.findName.Contains("door") &&
                !_aPath.Contains(_n.name))
            {
                var _num = Vector3.Distance(_n.pos, _pos);
                if (_num < _weight)
                {
                    _weight = _num;
                    _sign = _n.findName;
                    //Debug.LogError(_sign);
                }
            }
              
        }
        //Debug.Log(_sign);
        if (_sign == "")
        {
            //返回上一步并重新寻找
            _aPath.Add(_nStart.name);
            _path.Remove(_nStart.name);
            BStartFind(_path[_path.Count - 1].Split(',')[0], _end, _pos, _path, _aPath);
        }
        else
        {
            var _nn = GetNodeInPath(_sign);

            _path.Add(_nn.name);
            BStartFind(_nn.findName, _end, _pos, _path,_aPath);
        }      
    }
}

 

 

------------------------------------------2020/12/30更新------------------------------------------

加上算法启发与路径校正:

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

    private string GetDotFindName(string _str)
    {
        return _str.Split(',')[0];
    }

    /// <summary>
    /// 寻找路径
    /// </summary>
    public List<string> GetPathWay(string _start,string _end)
    {
        List<string> _path = new List<string>();
        Node _nStart = GetNodeInPath(_start);
        _path.Add(_nStart.name);

        List<string> _abandonedPath = new List<string>();
        
        Node _nEnd = GetNodeInPath(_end);
        BStartFind(_start, _end,_nStart.pos, _nEnd.pos, _path, _abandonedPath, 0);
        _path.Add(_nEnd.name);

        //PathCorrection(_path, _path.Count - 1);

        return _path;
    }

  

    /// <summary>
    /// 伪B*算法:
    /// 优先找距离目标点最近的点    
    /// 待定:找到死点的话回退
    /// </summary> 
    private void BStartFind(string _start, string _end, Vector3 _sPos, Vector3 _ePos,List<string> _path, List<string> _aPath,int _move)
    {
        if (_move > 1000)
        {
            Debug.LogError("此次寻路路径超过1000错误");
            return;
        }   
        _move++;

        var _sign = "";
        var _weight = 1000f;
        Node _nStart = GetNodeInPath(_start);
        var _findWay = _nStart.findWay;
        for (var i = 0; i < _findWay.Count; i++)
        {
            var _str = _findWay[i];
            //直接返回
            if (_str == _end)
            {
                _path.Add(_nStart.name);return;
            }
              
            //比较权重,挑出最合适的点
            var _n = GetNodeInPath(_str);

            if (!_path.Contains(_n.name) &&
                !_n.findName.Contains("door") &&
                !_aPath.Contains(_n.name))
            {
                var _H = Vector3.Distance(_n.pos, _ePos);
                var _G = Vector3.Distance(_n.pos, _sPos);
                var _F = _H + _G;
                if (_nStart.findName == "106") Debug.Log(_str + "  " + _F);
                if (_F < _weight)
                {
                    _weight = _F;
                    _sign = _n.findName;
                    //Debug.LogError(_sign);
                }
            }
              
        }
        //Debug.Log(_sign);
        if (_sign == "")
        {
            //返回上一步并重新寻找
            _aPath.Add(_nStart.name);
            _path.Remove(_nStart.name);
            BStartFind(_path[_path.Count - 1].Split(',')[0], _end,_sPos, _ePos, _path, _aPath,_move);
        }
        else
        {
            var _nn = GetNodeInPath(_sign);
            
            _path.Add(_nn.name);
            BStartFind(_nn.findName, _end,_sPos, _ePos, _path,_aPath,_move);
        }      
    }

    /// <summary>
    /// 路径校正
    /// </summary>
    private void PathCorrection(List<string> _path,int _sign)
    {
        //从后往前遍历,找到直接相邻的非连接点,舍弃中间点并开启下一轮
        if (_sign < 2) return;
        for (var _i = _sign; _i >= 2; _i--)
        {
            var _n = GetNodeInPath(GetDotFindName(_path[_i]));
            for (var _j = 0; _j < _i - 1; _j++)
            {
                var _nJ = GetNodeInPath(GetDotFindName(_path[_j]));
                if (_nJ.findWay.Contains(_n.findName))
                {
                    //消减所有中间项
                    Debug.LogError(_n.name + "  " + _nJ.name);
                    var _theSign = _j;
                    //_j以上/_i以下的全部删除
                    for (var _r = _i - 1; _r > _j; _r--)
                        _path.Remove(_path[_r]);
                    PathCorrection(_path, _j);
                    return;
                }
            }
        }
    }
}

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值