A星算法——简易版
A星管理器: AStarWrapper.cs类
using System.Collections.Generic;
using UnityEngine;
namespace AStar {
/// <summary>
/// A星寻路的单例封装
/// 实现原理
/// 1、首先有一张一定宽高的地图 (定义好 Point 点的地图,其中 Point 中有 IsWall 属性)
/// 2、设定开始点,和目标点
/// 3、传入 FindPath 开始寻找较短路径,找到返回true,否则 false
/// 4、为 true 就可以通过 目标点的父亲点的父亲点的父亲点,直到父亲点为开始点,这些点集合即是路径
/// 5、FindPath 寻找原理
/// 1)开列表,关列表初始化
/// 2)添加开始点到开列表,然后获得周围点集合,接着又把开始点从开列表中移除,并添加到关列表
/// 3)判断这些周围点集合是否已经在开列表中,不在则更新这些点的F 和 父亲点,并添加到开列表;再则重新计算G值,G较小则更新GF 和父亲点
/// 4)从周围点集合中找到 F 最小的点,然后获得周围点集合,接着又把找到 F 最小的点从开列表中移除,并添加到关列表
/// 5)接着执行第 3) 步骤
/// 6)直到目标点被添加到开列表中,则路径找到
/// 7)否则,直到开列表中没有了数据,则说明没有合适路径
/// </summary>
public class AStarWrapper : Singleton<AStarWrapper>
{
/// <summary>
/// 根据开始点和结束点,取得较短路径
/// </summary>
/// <param name="start">开始位置</param>
/// <param name="end">结束位置</param>
/// <param name="map">地图</param>
/// <param name="mapWidth">地图宽</param>
/// <param name="mapHeight">地图高</param>
/// <returns>bool 找到路线/false 没有</returns>
public bool FindPath(Point start, Point end, Point[,] map, int mapWidth, int mapHeight)
{
// 开列表(周围的点列表),关列表(每次比较后得到的最小 F 的点列表)
List<Point> openList = new List<Point>();
List<Point> closeList = new List<Point>();
// 首先开始点添加进开列表
openList.Add(start);
while (openList.Count > 0)
{
// 寻找开列表中最小的 F 值
Point point = FindMinFOfPoint(openList);
// 把得到的 F 最小的点移除 开列表,添加到关列表中
openList.Remove(point);
closeList.Add(point);
// 获取当前最小 F 点周围的点集合
List<Point> surroundPoints = GetSurroundPoints(point, map, mapWidth, mapHeight);
// 过滤掉关列表中的数据
PointFilter(surroundPoints, closeList);
// 遍历获得的周围点
foreach (Point item in surroundPoints)
{
// 已存在开列表中的话
if (openList.IndexOf(item) > -1)
{
// 计算 G 值
float nowG = CalcG(item, point);
// 得到的 G 值小的话,更新 G 值和点的父节点
if (nowG < item.G)
{
item.UpdateParent(point, nowG);
}
}
else // 不在开列表中
{
//把 最小 F 点,设置为他的父节点
item.Parent = point;
// 计算更新 item的 FGH
CalcF(item, end);
// 添加到 开列表
openList.Add(item);
}
}
// 判断 end 是否在 开列表中,即找到路径结束
if (openList.IndexOf(end) > -1)
{
return true;
}
}
// 开列表没有了点,说明找不到路径
return false;
}
/// <summary>
/// 把关列表中的点从周围点集合中过滤掉
/// </summary>
/// <param name="src">周围点集合</param>
/// <param name="closeList">关列表</param>
private void PointFilter(List<Point> src, List<Point> closeList)
{
// 遍历,存在则移除
foreach (Point item in closeList)
{
if (src.IndexOf(item) > -1)
{
src.Remove(item);
}
}
}
/// <summary>
/// 获得当前最小 F 的周围点
/// </summary>
/// <param name="point">当前最小 F 点</param>
/// <param name="map">地图点集合</param>
/// <param name="mapWidth">地图宽</param>
/// <param name="mapHeight">地图高</param>
/// <returns>返回获得的周围点集合</returns>
private List<Point> GetSurroundPoints(Point point, Point[,] map, int mapWidth, int mapHeight)
{
// 一个点一般会有上、下、左、右,左上、左下、右上、右下 八个点
Point up = null, down = null, left = null, right = null;
Point lu = null, ru = null, ld = null, rd = null;
// 如果 点 Y 小于 mapHeight - 1,则表明不是最顶端,有上点
if (point.Y < mapHeight - 1)
{
up = map[point.X, point.Y + 1];
}
// 如果 点 Y 大于 0,则表明不是最下端,有下点
if (point.Y > 0)
{
down = map[point.X, point.Y - 1];
}
// 如果 点 X 小于 mapWidth - 1,则表明不是最右端,有右点
if (point.X < mapWidth - 1)
{
right = map[point.X + 1, point.Y];
}
// 如果 点 X 大于 0,则表明不是最左端,有左点
if (point.X > 0)
{
left = map[point.X - 1, point.Y];
}
// 边角点
// 有上点和左点,说明有左上点
if (up != null && left != null)
{
lu = map[point.X - 1, point.Y + 1];
}
// 有上点和右点,说明有右上点
if (up != null && right != null)
{
ru = map[point.X + 1, point.Y + 1];
}
// 有下点和左点,说明有左下点
if (down != null && left != null)
{
ld = map[point.X - 1, point.Y - 1];
}
// 有下点和右点,说明有右下点
if (down != null && right != null)
{
rd = map[point.X + 1, point.Y - 1];
}
// 新建一个列表
List<Point> list = new List<Point>();
// 把点添加到列表
// 上点不为空,且不是障碍物,则添加到返回列表中,下面同理
if (up != null && up.IsWall == false)
{
list.Add(up);
}
if (down != null && down.IsWall == false)
{
list.Add(down);
}
if (left != null && left.IsWall == false)
{
list.Add(left);
}
if (right != null && right.IsWall == false)
{
list.Add(right);
}
// 添加边角到列表
// 左上点不为空且不是障碍物,并且 左点和上点都不是障碍物,则添加到返回列表,以下同理
if (lu != null && lu.IsWall == false && left.IsWall == false && up.IsWall == false)
{
list.Add(lu);
}
if (ru != null && ru.IsWall == false && right.IsWall == false && up.IsWall == false)
{
list.Add(ru);
}
if (ld != null && ld.IsWall == false && left.IsWall == false && down.IsWall == false)
{
list.Add(ld);
}
if (rd != null && rd.IsWall == false && right.IsWall == false && down.IsWall == false)
{
list.Add(rd);
}
// 返回列表
return list;
}
/// <summary>
/// 比较开列表中所有点的 F 值,获得当前列表中最小的点
/// </summary>
/// <param name="openList">开列表</param>
/// <returns></returns>
private Point FindMinFOfPoint(List<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>
/// 计算 G 值
/// </summary>
/// <param name="now">当前点</param>
/// <param name="parent">当前点的父节点</param>
/// <returns></returns>
private float CalcG(Point now, Point parent)
{
return Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(parent.X, parent.Y)) + parent.G;
}
/// <summary>
/// 计算 F 值
/// </summary>
/// <param name="now">当前点</param>
/// <param name="end">结束点</param>
private void CalcF(Point now, Point end)
{
// F = G + H
// H 值的计算方式不唯一,有意义就行,这里是结束点X和Y的和
float h = Mathf.Abs(end.X - now.X) + Mathf.Abs(end.Y - now.Y);
float g = 0;
if (now.Parent == null)
{
g = 0;
}
else
{
// 当前点和父节点的距离
g = Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(now.Parent.X, now.Parent.Y)) + now.Parent.G;
}
// 更新当前点FGH的值
float f = g + h;
now.F = f;
now.G = g;
now.H = h;
}
}
}
路点:Point.cs类
using UnityEngine;
namespace AStar
{
/// <summary>
/// AStar 中点的定义
/// </summary>
public class Point
{
// 父亲节点
public Point Parent { get; set; }
// F G H 值
public float F { get; set; }
public float G { get; set; }
public float H { get; set; }
// 坐标值
public int X { get; set; }
public int Y { get; set; }
// 是否是障碍物(例如墙)
public bool IsWall { get; set; }
// 该点的游戏物体(根据需要可不用可删除)
public GameObject gameObject;
// 该点的空间位置(根据需要可不用可删除)
public Vector3 position;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="go"></param>
/// <param name="parent"></param>
/// <param name="position"></param>
public Point(int x, int y, GameObject go = null, Point parent = null, Vector3 position = default)
{
this.X = x;
this.Y = y;
this.gameObject = go;
this.position = position;
this.Parent = parent;
IsWall = false;
}
/// <summary>
/// 更新G,F 值,和父亲节点
/// </summary>
/// <param name="parent"></param>
/// <param name="g"></param>
public void UpdateParent(Point parent, float g)
{
this.Parent = parent;
this.G = g;
F = G + H;
}
}
}
单例类:Singleton.cs
public class Singleton<T> where T :class,new()
{
private static T instance;
private static readonly object locker = new object();
public static T Instance {
get {
lock (locker) {
if (instance == null) {
instance = new T();
}
return instance;
}
}
}
}
A星测试:Test_AStarWrapper.cs
using AStar;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test_AStarWrapper : MonoBehaviour
{
int mapWidth;//地图宽度
int mapHeight;//地图高度
Point[,] map = null;//所有地图点
List<Point> wallList = null;//阻碍点
int wallNum;//阻碍数量
Point startPoint = null;//开始位置
Point targetPoint = null;//目标位置
List<Point> oldPath = null;//旧的路径
List<Point> newPath = null;//新的路径
//有关颜色
Color normalColor = Color.white;//正常颜色
Color wallColor = Color.black;//阻碍颜色
Color startPosColor = Color.green;//开始颜色
Color targetPosColor = Color.red;//目标颜色
Color pathPosColor = Color.yellow;//路径颜色
//有关相机
Vector3 cameraTargetPosition;
Camera cameraMain;
void Start()
{
Init();
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
//点击鼠标左键,设置起始点
SelectStartPoint(Input.mousePosition);
}
if (Input.GetMouseButtonDown(1))
{
//点击鼠标右键,设置目标点
SelectTargetPoint(Input.mousePosition);
}
if (Input.GetKeyDown(KeyCode.Space))
{
//按下空格重置地图
RefreshMap();
}
}
void Init() {
mapWidth = 12;
mapHeight = 10;
map = new Point[mapWidth, mapHeight];
wallNum = (int)(mapWidth * mapHeight * 0.1f);
wallList = new List<Point>();
oldPath = new List<Point>();
newPath = new List<Point>();
InitMap(mapWidth, mapHeight);//创建地图
SetWall();//设置地图障碍物
//有关相机
cameraMain = Camera.main;
cameraTargetPosition = new Vector3(mapWidth / 2, mapHeight / 2, (mapWidth> mapHeight)?-mapWidth:-mapHeight);
cameraMain.transform.position = cameraTargetPosition;
}
void InitMap(int mapWidth, int mapHeight)
{
for (int x = 0; x < mapWidth; x++)
{
for (int y = 0; y < mapHeight; y++)
{
//创建格子
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
go.transform.position = new Vector3(x, y, 0);
go.transform.localScale = Vector3.one * 0.9f;
go.GetComponent<Renderer>().material.color = normalColor;
//存入地图
map[x, y] = new Point(x, y, go);
}
}
}
/// <summary>
/// 查找路径并且显示出来
/// </summary>
private void FindPathAndShowPath()
{
if (startPoint != null && targetPoint != null)
{
bool isFind = AStarWrapper.Instance.FindPath(startPoint, targetPoint, map, mapWidth, mapHeight);
ClearOldPath();
if (isFind == true)
ShowPath(startPoint, targetPoint);//显示路径
else
Debug.Log("没有可到达的路径");
}
}
private void ShowPath(Point startPoint, Point targetPoint)
{
newPath.Clear();
Point temp = targetPoint.Parent;
while (true)
{
if (temp == startPoint)
{
break;
}
if (temp != null)
{
newPath.Add(temp);
}
temp = temp.Parent;
}
StartCoroutine(PaintnewPath());
}
IEnumerator PaintnewPath()
{
yield return new WaitForEndOfFrame();
if (newPath.Count > 0)
{
for (int i = newPath.Count - 1; i >= 0; i--)
{
SetCubeColor(newPath[i].X, newPath[i].Y, pathPosColor);
yield return new WaitForSeconds(0.05f);
}
}
}
//设置格子颜色
private void SetCubeColor(int x, int y, Color c)
{
map[x, y].gameObject.GetComponent<Renderer>().material.color = c;
}
//设置障碍物
private void SetWall()
{
if (wallList != null)
{
//障碍点不为空,将所有障碍点设置为正常颜色
foreach (Point wall in wallList)
{
wall.IsWall = false;
SetCubeColor(wall.X, wall.Y, normalColor);
}
//清空障碍点
wallList.Clear();
}
while (true)
{
if (wallList.Count >= wallNum)
{
break;
}
//随机生成障碍点
Point p = map[Random.Range(0, mapWidth), Random.Range(0, mapHeight)];
if (wallList.IndexOf(p) == -1)//如果障碍点不存在
{
p.IsWall = true;
SetCubeColor(p.X, p.Y, wallColor);//设置Cube颜色
wallList.Add(p);//添加障碍点
}
}
}
void SelectStartPoint(Vector3 mousePos)
{
Point tmp = PhysicCAst(mousePos);
if (tmp != null)
{
ClearOldStartPoint();
startPoint = tmp;
SetCubeColor(startPoint.X, startPoint.Y, startPosColor);
// 更新路径
FindPathAndShowPath();
}
}
private void SelectTargetPoint(Vector3 mousePos)
{
Point tmp = PhysicCAst(mousePos);
if (tmp != null)
{
ClearOldTargetPoint();
targetPoint = tmp;
SetCubeColor(targetPoint.X, targetPoint.Y, targetPosColor);
// 更新路径
FindPathAndShowPath();
}
}
/// <summary>
/// 重置地图
/// </summary>
private void RefreshMap()
{
ClearOldStartPoint();//清除旧的开始点
ClearOldTargetPoint();//清除旧的目标点
ClearOldPath();//清除路径
oldPath.Clear();//清空旧路径
newPath.Clear();//清除新路径
SetWall();
}
/// <summary>
/// 清除旧的开始点
/// </summary>
void ClearOldStartPoint()
{
if (startPoint != null)
{
if (startPoint == targetPoint)
SetCubeColor(startPoint.X, startPoint.Y, targetPosColor);
else
SetCubeColor(startPoint.X, startPoint.Y, normalColor);
startPoint = null;
}
}
/// <summary>
/// 清除旧目标点
/// </summary>
void ClearOldTargetPoint()
{
if (targetPoint != null)
{
if (targetPoint == startPoint)
SetCubeColor(targetPoint.X, targetPoint.Y, startPosColor);
else
SetCubeColor(targetPoint.X, targetPoint.Y, normalColor);
targetPoint = null;
}
}
/// <summary>
/// 清除旧路径
/// </summary>
void ClearOldPath()
{
oldPath = newPath;
foreach (Point pathItem in oldPath)
{
SetCubeColor(pathItem.X, pathItem.Y, normalColor);
}
// 避免开始点或者目标点在老路径上被清掉,所以重新绘制一次开始点和目标点的颜色
if (startPoint != null) {
SetCubeColor(startPoint.X, startPoint.Y, startPosColor);
}
if (targetPoint != null) {
SetCubeColor(targetPoint.X, targetPoint.Y, targetPosColor);
}
}
/// <summary>
/// 射线检测
/// </summary>
/// <param name="mousePos">鼠标位置</param>
/// <returns>返回一个路点</returns>
Point PhysicCAst(Vector3 mousePos)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo, 20))
{
Transform tmp = hitInfo.collider.transform;
if (map[(int)tmp.position.x, (int)tmp.position.y].IsWall == false)
{
//如果该点不为障碍点,则返回路店
return map[(int)tmp.position.x, (int)tmp.position.y];
}
}
return null;
}
}