当前位置:   article > 正文

Unity 中的简单A*寻路 (AStar寻路)实现

Unity 中的简单A*寻路 (AStar寻路)实现

本文实现的A*算法,未经过大量的优化,后续文章会进一步实现优化

后篇:A*优化讨论

寻路代码实现

结点类:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public enum E_Node_Type
  5. {
  6. /// <summary>
  7. /// 可行走
  8. /// </summary>
  9. Normal,
  10. /// <summary>
  11. /// 障碍
  12. /// </summary>
  13. Obstacles
  14. }
  15. public class AStarNode
  16. {
  17. /// <summary>
  18. /// x坐标
  19. /// </summary>
  20. public int x;
  21. /// <summary>
  22. /// y坐标
  23. /// </summary>
  24. public int y;
  25. /// <summary>
  26. /// 寻路消耗
  27. /// </summary>
  28. public float f;
  29. /// <summary>
  30. /// 距离起点距离
  31. /// </summary>
  32. public float g;
  33. /// <summary>
  34. /// 距离终点距离
  35. /// </summary>
  36. public float h;
  37. /// <summary>
  38. /// 父结点
  39. /// </summary>
  40. public AStarNode father;
  41. /// <summary>
  42. /// 结点类型
  43. /// </summary>
  44. public E_Node_Type type;
  45. public AStarNode(int xPos, int yPos, E_Node_Type t)
  46. {
  47. this.x = xPos;
  48. this.y = yPos;
  49. this.type = t;
  50. }
  51. }

