示意
点击某处,让角色向该处移动。

用法
将2个代码文件(NavPathSys.cs、Pathfinder.cs)放入工程中。
调用NavPathSys的GetPath方法,获取一组路径点。
- 记得在合适的时机(一般是游戏开始和结束)初始化和销毁该Sys
获取路径后,自行实现从当前点移动至目标点的逻辑:如在Update中,让角色依次移动至路径中的各点(详见具体实现)
实现
MoveTest.cs
示意用法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveTest : MonoBehaviour
{
private NavPathSys _navPathSys;
private List<Vector2> _path;
private Vector2 PathFirstPos { get { return _path[0]; } }
[SerializeField]
private LayerMask _obstaclesLayer;
private float _agentRadius = 0.5f;
[SerializeField]
private bool _shouldDrawPath = false;
[SerializeField]
private Color _drawPathColor = Color.blue;
private bool CanMove { get { return _path.Count > 0; } }
[SerializeField]
private Transform _moveTrans;
[SerializeField, Header("应是MoveTrans的子节点")]
private Transform _dirTrans;
[SerializeField]
private Vector2 Dir { get { return _dirTrans.right; } }
private void SetDir(Vector2 dir)
{
_dirTrans.right = dir.normalized;
}
[SerializeField]
private float _moveSpeed = 5f;
private float ActualMoveSpeed { get { return _moveSpeed * Time.deltaTime; } }
private Vector2 Pos { get { return transform.position; } set { transform.position = value; } }
private void Move(Vector2 offset)
{
transform.position += new Vector3(offset.x, offset.y, 0);
}
private bool Is2DCam { get { return Camera.main.orthographic; } }
private void Start()
{
Init();
}
private void Update()
{
MyUpdate();
}
private void OnDestroy()
{
MyDestroy();
}
private void Init()
{
_navPathSys = new NavPathSys();
_path = new List<Vector2>();
}
private void MyUpdate()
{
if (Input.GetMouseButtonDown(0))
{
if (Is2DCam)
{
MoveToTarget(Camera.main.ScreenToWorldPoint(Input.mousePosition));
}
else
{
MoveToTarget(Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, _moveTrans.position.z - Camera.main.transform.position.z)));
}
}
if (CanMove)
{
SetDir(PathFirstPos - Pos);
Move(Dir * ActualMoveSpeed);
// 若到达了当前的目标点
if ((Pos - PathFirstPos).sqrMagnitude < ActualMoveSpeed * ActualMoveSpeed)
{
Pos = PathFirstPos;
_path.RemoveAt(0);
}
// 画出路径
if (_shouldDrawPath)
{
for (int i = 0; i < _path.Count - 1; i++)
{
Debug.DrawLine(_path[i], _path[i + 1], _drawPathColor);
}
}
}
}
private void MyDestroy()
{
_path.Clear();
_path = null;
_navPathSys.MyDestroy();
_navPathSys = null;
}
private void MoveToTarget(Vector2 targetPos)
{
if (targetPos == Pos)
{
Debug.Log("当前位置就是目标位置,无需移动");
return;
}
// 生成路径点
_path = _navPathSys.GetPath(Pos, targetPos, _obstaclesLayer, _agentRadius);
if (_path.Count == 0)
{
Debug.Log("CatMove生成路径失败");
}
}
}
NavPathSys.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NavPathSys
{
// 网格大小
private float GridSize { get { return 0.5f; } }
// 设为false时,路径点必然是各Grid。(设为true会更耗,因为使用了Physics2D.Linecast)
private bool ShouldSearchShortcut { get { return false; } }
// 是否要求移动物体必须对齐到grid的位置。
private bool ShouldSnapToGrid { get { return false; } }
public NavPathSys()
{
}
public void MyDestroy()
{
}
public List<Vector2> GetPath(Vector2 from, Vector2 to, LayerMask obstacleLayer, float agentRadius)
{
var info = new MyPathFinderInfo()
{
GridSize = this.GridSize,
ObstacleLayer = obstacleLayer,
ShouldSearchShortcut = this.ShouldSearchShortcut,
ShouldSnapToGrid = this.ShouldSnapToGrid,
AgentRadius = agentRadius
};
var myPathFinder = new MyPathFinder(info);
return myPathFinder.GetPath(from, to);
}
private class MyPathFinderInfo
{
public float GridSize;
public LayerMask ObstacleLayer;
public bool ShouldSearchShortcut;
public bool ShouldSnapToGrid;
public float AgentRadius;
}
private class MyPathFinder
{
private MyPathFinderInfo _info;
// 寻路器:提供获取路径点的方法等
private Pathfinder<Vector2> _pathFinder;
public MyPathFinder(MyPathFinderInfo info)
{
_info = info;
// 地图越大,迭代次数需配置得越大
_pathFinder = new Pathfinder<Vector2>(GetDistance, GetNeighbourNodes, 1000);
}
public void MyDestroy()
{
_pathFinder = null;
_info = null;
}
public List<Vector2> GetPath(Vector2 from, Vector2 to)
{
var path = new List<Vector2>();
if (_pathFinder.GenerateAstarPath(GetClosestNode(from), GetClosestNode(to), out path))
{
// 设置为当前要移动的路径
// 寻找近路
if (_info.ShouldSearchShortcut && path.Count > 0)
path = ShortenPath(path);
else
{
if (!_info.ShouldSnapToGrid)
{
// 将终点位置添加到路径最后
path.Add(to);
}
}
}
else
{
// Debug.Log("获取路径失败!" + from + "-----" + to);
}
return path;
}
/// <summary>
/// 获得2点的距离用于比较(为了性能,实际获得的是距离的平方)
/// </summary>
/// <param name="A"></param>
/// <param name="B"></param>
/// <returns></returns>
float GetDistance(Vector2 A, Vector2 B)
{
return (A - B).sqrMagnitude;
}
/// <summary>
/// 获得邻近的可到达(两点之间未被障碍物遮挡)的grid及与对方之间的距离(不包括自己)
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
Dictionary<Vector2, float> GetNeighbourNodes(Vector2 pos)
{
Dictionary<Vector2, float> neighbours = new Dictionary<Vector2, float>();
for (int i = -1; i < 2; i++)
{
for (int j = -1; j < 2; j++)
{
if (i == 0 && j == 0)
{
continue;
}
Vector2 dir = new Vector2(i, j) * _info.GridSize;
if (!Physics2D.Linecast(pos, pos + dir, _info.ObstacleLayer))
{
// 避免路径过于贴近障碍物导致“移动者穿墙”
if (Physics2D.OverlapCircle(pos + dir, _info.AgentRadius, _info.ObstacleLayer) != null)
{
continue;
}
neighbours.Add(GetClosestNode(pos + dir), dir.magnitude);
}
}
}
return neighbours;
}
/// <summary>
/// 获取与目标点位置最近的grid
/// </summary>
/// <param name="target"></param>
/// <returns></returns>
Vector2 GetClosestNode(Vector2 target)
{
return new Vector2(Mathf.Round(target.x / _info.GridSize) * _info.GridSize, Mathf.Round(target.y / _info.GridSize) * _info.GridSize);
}
// 获得近路:原理是剔除部分(可省略的)路径点
List<Vector2> ShortenPath(List<Vector2> path)
{
List<Vector2> newPath = new List<Vector2>();
for (int i = 0; i < path.Count; i++)
{
newPath.Add(path[i]);
for (int j = path.Count - 1; j > i; j--)
{
if (!Physics2D.Linecast(path[i], path[j], _info.ObstacleLayer))
{
i = j;
break;
}
}
newPath.Add(path[i]);
}
newPath.Add(path[path.Count - 1]);
return newPath;
}
}
}
Pathfinder.cs
using System;
using System.Collections.Generic;
using System.Linq;
public class Pathfinder<T>
{
private struct pathData
{
public float g;
public float h;
public float f;
}
private int _calculatorPatience;
private Func<T, T, float> _HeuristicDistance;
private Func<T, Dictionary<T, float>> _ConnectedNodesAndStepCosts;
public Pathfinder(Func<T, T, float> HeuristicDistance, Func<T, Dictionary<T, float>> ConnectedNodesAndStepCosts, int calculatorPatience = 9999)
{
_HeuristicDistance = HeuristicDistance;
_ConnectedNodesAndStepCosts = ConnectedNodesAndStepCosts;
_calculatorPatience = calculatorPatience;
}
public bool GenerateAstarPath(T startNode, T targetNode, out List<T> path)
{
float num = _HeuristicDistance(startNode, targetNode);
int num2 = _calculatorPatience;
HashSet<T> hashSet = new HashSet<T>();
Dictionary<T, pathData> dictionary = new Dictionary<T, pathData> {
{
startNode,
new pathData
{
g = 0f,
h = num,
f = num
}
} };
Dictionary<T, T> dictionary2 = new Dictionary<T, T>();
while (num2 > 0)
{
num2--;
if (dictionary.Count == 0)
{
break;
}
T key = dictionary.Aggregate((KeyValuePair<T, pathData> l, KeyValuePair<T, pathData> r) => (l.Value.f < r.Value.f) ? l : r).Key;
pathData pathData = dictionary[key];
dictionary.Remove(key);
hashSet.Add(key);
if (key.Equals(targetNode))
{
List<T> list = new List<T>();
T val = key;
while (!val.Equals(startNode))
{
list.Add(val);
val = dictionary2[val];
}
list.Reverse();
path = list;
return true;
}
foreach (KeyValuePair<T, float> item in _ConnectedNodesAndStepCosts(key))
{
if (Enumerable.Contains(hashSet, item.Key))
{
continue;
}
float num3 = item.Value + pathData.g;
if (!dictionary.ContainsKey(item.Key) || dictionary[item.Key].g > num3)
{
float num4 = _HeuristicDistance(item.Key, targetNode);
float f = num4 + num3;
dictionary2[item.Key] = key;
if (!dictionary.ContainsKey(item.Key))
{
dictionary.Add(item.Key, new pathData
{
g = num3,
h = num4,
f = f
});
}
else
{
dictionary[item.Key] = new pathData
{
g = num3,
h = num4,
f = f
};
}
}
}
}
path = new List<T>();
return false;
}
}
网友评论