最近开发的模拟经营游戏中,有人物寻路的需求。具体如下图所示,人物会沿着固定轨迹走到工作的地点,进行相应的行为逻辑。
由于人物基本按照直线进行行走,使用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;
}
}
}
}
}