结点管理类:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class AStarManger : BaseManger<AStarManger>
  5. {
  6. public int mapW;
  7. public int mapH;
  8. private AStarNode startNode;
  9. private AStarNode endNode;
  10. //private Vector2 endVec = Vector2.right * -1;
  11. public AStarNode[,] nodes;
  12. List<AStarNode> openList = new List<AStarNode>();
  13. List<AStarNode> closeList = new List<AStarNode>();
  14. /// <summary>
  15. /// 初始化
  16. /// </summary>
  17. public void InitAStarManger(int mapW, int mapH)
  18. {
  19. if(mapH < 0 || mapW < 0)
  20. {
  21. Debug.LogError("地图尺寸存在问题");
  22. return;
  23. }
  24. this.mapW = mapW;
  25. this.mapH = mapH;
  26. nodes = new AStarNode[mapW, mapH];
  27. for(int i = 0; i < this.mapW; i++)
  28. {
  29. for(int j = 0; j < this.mapH; j++)
  30. {
  31. AStarNode an = new AStarNode(i, j, Random.Range(0, 100) < 20 ? E_Node_Type.Obstacles : E_Node_Type.Normal);
  32. nodes[i, j] = an;
  33. }
  34. }
  35. }
  36. /// <summary>
  37. /// 开始寻径
  38. /// </summary>
  39. /// <returns></returns>
  40. public List<AStarNode> FindPath(Vector2 startPos, Vector2 endPos)
  41. {
  42. if(startPos.x < 0 || startPos.y < 0 || endPos.x < 0 || endPos.y < 0
  43. || startPos.x >= this.mapW || startPos.y >= this.mapH
  44. || endPos.x >= this.mapW || endPos.y >= this.mapH)
  45. {
  46. Debug.LogError("坐标超出范围");
  47. return null;
  48. }
  49. startNode = nodes[(int)startPos.x, (int)startPos.y];
  50. endNode = nodes[(int)endPos.x, (int)endPos.y];
  51. if(startNode.type is E_Node_Type.Obstacles || endNode.type is E_Node_Type.Obstacles)
  52. {
  53. Debug.LogError("起始结点或目标结点为障碍");
  54. return null;
  55. }
  56. startNode.father = null;
  57. startNode.f = 0;
  58. startNode.g = 0;
  59. startNode.h = 0;
  60. openList.Clear();
  61. closeList.Clear();
  62. closeList.Add(startNode);
  63. while(true)
  64. {
  65. //找附近点,并将符合条件的添加进openList
  66. AddNearlyNode2OpenList(startNode.x - 1, startNode.y -1, 1.4f, startNode, endPos);
  67. AddNearlyNode2OpenList(startNode.x, startNode.y - 1, 1f, startNode, endPos);
  68. AddNearlyNode2OpenList(startNode.x + 1, startNode.y - 1, 1.4f, startNode, endPos);
  69. AddNearlyNode2OpenList(startNode.x - 1, startNode.y, 1f, startNode, endPos);
  70. AddNearlyNode2OpenList(startNode.x + 1, startNode.y, 1f, startNode, endPos);
  71. AddNearlyNode2OpenList(startNode.x - 1, startNode.y + 1, 1.4f, startNode, endPos);
  72. AddNearlyNode2OpenList(startNode.x, startNode.y + 1, 1f, startNode, endPos);
  73. AddNearlyNode2OpenList(startNode.x + 1, startNode.y + 1, 1.4f, startNode, endPos);
  74. if(openList.Count == 0)
  75. {
  76. Debug.LogWarning("终点不可达");
  77. return null;
  78. }
  79. //排序
  80. openList.Sort(SortRuler);
  81. //选择新的父节点
  82. startNode = openList[0];
  83. //添加到closeList
  84. closeList.Add(startNode);
  85. openList.RemoveAt(0);
  86. //判断是否到达终点
  87. if(startNode == endNode)
  88. {
  89. List<AStarNode> path = new List<AStarNode>();
  90. path.Add(endNode);
  91. while(endNode.father != null)
  92. {
  93. path.Add(endNode.father);
  94. endNode = endNode.father;
  95. }
  96. path.Reverse();
  97. return path;
  98. }
  99. }
  100. }
  101. private int SortRuler(AStarNode a, AStarNode b)
  102. {
  103. if (a.f >= b.f) return 1;
  104. else return -1;
  105. }
  106. /// <summary>
  107. /// 将父结点周围符合条件结点,添加进openList
  108. /// </summary>
  109. private void AddNearlyNode2OpenList(int x, int y, float g, AStarNode father, Vector2 endPos)
  110. {
  111. //判断传入坐标是否超范围
  112. if(x < 0 || y < 0 || x >= this.mapW || y >= this.mapH)
  113. {
  114. return;
  115. }
  116. AStarNode an = nodes[x, y];
  117. //如果该结点是障碍结点 或 ol,cl里有,忽略
  118. //如果结点为null,或是上下左右都有障碍物,忽略
  119. if(an.type is E_Node_Type.Obstacles
  120. || an is null || closeList.Contains(an)
  121. || nodes[x, father.y].type is E_Node_Type.Obstacles
  122. || nodes[father.x, y].type is E_Node_Type.Obstacles)
  123. {
  124. return;
  125. }
  126. //单独判断是否在ol里,优化。如果结点已经在ol里,并且下一个最小f值的点也可以算到该点,那么该点的父节点不会变为这个最小值点,导致不是最短路径
  127. if(openList.Contains(an))
  128. {
  129. float gThis = father.g + g;
  130. if(gThis < an.g)
  131. {
  132. an.g = gThis;
  133. an.f = an.g + an.h;
  134. an.father = father;
  135. }
  136. return;
  137. }
  138. //父结点距离起点距离,加上自己距离父结点距离
  139. an.g = father.g + g;
  140. an.h = Mathf.Abs(endPos.x - x) + Mathf.Abs(endPos.y - y);
  141. //计算曼哈顿距离
  142. an.f = an.g + an.h;
  143. //将结点加入openList
  144. openList.Add(an);
  145. //设置父结点
  146. an.father = father;
  147. }
  148. }

单例模板:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class BaseManger<T> where T : new()
  5. {
  6. private static T instance;
  7. public static T Getinstance()
  8. {
  9. if(instance is null)
  10. {
  11. instance = new T();
  12. }
  13. return instance;
  14. }
  15. }

Unity中界面构建

