逻辑思路
- 对起点和终点的合法性进行判断
1、起点和终点需要在地图范围内
2、起点和终点其中一个不能是不可通过点
3、起点和终点不能是同一个点 - 清空上次寻路的信息
1、将开放列表和关闭列表中的格子信息重置并清空开放列表和关闭列表
2、将起点添加到关闭列表中,并设置其父节点信息为null - 寻路逻辑
1、将起点设置为寻路逻辑起点A
2、将A周围的8个符合条件的格子加入开放列表中,将A设置为其父节点并计算每个格子的消耗值h
条件:
(1)格子在地图范围内
(2)格子是可通过的
(3)格子未在开放列表和关闭列表中
3、将开放列表根据消耗值(h)进行升序排序。
4、如果开放列表为空,表示起点到终点的道路不通,寻路逻辑终止
如果开放列表不为空,将开放列表中的第一个(即h值最小的)格子从开放列表移入到关闭列表中
5、如果取出来的格子跟终点是一个,表示寻路结束,从终点开始,顺着其父节点往上找,直到顺到起点结束,这就是寻路的路径(注意该路径是从终点到起点的顺序,最后需要对该列表进行反转)
如果取出来的格子不是终点,将该格子设置为寻路逻辑的起点A,继续步骤2,直到满足步骤5的第一种情况
源码
using System.Collections.Generic;
using UnityEngine;
public class AStarTest : MonoBehaviour
{
public float startX;
public float startY;
public float gapX = 2;
public float gapY = 2;
public int rows = 5;
public int cols = 5;
public Material yellowMat;
public Material redMat;
public Material whiteMat;
public Material greenMat;
private Dictionary<string, MeshRenderer> gridsGo = new Dictionary<string, MeshRenderer>();
void Start()
{
AStarMgr.GetInstance().Init(rows, cols);
InitGridGo();
ResetGridGoDisplay();
}
void InitGridGo()
{
GameObject go;
string gridIdStr;
for (int i = 0; i < cols; i++)
{
for (int j = 0; j < rows; j++)
{
go = GameObject.CreatePrimitive(PrimitiveType.Cube);
go.transform.position = new Vector3(startX + i * gapX, startY + j * gapY, 0);
gridIdStr = AStarMgr.GetInstance().getGridIdStr(i, j);
go.name = gridIdStr;
gridsGo[gridIdStr] = go.GetComponent<MeshRenderer>();
}
}
}
void ResetGridGoDisplay()
{
GridInfo gridInfo;
foreach (var item in gridsGo)
{
if (AStarMgr.GetInstance().GetAllGrids().TryGetValue(item.Key, out gridInfo))
{
GridType gridType = gridInfo.gridType;
if (gridType == GridType.Unwalkable)
{
item.Value.material = redMat;
}
else if(gridType == GridType.Walkable)
{
item.Value.material = whiteMat;
}
}
}
}
Vector2 start = Vector2.left;
Vector2 end = Vector2.left;
void Update()
{
if(Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit raycastInfo;
if(Physics.Raycast(ray, out raycastInfo, 1000))
{
GameObject hitGo = raycastInfo.collider.gameObject;
string hitGoName = hitGo.name;
string[] tmp = hitGoName.Split('_');
MeshRenderer mesh = gridsGo[hitGoName];
if (start == Vector2.left)
{
start = new Vector2(int.Parse(tmp[0]), int.Parse(tmp[1]));
mesh.material = yellowMat;
}
else if(end == Vector2.left)
{
end = new Vector2(int.Parse(tmp[0]), int.Parse(tmp[1]));
mesh.material = yellowMat;
List<GridInfo> result = AStarMgr.GetInstance().GetPath(start, end);
foreach(var item in result)
{
mesh = gridsGo[AStarMgr.GetInstance().getGridIdStr(item.x, item.y)];
mesh.material = greenMat;
}
}
else
{
ResetGridGoDisplay();
start = new Vector2(int.Parse(tmp[0]), int.Parse(tmp[1]));
mesh.material = yellowMat;
end = Vector2.left;
}
}
}
}
}
public enum GridType
{
Walkable = 1,
Unwalkable = 2,
}
public class GridInfo
{
public GridType gridType;
public GridInfo father;
public int x;
public int y;
public float f;
public float g;
public float h;
public GridInfo(int x, int y, GridType gridType)
{
this.gridType = gridType;
this.x = x;
this.y = y;
}
public void Reset()
{
f = 0;
g = 0;
h = 0;
father = null;
}
}
using System.Collections.Generic;
using UnityEngine;
public class AStarMgr
{
private static AStarMgr _instance;
public static AStarMgr GetInstance()
{
if (_instance == null)
{
_instance = new AStarMgr();
}
return _instance;
}
private Dictionary<string, GridInfo> grids;
private int rows;
private int cols;
private List<GridInfo> openList = new List<GridInfo>();
private List<GridInfo> closeList = new List<GridInfo>();
private GridInfo startGridInfo;
private GridInfo endGridInfo;
public void Init(int rows, int cols)
{
this.rows = rows;
this.cols = cols;
if (grids == null)
{
grids = new Dictionary<string, GridInfo>();
}
grids.Clear();
for (int i = 0; i < cols; i++)
{
for (int j = 0; j < rows; j++)
{
grids[getGridIdStr(i, j)] = new GridInfo(i, j, Random.Range(0, 100) < 20 ? GridType.Unwalkable : GridType.Walkable);
}
}
}
public List<GridInfo> GetPath(Vector2 start, Vector2 end)
{
if (start == null || end == null)
return null;
if ( start == end
|| start.x < 0 || start.x >= cols || start.x < 0 || start.y >= rows
|| end.x < 0 || end.x >= cols || end.x < 0 || end.y >= rows)
return null;
startGridInfo = this.getGridInfo((int)start.x, (int)start.y);
endGridInfo = this.getGridInfo((int)end.x, (int)end.y);
if (startGridInfo == null || startGridInfo.gridType == GridType.Unwalkable
|| endGridInfo == null || endGridInfo.gridType == GridType.Unwalkable)
return null;
foreach(var item in openList)
{
item.Reset();
}
openList.Clear();
foreach(var item in closeList)
{
item.Reset();
}
closeList.Clear();
startGridInfo.Reset();
closeList.Add(startGridInfo);
return FindPath();
}
private List<GridInfo> FindPath()
{
GridInfo start = startGridInfo;
while(true)
{
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
if (!(i == 0 && j == 0))
{
PutGridIntoOpenList(start.x + i, start.y + j, start);
}
}
}
if (openList.Count == 0)
{
Debug.Log("找不到路径");
return null;
}
openList.Sort((a, b) => a.f < b.f ? -1 : 1);
GridInfo minCostGird = openList[0];
closeList.Add(minCostGird);
openList.RemoveAt(0);
if (minCostGird == endGridInfo)
{
List<GridInfo> result = new List<GridInfo>();
result.Add(minCostGird);
while (minCostGird.father != null)
{
result.Add(minCostGird.father);
minCostGird = minCostGird.father;
}
result.Reverse();
return result;
}
else
{
start = minCostGird;
}
}
}
private void PutGridIntoOpenList(int x, int y, GridInfo father)
{
if (x < 0 || x >= cols || y < 0 || y >= rows)
return;
GridInfo gridInfo = getGridInfo(x, y);
if (gridInfo == null || gridInfo.gridType == GridType.Unwalkable)
return;
if (openList.Contains(gridInfo) || closeList.Contains(gridInfo))
return;
gridInfo.father = father;
float g = 0;
if(x == father.x || y == father.y)
{
g = 1;
}
else
{
g = 1.414f;
}
float h = Mathf.Abs(endGridInfo.x - x) + Mathf.Abs(endGridInfo.y - y);
gridInfo.f = father.g + g + h;
openList.Add(gridInfo);
}
public Dictionary<string, GridInfo> GetAllGrids()
{
return grids;
}
public string getGridIdStr(int x, int y)
{
return $"{x}_{y}";
}
private GridInfo getGridInfo(int x, int y)
{
string gridIdStr = getGridIdStr(x, y);
if(grids.ContainsKey(gridIdStr))
{
return grids[gridIdStr];
}
return null;
}
}
结果演示