unity:完成A星寻路算法的基本实现
参考了视频来实现A星寻路算法,居然还花费了2天时间,每次只要一动手就会出现无数的坑,对自己产生了深深的怀疑,故决定写博客记录下来。
参考视频:https://www.bilibili.com/video/BV147411u7r5?t=477&p=4
代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; using SGF; using UnityEngine.Tilemaps; //测试A星寻路算法 public class TestAstar : MonoBehaviour { public Tilemap tilemap; public Tile tile0; public Tile tile1; public Tile tile2; public List<AStar> PathList = new List<AStar>(); public List<GameObject> path = new List<GameObject>(); // Start is called before the first frame update void Start() { AStarManeger aStarManeger = new AStarManeger(); aStarManeger.InitAStarManeger(new Vector2(20,20),tilemap,tile0); PathList = aStarManeger.FindPath(aStarManeger.Nodes[5,2], aStarManeger.Nodes[12, 15]); tilemap.SetTile(new Vector3Int(5,2,0), tile1); tilemap.SetTile(new Vector3Int(12, 15, 0), tile1); for (int i=0;i<PathList.Count;i++) { tilemap.SetTile(new Vector3Int((int)PathList[i].Postion.x, (int)PathList[i].Postion.y, 1), tile2); Debug.Log(PathList[i].Postion); } } // Update is called once per frame void Update() { } } //格子类型 public enum E_Node_Type { Walk, Stop, } public class AStar { public Vector2 Postion; public float f; public float g; public float h; public AStar parent; public E_Node_Type Type; public AStar(Vector2 postion,E_Node_Type type) { Postion = postion; Type = type; } } public class AStarManeger : Singleton<AStarManeger> { public AStar[,] Nodes;//所有寻路格子 private List<AStar> OpenList = new List<AStar>(); private List<AStar> CloseList = new List<AStar>(); public Vector2 MapSize; public void InitAStarManeger(Vector2 mapSize, Tilemap tilemap, Tile tile) { //生成地图,生成寻路格子 MapSize = mapSize; Nodes = new AStar[(int)mapSize.x, (int)mapSize.y]; for (int i = 0; i < mapSize.x; i++) { for (int j = 0; j < mapSize.y; j++) { Nodes[i, j] = new AStar(new Vector2(i, j), Random.Range(0, 10) < 4 ? E_Node_Type.Stop : E_Node_Type.Walk) { f = 10000, g = 0, h = 0, parent = null }; if ((i == 5 && j == 2) || (i == 12 && j == 15)) { Nodes[i, j].Type = E_Node_Type.Walk; } if (Nodes[i, j].Type == E_Node_Type.Walk) { tilemap.SetTile(new Vector3Int(i, j, 0), tile); } else { tilemap.SetTile(new Vector3Int(i, j, 0), null); } } } } public List<AStar> FindPath(AStar start, AStar end) { OpenList.Clear(); CloseList.Clear(); //判断起点和终点是否是阻挡格子 if (start.Type == E_Node_Type.Stop|| end.Type == E_Node_Type.Stop) { return null; } //判断起点和终点是否在地图范围内 if (0 < start.Postion.x && start.Postion.x < MapSize.x && 0 < start.Postion.y && start.Postion.y < MapSize.y) { } else { this.LogWarning("起点不在地图范围内,MapSize:{0},{1} Start:{2},{3}", MapSize.x, MapSize.y, start.Postion.x, start.Postion.y); return null; } if (0 < end.Postion.x && end.Postion.x < MapSize.x && 0 < end.Postion.y && end.Postion.y < MapSize.y) { } else { this.LogWarning("终点不在地图范围内,MapSize:{0},{1} Start:{2},{3}", MapSize.x, MapSize.y, start.Postion.x, start.Postion.y); return null; } //添加起点到开启列表 OpenList.Add(start); var point = start; //寻路循环 while (point.Postion != end.Postion) { //添加当前格子四周的格子到开启列表 AroundToOpenList4(point,start, end); //死路判断(开启列表是否为空) if (OpenList.Count == 0) { Debug.Log("死路"); break; } //将开启列表排序并将f最小的格子放入关闭列表 OpenList.Sort(SortOpenList); var min = OpenList[0]; point = min; //min.parent = point;//错误代码,二次赋值父物体,造成回溯时错误,出现多余路径 //寻路完成判断 if (min == end) { break; } CloseList.Add(min); OpenList.RemoveAt(0); } //回溯最终路径 var aStar = end; var PathList = new List<AStar>(); while (aStar.parent != null) { PathList.Add(aStar); aStar = aStar.parent; } PathList.Reverse(); return PathList; } int SortOpenList(AStar a, AStar b) { if (a.f >= b.f) { return 1; } else { return -1; } } void AddToOpenList(int i, int j, float g, AStar father,AStar start, AStar end) { //判断是否在地图外 if (i < 0 || j < 0 || i >= MapSize.x || j >= MapSize.y) { return; } //判断是否阻挡 if (Nodes[i, j].Type == E_Node_Type.Stop) { return; } //判断是否是起点 if (Nodes[i,j]==start) { return; } //判断是否在开启列表和关闭列表 if (OpenList.Contains(Nodes[i, j]) || CloseList.Contains(Nodes[i, j])) { return; } Nodes[i, j].parent = father; Nodes[i, j].g = g; Nodes[i, j].h = Mathf.Abs((Nodes[i, j].Postion - end.Postion).x) + Mathf.Abs((Nodes[i, j].Postion - end.Postion).y); Nodes[i, j].f = Nodes[i, j].g + Nodes[i, j].h; OpenList.Add(Nodes[i, j]); } //添加周围8格到开启列表 void AroundToOpenList8(AStar point,AStar start,AStar end) { AddToOpenList((int)point.Postion.x - 1, (int)point.Postion.y - 1, 1.4f, point, start, end); AddToOpenList((int)point.Postion.x - 1, (int)point.Postion.y, 1, point, start, end); AddToOpenList((int)point.Postion.x - 1, (int)point.Postion.y + 1, 1.4f, point, start, end); AddToOpenList((int)point.Postion.x, (int)point.Postion.y - 1, 1, point, start, end); AddToOpenList((int)point.Postion.x, (int)point.Postion.y + 1, 1, point, start, end); AddToOpenList((int)point.Postion.x + 1, (int)point.Postion.y - 1, 1.4f, point, start, end); AddToOpenList((int)point.Postion.x + 1, (int)point.Postion.y, 1, point, start, end); AddToOpenList((int)point.Postion.x + 1, (int)point.Postion.y + 1, 1.4f, point, start, end); } //添加周围4格到开启列表 void AroundToOpenList4(AStar point,AStar start,AStar end) { AddToOpenList((int)point.Postion.x - 1, (int)point.Postion.y, 1, point, start, end); AddToOpenList((int)point.Postion.x, (int)point.Postion.y - 1, 1, point, start, end); AddToOpenList((int)point.Postion.x, (int)point.Postion.y + 1, 1, point, start, end); AddToOpenList((int)point.Postion.x + 1, (int)point.Postion.y, 1, point, start, end); } }
实现效果:
- 死路
- 只能从x,y方向走
- 可以从斜向方向走
-
总结:因为不是完全照着视频来写,而是看了原理后自己实现,还是暴露出来了自己的很多问题
- 一开始,在初始化寻路格子时将f的值设定为了0,并且将起点格子加入了开启列表,而起点格子的f在之后不会再改变,导致在对开启列表排序时起点排在了最小位置,导致对起点的父物体进行了赋值,最终在回溯时形成了死循环,导致内存溢出。解决方法是,不要把起点加入开启列表,或者将起点的f值设的尽量大;
- 对开启列表中的f最小的格子的父物体进行了二次赋值,造成回溯时错误,出现多余路径;(这里是对A星寻路的最终路径回溯的原理没有完全理解导致的)。