A*寻路主体思路
A*寻路代码实现
不同的距离估算公式会导致结果的细微差别
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace AStar
{
public class Astar : Singleton<Astar>
{
private Astar() { }
//方向数组
int[,] dir = new int[4, 2] { { 0, 1 }, { 0, -1 }, { 1, 0 }, { -1, 0 } };
/// <summary>
/// 寻路主体
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
/// <param name="map"></param>
/// <returns></returns>
public bool FindPath(Point start, Point end, Point[,] map)
{
if (start == null || end == null || map == null)
{
return false;
}
LinkedList<Point> openList = new LinkedList<Point>();
LinkedList<Point> closeList = new LinkedList<Point>();
openList.AddLast(start);
while (openList.Count > 0)
{
Point point = FindMinFOfPoint(openList);
openList.Remove(point);
closeList.AddLast(point);
LinkedList<Point> roundPointList = GetRoundPoint(point, map, closeList);
foreach (Point item in roundPointList)
{
if (openList.Contains(item))
{
float nowG = CalcG(item, point);
if (nowG < item.G)
{
item.Update(point, nowG);
}
}
else
{
item.parent = point;
CalcF(item, end);
openList.AddLast(item);
}
if (item == end)
{
return true;
}
}
}
return false;
}
/// <summary>
/// 获取F值最小的节点
/// </summary>
/// <param name="openList"></param>
/// <returns></returns>
Point FindMinFOfPoint(LinkedList<Point> openList)
{
float f = float.MaxValue;
Point tmp = null;
foreach (Point p in openList)
{
if (p.F < f)
{
tmp = p;
f = p.F;
}
}
return tmp;
}
/// <summary>
/// 获取相邻节点
/// </summary>
/// <param name="p"></param>
/// <param name="map"></param>
/// <param name="closeList"></param>
/// <returns></returns>
LinkedList<Point> GetRoundPoint(Point p, Point[,] map, LinkedList<Point> closeList)
{
LinkedList<Point> ret = new LinkedList<Point>();
bool[] book = new bool[4];
int mapLen = map.GetLength(0);
int mapWid = map.GetLength(1);
for (int i = 0; i < 4; i++)
{
int x = p.x + dir[i, 0];
int y = p.y + dir[i, 1];
if (x >= 0 && x < mapLen && y >= 0 && y < mapWid && !map[x, y].isWall)
{
ret.AddLast(map[x, y]);
book[i] = true;
}
}
for (int i = 0; i < 2; i++)
{
for (int j = 2; j < 4; j++)
{
int x = p.x + dir[i, 0] + dir[j, 0];
int y = p.y + dir[i, 1] + dir[j, 1];
if (book[i] && book[j] && x >= 0 && x < mapLen && y >= 0 && y < mapWid && !map[x, y].isWall)
{
ret.AddLast(map[x, y]);
}
}
}
foreach (var item in closeList)
{
if (ret.Contains(item))
{
ret.Remove(item);
}
}
return ret;
}
/// <summary>
/// 计算G值
/// </summary>
/// <param name="now"></param>
/// <param name="parent"></param>
/// <returns></returns>
float CalcG(Point now, Point parent)
{
return GetDistance(now, parent) + parent.G;
}
/// <summary>
/// 计算F值
/// </summary>
/// <param name="now"></param>
/// <param name="end"></param>
void CalcF(Point now, Point end)
{
float h = GetDistance_MHD(now, end);
float g = 0;
if (now.parent != null)
{
g = CalcG(now,now.parent);
}
float f = g + h;
now.F = f;
now.G = g;
now.H = h;
}
/// <summary>
/// 欧式距离
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
float GetDistance(Point a, Point b)
{
return Vector2.Distance(new Vector2(a.x, a.y), new Vector2(b.x, b.y));
}
/// <summary>
/// 曼哈顿距离
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
float GetDistance_MHD(Point a, Point b)
{
return Mathf.Abs(a.x - b.x) + Mathf.Abs(a.y - b.y);
}
/// <summary>
/// 对角距离
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
float GetDistance_DJ(Point a, Point b)
{
float dx = Mathf.Abs(a.x - b.x);
float dy = Mathf.Abs(a.y - b.y);
return dx + dy + (Mathf.Sqrt(2) - 2) * Mathf.Min(dx, dy);
}
}
}
测试寻路
Point
public class Point
{
public Point parent;
public float F;
public float G;
public float H;
public int x;
public int y;
public bool isWall;
public GameObject go;
public Point(int x, int y, GameObject go = null, Point parent = null)
{
this.go = go;
this.x = x;
this.y = y;
this.parent = parent;
}
public void Update(Point parent, float g)
{
this.parent = parent;
G = g;
F = G + H;
}
}
AStarTest
public class AstarTest : MonoBehaviour
{
public RectTransform map;
//格子的长宽
public int ceilWidth = 20;
public int ceilHeight = 20;
//格子间隔
public int margin = 2;
//横排格子数
public int xNum = 20;
//竖排格子数
public int yNum = 20;
public Button drawBtn;
Point[,] mapData;
Point star;
Point end;
Coroutine co;
//模拟玩家
GameObject player;
//初始化
void Start()
{
player = new GameObject("Player");
player.AddComponent<RectTransform>().sizeDelta = new Vector2(20, 20);
player.AddComponent<Image>().color = Color.blue;
player.SetActive(false);
player.transform.SetParent(map.transform.parent);
int mapx = (ceilWidth + margin) * xNum + 10;
int mapy = (ceilWidth + margin) * yNum + 10;
map.sizeDelta = new Vector2(mapx, mapy);
var grid = map.gameObject.AddComponent<GridLayoutGroup>();
grid.cellSize = new Vector2(ceilWidth, ceilHeight);
grid.spacing = new Vector2(margin, margin);
mapData = new Point[yNum, xNum];
for (int i = 0; i < yNum; i++)
{
for (int j = 0; j < xNum; j++)
{
GameObject go = new GameObject();
go.AddComponent<RectTransform>();
go.AddComponent<Image>();
var btm = go.AddComponent<ButtonPointManager>();
go.transform.SetParent(map.transform);
Point point = new Point(i, j, go);
mapData[i, j] = point;
btm.leftClick.AddListener(() =>
{
if (point.isWall) return;
point.go.GetComponent<Image>().color = Color.green;
if (co != null) StopCoroutine(co);
ClearRoad();
if (star != null)
{
star.go.GetComponent<Image>().color = Color.white;
}
star = point;
co = StartCoroutine(MovePath());
});
btm.rightClick.AddListener(() =>
{
if (point.isWall) return;
point.go.GetComponent<Image>().color = Color.red;
if (co != null) StopCoroutine(co);
ClearRoad();
if (end != null)
{
end.go.GetComponent<Image>().color = Color.white;
}
end = point;
co = StartCoroutine(MovePath());
});
btm.middleClick.AddListener(() =>
{
if (point != end && point != star)
{
point.isWall = !point.isWall;
point.go.GetComponent<Image>().color = point.isWall ? Color.black : Color.white;
}
});
}
}
DrawMap();
if (drawBtn != null)
{
drawBtn.onClick.AddListener(() =>
{
DrawMap();
});
}
}
//清空痕迹
void ClearRoad()
{
if (star != null && end != null)
{
Point temp = end.parent;
while (temp != null)
{
if (temp == star) return;
temp.go.GetComponent<Image>().color = Color.white;
temp = temp.parent;
}
}
}
//根据路径移动
IEnumerator MovePath()
{
List<Point> points = new List<Point>();
Point x1 = star, x2 = end;
while (x1 != null && x2 != null && x1 != x2 && Astar.Instance.FindPath(x1, x2, mapData))
{
player.transform.position = x1.go.transform.position;
player.SetActive(true);
Point temp = x2;
while (temp != null)
{
points.Add(temp);
temp = temp.parent;
if (temp == x1) break;
}
points.Reverse();
foreach (Point item in points)
{
//无法通过重新寻路
if (!Check(x1, item))
{
points.Clear();
break;
}
StartCoroutine(MoveTrans(player.transform, item.go.transform.position, 0.49f));
yield return new WaitForSeconds(0.5f);
x1 = item;
// 保留痕迹
if (item != x2)
{
item.go.GetComponent<Image>().color = Color.yellow;
}
}
}
yield return 0;
}
// 移动物体
IEnumerator MoveTrans(Transform trans, Vector3 target, float time)
{
float curTime = 0;
while (true)
{
curTime += Time.deltaTime;
if (curTime >= time) curTime = 1;
trans.position = Vector3.Lerp(trans.position, target, curTime);
if (curTime == 1) break;
yield return 0;
}
}
//绘制地图
void DrawMap()
{
if (co != null) StopCoroutine(co);
for (int i = 0; i < mapData.GetLength(0); i++)
{
for (int j = 0; j < mapData.GetLength(1); j++)
{
var point = mapData[i, j];
point.isWall = Random.Range(0, 100) > 90;
point.parent = null;
point.go.GetComponent<Image>().color = point.isWall ? Color.black : Color.white;
}
}
}
/// <summary>
/// 检查x1能否到达x2
/// </summary>
/// <param name="x1"></param>
/// <param name="x2"></param>
/// <returns></returns>
bool Check(Point x1, Point x2)
{
if (x2.isWall) return false;
int x = x2.x - x1.x;
int y = x2.y - x1.y;
if (Mathf.Abs(x) + Mathf.Abs(y) == 2)
{
if (mapData[x1.x + x, x1.y].isWall || mapData[x1.x, x1.y + y].isWall) return false;
}
return true;
}
}