如图所示,为了向大家介绍AStar自动寻路算法,我先用一个二维数组作为地图的数据建立了一个如图所示的地图
privateint[,] map0 = new int[,]{
{ 0,0,0,0,0,0,0},
{ 0,1,1,1,1,1,0},
{ 0,1,2,0,1,1,0},
{ 0,1,1,0,1,1,0},
{ 0,1,1,0,1,1,0},
{ 0,1,1,1,1,1,0},
{ 0,0,0,0,0,0,0}
};
二维数组用来显示地图的基本信息是最为简单的一种方式,在游戏中的应用是很多的, 例如贪吃蛇和俄罗斯方块基本原理就是移动方块而已. 而一些大型游戏的地图看起来比较平滑的起始只是进行了些处理, 是将各种"地貌"铺在这样的小方块上. 为了抽象理解看如下这张较为平滑的简易地图作为理解
【问题分析】
1. 已知地图样貌信息和起始位置
2. 从初始方格周围开始计算距离终止方格的信息并进行记录,减少信息冗余
3. 可走动方向为棋面可走8个方向,但斜走时不可有上下左右墙阻挡(路径抽象最小化如1图可知次举动不可为,除非特殊情况的设定。本帖设定不可)
【实现策略】
1. 定义方格的信息(距离重点信息+移动位置点信息(包含前一位置))权值
F = G + H
G: 表示从某位置移动到网格上指定位置点的移动耗费 (距离)—包含前一位置耗费
H: .表示从指定的位置点移动到终点 B 的预计耗费
2. 定义两个容器
(1)“开启列表”保存已检测到和未到达的方格
(2)“关闭列表”保存已到达的过方格
3. 先将起始位置保存到(1),从(1)得到F值最小的并设为A, 再将A从(1)中移除,记录A位置可到达方格并将他们的父亲设为A并计算他们的权值并保存到(1)中。如果从(1)中得到F值最小方格为终点则结束
4. 线性递归 【3】
5. A为终点结束后只需从终点的的父节点开始向上遍历则可得到完整的路径点
【抽象分析】 使用图例加强理解
◆ 假设从绿方格到红方格,将起点加入(1);再从(1)得到F值最小从(1)中移除并设为A。在计算A可到达的方格加入(1)中并计算他们的权值,在将他们的父亲设为A
◆ 再从(1)得到F值最小的从(1)中移除并设为A。在计算A可到达的方格加入(1)中并将他们的父亲设为A再计算他们的权值(需将G值进行累==加上其父亲的G值表示路程信息),如A上面的方格G值原本位置因为其父亲的G值为1所以就为1+1=2
● 从(1)得到F值最小的以第一个为先(否则只需更改算法设定)
◆ 再从(1)得到F值最小的从(1)中移除并设为A。在计算A可到达的方格加入(1)中并将他们的父亲设为A再计算他们的权值
◆ 再从(1)得到F值最小的从(1)中移除并设为A。在计算A可到达的方格加入(1)中并将他们的父亲设为A再计算他们的权值
◆ 再从(1)得到F值最小的从(1)中移除并设为A。如果A为终点则结束
【代码实现】
Point类 --方格信息类
public class Point
{
public Point Parent { get; set; }
public float F { get; set; }
public float G { get; set; }
public float H { get; set; }
public int mapPointX { get; set; }
public int mapPointY { get; set; }
public Vector2 MapVector2 { get; set; }
public bool IsWall { get; set; }
private bool _isVisi;
public bool IsVisi { get { return _isVisi && !IsWall; } set { _isVisi = value; } }
public Point(int mapX, int mapY, Point parent = null)
{
this.mapPointX = mapX;
this.mapPointY = mapY;
MapVector2 = new Vector2(mapPointX, mapPointY);
this.Parent = parent;
IsWall = false;
IsVisi = true;
}
public Point(bool isVisi = false)
{
IsVisi = isVisi;
}
public void UpdateParent(Point parent, float g)
{
this.Parent = parent;
this.G = g;
F = G + H;
}
}
AStar类 --实现类
public class Astar :MonoBehaviour{
private const float COST_HOR = 1f;
private const float COST_VER = 1f;
private const float COST_DIA = 2f;
private static int mapWidth;
private static int mapHeight;
private static Point[,] map; //数据地图
//private static List<GameObject> objList = new List<GameObject>(); //最佳移动点集
private static List<Point> closeList = new List<Point>();
//得到传入的数据--初始化数据地图--计算最佳移动点集--返回最佳移动点集
public static Vector3[] StartMake (int[,] map0,Vector3Int sPos, Vector3Int ePos)
{
if(sPos == ePos) return new Vector3[1] { sPos };
if (sPos.x > map0.GetLength(0) || ePos.x > map0.GetLength(0) || sPos.y > map0.GetLength(1) || ePos.y > map0.GetLength(1)) {
Debug.LogError("["+sPos.x + "," + sPos.y+"]["+ ePos.x+","+ ePos.y+"]");
Debug.LogError("数组索引超出范围");
return new Vector3[1] { sPos };
}
InitMap(map0, sPos, ePos);
Point start = map[sPos.x, sPos.y];
Point end = map[ePos.x, ePos.y];
if (FindPath(start, end))
{
return GetPathPoints(start, end); //返回最佳移动点集
}
else
{
return new Vector3[1] { sPos };
}
}
//初始化数据地图
static void InitMap(int[,] map0, Vector3Int start, Vector3Int end)
{
mapWidth = map0.GetLength(0);
mapHeight = map0.GetLength(1);
//print(mapWidth + " " + mapHeight);
map = new Point[mapWidth, mapHeight];
for (int x = 0; x < mapWidth; x++) {
for (int y = 0; y < mapHeight; y++) {
map[x,y] = new Point(x,y);
if(map0[x,y] == 0)
map[x, y].IsWall = true;
}
}
}
//计算最佳移动点集
static bool FindPath(Point start, Point end) {
List<Point> openList = new List<Point>();
//List<Point> closeList = new List<Point>();
closeList.Clear();
openList.Add(start);
while (openList.Count > 0) {
Point point = GetMinF(openList);
openList.Remove(point);
closeList.Add(point);
List<Point> surroundPoints = GetGroundPoint(point);
GetSurroundPoints(surroundPoints, closeList);
foreach (Point surroundPoint in surroundPoints) {
if (openList.IndexOf(surroundPoint) > -1)
{
float nowG = CalcG(surroundPoint, point);
if (nowG < point.G)
{
surroundPoint.UpdateParent(point, nowG);
}
}
else {
surroundPoint.Parent = point;
CalcFGH(surroundPoint, end);
FindAndSort(openList, surroundPoint);
}
}
if (openList.IndexOf(end) > -1)
{
return true;
}
else if (openList.Count == 0)
{
break;
}
}
return false;
}
static void FindAndSort(List<Point> openList, Point point)
{
bool IsInsert = true;
foreach (Point pointT in openList)
{
if (point.mapPointX == pointT.mapPointX && point.mapPointY == pointT.mapPointY)
{
IsInsert = false;
}
}
if (IsInsert)
{
openList.Add(point);
}
}
static void GetSurroundPoints(List<Point> src, List<Point> closeList) {
foreach (Point p in closeList) {
if (src.IndexOf(p) > -1)
{
src.Remove(p);
}
}
}
static List<Point> GetGroundPoint(Point point) {
Point up = null, down = null, right = null, left = null;
Point uR = null, uL = null, dR = null, dL = null;
List<Point> tempList = new List<Point>();
//上下左右
if (point.mapPointY < mapHeight - 1) up = map[point.mapPointX, point.mapPointY + 1];
else up = new Point();
if (point.mapPointY > 0) down = map[point.mapPointX, point.mapPointY - 1];
else down = new Point();
if (point.mapPointX < mapWidth - 1) right = map[point.mapPointX + 1, point.mapPointY];
else right = new Point();
if (point.mapPointX > 0) left = map[point.mapPointX - 1, point.mapPointY];
else left = new Point();
//上右 上左 下右 下左
if (up.IsVisi && right.IsVisi) uR = map[point.mapPointX + 1, point.mapPointY + 1];
else uR = new Point();
if (up.IsVisi && left.IsVisi) uL = map[point.mapPointX - 1, point.mapPointY + 1];
else uL = new Point();
if (down.IsVisi && right.IsVisi) dR = map[point.mapPointX + 1, point.mapPointY - 1];
else dR = new Point();
if (down.IsVisi && left.IsVisi) dL = map[point.mapPointX - 1, point.mapPointY - 1];
else dL = new Point();
if (up.IsVisi) tempList.Add(up);
if (down.IsVisi) tempList.Add(down);
if (right.IsVisi) tempList.Add(right);
if (left.IsVisi) tempList.Add(left);
if (uR.IsVisi && up.IsVisi && right.IsVisi) tempList.Add(uR);
if (uL.IsVisi && up.IsVisi && left.IsVisi) tempList.Add(uL);
if (dR.IsVisi && down.IsVisi && right.IsVisi) tempList.Add(dR);
if (dL.IsVisi && down.IsVisi && right.IsVisi) tempList.Add(dL);
return tempList;
}
static Point GetMinF(List<Point> tempList) {
Point point = null;
float minPoint = float.MaxValue;
for (int i = tempList.Count - 1; i >= 0; i--) {
if (tempList[i].F <= minPoint)
{
minPoint = tempList[i].F;
point = tempList[i];
}
}
return point;
}
static void CalcFGH(Point now, Point end) {
// F =G + H
float h = Mathf.Abs(end.mapPointX - now.mapPointX) + Mathf.Abs(end.mapPointY - now.mapPointY);
float g;
if (now.Parent == null)
{
g = 0;
}
else
{
float disance = Vector2.Distance(now.Parent.MapVector2, now.MapVector2);
float disanceZ = Mathf.RoundToInt(disance);
if (disance - disanceZ != 0)
{
g = disance * COST_DIA + now.Parent.G;
}
else
{
if (now.Parent.mapPointX == now.mapPointX && now.Parent.mapPointY != now.mapPointY)
g = disance * COST_HOR + now.Parent.G;
else
g = disance * COST_VER + now.Parent.G;
}
}
now.G = g;
now.H = h;
now.F = g + h;
}
static float CalcG(Point now, Point parent) {
return Vector2.Distance(now.MapVector2, parent.MapVector2) + parent.G;
}
static Vector3[] GetPathPoints(Point start, Point end)
{
List<Vector3> path = new List<Vector3>();
Vector3[] pathPoints;
Point point = end;
while (true)
{
path.Add(new Vector3(point.mapPointX, point.mapPointY,0));
point = point.Parent;
if (point == null)
break;
}
pathPoints = new Vector3[path.Count];
for (int i = 1; i <= path.Count; i++)
{
pathPoints[i - 1] = path[path.Count - i];
}
return pathPoints;
}
}
AStar 辅助方法测试用
#region AStarPaint
List<Vector3> GetPathPoints(Point start, Point end) {
foreach (GameObject obj in objList) {
Destroy(obj);
}
objList.Clear();
for (int x = 0; x < mapWidth; x++)
{
for (int y = 0; y < mapHeight; y++)
{
if (map[x, y].IsWall)
{
PaintSceneCube(x+ mapWidth, y, Color.black);
}
PaintTest(x + mapWidth, y, map[x, y]);
}
}
List<Vector3> path = new List<Vector3>();
List<Vector3> path0 = new List<Vector3>();
Point point = end;
while (true) {
path.Add(new Vector3(point.mapPointX, 0.5f, point.mapPointY));
Color color = Color.gray;
if (point == start)
{
color = Color.green;
}
if (point == end)
{
color = Color.red;
}
PaintSceneCube(point.mapPointX+ mapWidth, point.mapPointY, color,point);
point = point.Parent;
if (point == null)
break;
}
for (int i=1;i<= path.Count;i++) {
path0.Add(path[path.Count - i]);
}
return path0;
}
void PaintSceneCube(int x, int y, Color color)
{
GameObject gameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
gameObject.transform.position = new Vector3(x, 0, y);
gameObject.transform.SetParent(GameObject.Find("GameFaced").transform);
gameObject.GetComponent<Renderer>().material.color = color;
objList.Add(gameObject);
}
void PaintSceneCube(int x, int y, Color color,Point point)
{
GameObject gameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
gameObject.transform.position = new Vector3(x, 0, y);
gameObject.transform.SetParent(GameObject.Find("GameFaced").transform);
gameObject.GetComponent<Renderer>().material.color = color;
objList.Add(gameObject);
}
void PaintTest(int x, int y, Point point) {
if (point.F <= 0)
return;
GameObject testObject = Instantiate(testObj);
testObject.transform.position = new Vector3(x, 0.7f, y);
testObject.transform.SetParent(GameObject.Find("GameFaced").transform);
testObject.transform.Find("F").GetComponent<Text>().text = Mathf.RoundToInt(point.F).ToString();
testObject.transform.Find("G").GetComponent<Text>().text = Mathf.RoundToInt(point.G).ToString();
testObject.transform.Find("H").GetComponent<Text>().text = Mathf.RoundToInt(point.H).ToString();
objList.Add(testObject);
}
#endregion
主控制类
public class GameManager : MonoBehaviour {
private int[,] map0 = new int[,]{
{ 0,0,0,0,0,0,0},
{ 0,1,1,1,1,1,0},
{ 0,1,2,0,1,1,0},
{ 0,1,1,0,1,1,0},
{ 0,1,1,0,1,1,0},
{ 0,1,1,1,1,1,0},
{ 0,0,0,0,0,0,0}
};
#region AStar
private Astar astar = new Astar();
public GameObject testObj;
#endregion
public GameObject wallObject;
public GameObject planeObject;
public GameObject gamePlayerObject;
private GameObject gamePlayer = null;
private List<Vector3> targetPos = new List<Vector3>();
void Start () {
Init();
}
// Update is called once per frame
void Update () {
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
if (hit.collider.tag == "Plane")
{
Vector3 temp = new Vector3(hit.transform.position.x,0.5f, hit.transform.position.z);
AtarGetPos(temp);
//targetPos.Add(temp);
}
}
}
}
private void FixedUpdate()
{
PlayerMove(targetPos);
}
void Init() {
for (int i = 0; i < map0.GetLength(0); i++)
{
for (int j = 0; j < map0.GetLength(1); j++)
{
switch (map0[i,j])
{
case 0:
Instantiate(wallObject, new Vector3(i, 0, j), Quaternion.identity, transform);
break;
case 1:
Instantiate(planeObject, new Vector3(i, 0, j), Quaternion.identity, transform);
break;
case 2:
Instantiate(planeObject, new Vector3(i, 0, j), Quaternion.identity, transform);
gamePlayer = Instantiate(gamePlayerObject, new Vector3(i, 0.5f, j), Quaternion.identity);
break;
default:
break;
}
}
}
}
void PlayerMove(List<Vector3> vector3) {
if (vector3.Count > 0)
{
Vector3 pos = new Vector3((int)vector3[0].x, 0.5f, (int)vector3[0].z);
gamePlayer.transform.position = pos;
vector3.Remove(vector3[0]);
}
}
void AtarGetPos(Vector3 temp) {
targetPos.Clear();
Vector3 pos = new Vector3((int)gamePlayer.transform.position.x, 0.5f, (int)gamePlayer.transform.position.z);
targetPos = astar.StartMake(map0, gamePlayer.transform.position, temp, testObj);
}
}
【效果演示】 右边为AStar 辅助方法测试用
【工程代码】
*提示:本博文中的脚本可能会与工程代码中的有些许不同以工程代码中的为准
链接: 码云仓库