这几天学习了一下A*寻路,这里记录一下代码,网上有很多详细的教程,代码里注释很详细。这里就不赘述了,直接上代码(萌新一枚,如有错还请指出!一起学习!)
首先是格子对象类
/// <summary> /// 格子对象类 /// </summary> public class AstarNode { public int x;//格子坐标信息 X Y public int y; public float f;//格子消耗信息 public float g; public float h; public AstarNode parent;//格子父对象 public NodeType nodeType;//格子类型 public AstarNode(){}//构造函数 public AstarNode(int x,int y,NodeType type) { this.x = x; this.y = y; this.nodeType = type; } } public enum NodeType { Wlak, Stop, }然后是核心管理类
/// <summary> /// A*寻路核心类 /// </summary> public class AstarManager : MonoBehaviour { private static AstarManager manager; public static AstarManager Manager { get { //if (manager == null) //{ // manager = this; //} return manager; } } private void Awake() { manager = this; } //地图最大高度 public int maxHight; //地图最大宽度 public int maxWidth; //获取场景网格父对象 public Transform NodeParent; //存储格子的二维数组 private AstarNode[,] nodes; //开启列表 private List<AstarNode> openList = new List<AstarNode>(); //关闭列表 private List<AstarNode> closeList = new List<AstarNode>(); //存储场景中的格子对象 private List<RectTransform> nodeObj = new List<RectTransform>(); /// <summary> /// 初始化地图大小 /// </summary> /// <param name="w">宽</param> /// <param name="h">高</param> public void InitMap(int w, int h) { //获取场景中的格子父对象 NodeParent = GameObject.Find("Canvas/BG").GetComponent<Transform>(); //设置高 maxHight = h; //设置宽 maxWidth = w; //初始化node二维数组 nodes = new AstarNode[w, h]; for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { //生成格子对象,并存入二维数组 nodes[i, j] = new AstarNode(i, j, Random.Range(0, 100) < 20 ? NodeType.Stop : NodeType.Wlak); //------------------在场景中生成对象 GameObject temp = Instantiate<GameObject>(Resources.Load<GameObject>("node")); temp.transform.SetParent(NodeParent); temp.transform.localScale = Vector3.one; //这里*105是因为格子在数值里是与场景里的格子大小不一样,如格子(1,1)位置,场景里一个对象大小我设置的是宽高都是100, //所以这里位置是宽高分别 *100,写105是想保持间隔 ((temp.transform) as RectTransform).anchoredPosition = new Vector3(i * 105, j * 105, 0); nodeObj.Add(temp.GetComponent<RectTransform>()); if (nodes[i, j].nodeType==NodeType.Stop) { //如果格子是障碍 则改变颜色为黑色 temp.GetComponent<Image>().color = Color.black; } //------------------在场景中生成对象 } } } /// <summary> /// 开始寻路 /// </summary> /// <param name="startPos">起点</param> /// <param name="endPos">终点</param> /// <returns></returns> public List<AstarNode> FindPath(Vector2 startPos, Vector2 endPos) { //判断起点终点是否合法,这里是不能超过地图 if (startPos.x >= maxWidth || startPos.y >= maxHight || startPos.x < 0 || startPos.y < 0 || endPos.x >= maxWidth || endPos.y >= maxHight || endPos.x < 0 || endPos.y < 0) { Debug.Log("起点或终点可能已超过地图大小"); return null; } AstarNode starNode = nodes[(int)startPos.x, (int)startPos.y]; AstarNode endNode = nodes[(int)endPos.x, (int)endPos.y]; //这里拿到格子信息后继续判断格子是否合法,起点或终点格子不能是障碍物 if (starNode.nodeType == NodeType.Stop || endNode.nodeType == NodeType.Stop) { Debug.Log("起点或终点可能是障碍物"); return null; } //此方法可能多次调用,所以要清空开启和关闭列表 openList.Clear(); closeList.Clear(); //初始化起点信息,父对象与寻路消耗 并加入关闭列表 starNode.parent = null; starNode.f = 0; starNode.h = 0; starNode.g = 0; closeList.Add(starNode); //-----------------------------这里是在场景中改变其颜色,标记出起点和终点的位置 for (int i = 0; i < nodeObj.Count; i++) { if (new Vector2(starNode.x * 105, starNode.y * 105) == nodeObj[i].anchoredPosition) { nodeObj[i].GetComponent<Image>().color = Color.red; continue; } if (new Vector2(endNode.x * 105, endNode.y * 105) == nodeObj[i].anchoredPosition) { nodeObj[i].GetComponent<Image>().color = Color.blue; continue; } } //----------------------------- //死循环开始分别计算起点周围8个方位的格子, while (true) { //top CheckNode(starNode.x, starNode.y + 1, 1, starNode, endNode); //down CheckNode(starNode.x, starNode.y - 1, 1, starNode, endNode); //left CheckNode(starNode.x - 1, starNode.y, 1, starNode, endNode); //right CheckNode(starNode.x + 1, starNode.y, 1, starNode, endNode); //topleft CheckNode(starNode.x - 1, starNode.y - 1, 1.4f, starNode, endNode); //downleft CheckNode(starNode.x - 1, starNode.y + 1, 1.4f, starNode, endNode); //topright CheckNode(starNode.x + 1, starNode.y + 1, 1.4f, starNode, endNode); //downright CheckNode(starNode.x + 1, starNode.y - 1, 1.4f, starNode, endNode); //死路判断,当开启列表为空时,说明已经找完了地图上所有的格子,此时便是死路,所有退出循环 if (openList.Count == 0)return null; //对开启列表进行排序,找到消耗最小的格子 openList.Sort(SortOpenList); //将其加入关闭列表 closeList.Add(openList[0]); //设置为下一个起点 starNode = openList[0]; //并从开启列表里 移除 openList.RemoveAt(0); //结束判断,如果当前起点等于终点,说明已经寻路完成,开始导出正确路径 if (starNode == endNode) { //这里的思路是新建一个格子列表,终点开始,向其中加入父对象,加入后变更终点对象,以此根据父对象找到正确的路径 //切忌不可使用关闭列表里的格子当做路径,因为关闭列表里除了有正确的路径还有可能存在其它路径 List<AstarNode> path = new List<AstarNode>(); path.Add(endNode); while (endNode.parent != null) { path.Add(endNode.parent); endNode = endNode.parent; } //这里反转数组是因为我们是从最后一个开始找的,如:6 5 4 3 2 1 所以反转过来就是正确的路径 path.Reverse(); //----------------------------------------------这里是在场景中绘制出正确的路径 for (int i = 0; i < path.Count; i++) { for (int j = 0; j < nodeObj.Count; j++) { if (new Vector2(path[i].x * 105, path[i].y * 105) == nodeObj[j].anchoredPosition) { if (nodeObj[j].GetComponent<Image>().color == Color.white) { nodeObj[j].GetComponent<Image>().color = Color.yellow; continue; } } } } //---------------------------------------------- //最后返回路径格子信息 return path; } } } /// <summary> /// 查找某个点周围的格子消耗信息,并加入开启列表 /// </summary> /// <param name="x">坐标X</param> /// <param name="y">坐标Y</param> /// <param name="g">拟消耗值</param> /// <param name="parent">其父对象</param> /// <param name="endNode">终点格子信息</param> public void CheckNode(int x, int y, float g, AstarNode parent, AstarNode endNode) { //node合法判断,是否超出边界 if (x < 0 || x >= maxWidth || y < 0 || y >= maxHight) return; AstarNode node = nodes[x, y]; //node合法判断,是否为空,是否为障碍物,是否已经存在于Openlist或Closelist if (node == null || node.nodeType == NodeType.Stop || openList.Contains(node) || closeList.Contains(node)) return; //赋予父节点 node.parent = parent; //计算自己的g node.g = parent.g + g; //计算自己的h 为终点的x,y减去自己的x,y相加,这里取绝对值,因为可能是负数 node.h = Mathf.Abs(endNode.x - node.x) + Mathf.Abs(endNode.y - node.y); //计算最终消耗 node.f = node.g + node.h; //加入开启列表 openList.Add(node); } //重写一个比较方法,方便传入sort进行排序 private int SortOpenList(AstarNode a, AstarNode b) { if (a.g > b.g) { return 1; } else if (a.g == b.g) { return 1; } else { return -1; } } }效果图 这里是会从2个障碍物直接穿过去 如下图, 按道理这种也应该是不存在的,后续修改了我会再上传的 --------------------------------------------------7.3更新-------------------------------- 之前说到的斜着走的问题,现在解决了,用了比较笨的方法 在原来的节点判断的合法检测部分加上新的方法,并且多了一个bool参数,是否进行斜走判断,默认是false
/// <summary> /// 判断某格是否可以斜着走 /// </summary> /// <param name="x">X轴</param> /// <param name="y">Y轴</param> /// <returns></returns> public bool CheckNodeType(int x, int y, AstarNode star) { //判断起点终点是否合法,这里是不能超过地图 if (x >= maxWidth || y >= maxHight || x < 0 || y < 0 || x >= maxWidth || y >= maxHight || x < 0 || y < 0) { Debug.Log("起点或终点可能已超过地图大小"); return false; } AstarNode starNode = nodes[x, y]; //这里拿到格子信息后继续判断格子是否是障碍 if (starNode.nodeType == NodeType.Stop) return false; //这里根据xy判断在那个斜方向,然后进行对应的判断 if (x > star.x && y > star.y) { if (nodes[x - 1, y].nodeType == NodeType.Stop && nodes[x, y - 1].nodeType == NodeType.Stop) { return false; } else { return true; } //右上 } if (x < star.x && y > star.y) { if (nodes[x + 1, y].nodeType == NodeType.Stop && nodes[x, y - 1].nodeType == NodeType.Stop) { return false; } else { return true; } //左上 } if (x < star.x && y < star.y) { if (nodes[x + 1, y].nodeType == NodeType.Stop && nodes[x, y + 1].nodeType == NodeType.Stop) { return false; } else { return true; } //左下 } if (x > star.x && y < star.y) { if (nodes[x - 1, y].nodeType == NodeType.Stop && nodes[x, y + 1].nodeType == NodeType.Stop) { return false; } else { return true; } //右下 } return false; }效果如上,不会再直接走过有障碍的斜角了