测试脚本

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UIElements;
  5. public class Test : MonoBehaviour
  6. {
  7. public float BeginX;
  8. public float BeginY;
  9. public float offsetX;
  10. public float offsetY;
  11. public int mapW;
  12. public int mapH;
  13. private Vector2 beginPos = Vector2.right * -1;
  14. private Dictionary<string, GameObject> cubes = new Dictionary<string, GameObject>();
  15. List<AStarNode> list = new List<AStarNode>();
  16. private GameObject tmpStart;
  17. private GameObject tmpEnd;
  18. // Start is called before the first frame update
  19. void Start()
  20. {
  21. Init();
  22. }
  23. // Update is called once per frame
  24. void Update()
  25. {
  26. if(Input.GetMouseButtonDown(0))
  27. {
  28. Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
  29. RaycastHit info;
  30. if(Physics.Raycast(ray, out info, 1000))
  31. {
  32. if(beginPos == Vector2.right * -1)
  33. {
  34. if (list != null)
  35. {
  36. for (int i = 0; i < list.Count; i++)
  37. {
  38. cubes[list[i].x + "_" + list[i].y].GetComponent<MeshRenderer>().material.color = Color.white;
  39. }
  40. }
  41. string[] strs = info.collider.gameObject.name.Split('_');
  42. beginPos = new Vector2(int.Parse(strs[0]), int.Parse(strs[1]));
  43. if (tmpEnd) tmpEnd.GetComponent<MeshRenderer>().material.color = Color.white;
  44. if (tmpStart) tmpStart.GetComponent<MeshRenderer>().material.color = Color.white;
  45. tmpStart = info.collider.gameObject;
  46. tmpStart.GetComponent<MeshRenderer>().material.color = Color.yellow;
  47. }
  48. else
  49. {
  50. string[] strs = info.collider.gameObject.name.Split('_');
  51. Vector2 endPos = new Vector2(int.Parse(strs[0]), int.Parse(strs[1]));
  52. tmpEnd = info.collider.gameObject;
  53. tmpEnd.GetComponent<MeshRenderer>().material.color = Color.blue;
  54. list = AStarManger.Getinstance().FindPath(beginPos, endPos);
  55. if(list != null)
  56. {
  57. for (int i = 0; i < list.Count; i++)
  58. {
  59. cubes[list[i].x + "_" + list[i].y].GetComponent<MeshRenderer>().material.color = Color.green;
  60. }
  61. }
  62. beginPos= Vector2.right * -1;
  63. }
  64. }
  65. }
  66. }
  67. public void Init()
  68. {
  69. AStarManger.Getinstance().InitAStarManger(mapW, mapH);
  70. tmpEnd = null;
  71. tmpStart = null;
  72. list = null;
  73. if(cubes.Count != 0)
  74. {
  75. foreach (var obj in cubes)
  76. {
  77. Destroy(obj.Value.gameObject);
  78. }
  79. cubes.Clear();
  80. }
  81. for (int i = 0; i < mapW; i++)
  82. {
  83. for (int j = 0; j < mapH; j++)
  84. {
  85. GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
  86. obj.transform.position = new Vector3(BeginX + i * offsetX, BeginY + j * offsetY);
  87. obj.transform.name = i + "_" + j;
  88. cubes.Add(obj.name, obj);
  89. AStarNode an = AStarManger.Getinstance().nodes[i, j];
  90. if (an.type == E_Node_Type.Obstacles)
  91. {
  92. MeshRenderer mr = obj.GetComponent<MeshRenderer>();
  93. mr.material.color = Color.red;
  94. }
  95. }
  96. }
  97. }
  98. }

新建一个场景,将测试脚本挂载在任意物体上

新建一个画布,并添加一个按钮。其它ui元素可随意设定

将按钮关联Init方法

实现效果

正常终点可达:

终点不可达情况:

后续优化文章: 

 进一步优化


本文仅为学习阶段的一个总结,按需浏览。存在一定不足的情况

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/94853
推荐阅读
相关标签
  

闽ICP备14008679号