这几天学习了一下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;
}
效果如上,不会再直接走过有障碍的斜角了