Unity A*学习笔记

A*算法测试

测试用例

测试用例2

源码链接在评论

实现思路

1,将地图表格化

2,找到自己所在的格子,与目标地点所在的格子

3,无线遍历周围的格子,寻找离目标最近的格子,直到找到目标为止或者是遍历所有没有走过的格子,直到无路可走

简单理解这个思路,其实抛去其中的代码细节实现,只要明白大致思路就能知道该如何做

第一个问题:如何找到周围的格子

第二个问题:如何知道我是否找过所有格子,亦或者我找到了哪些格子,哪些是已经找过的格子

第三个问题:当我找到目标时,我怎么才能知道自己找到的路径

第四个问题:如何知道周围的格子哪个是离目标最近的

1

好的,先解决这几个问题。

我们先思考,我如何去找到离目标最近的道路,

那么简单,两点之间线段最短,但是显然我们不可以这么做,如果周围是障碍物的话这个方法就不成立了。

那么采用第二种方法:

公式:F(n) = G + H,加入权重F(n) = A*G + (1-A)H

不加权重也可以,这里测试使用F(n) = G + H

其中G为离上一个格子不断累加的距离(G不是离起点的直接距离),H为离终点的距离(简单计算公式可以为y-y1和x-x1的距离之和),其中G和H的计算公式是不一样的否则就没有意义了

假设起点(0,0)终点(10,10)

我们下一步正常走应该是1,1,其中G=1.4,H=18

在下一步走2,2,其中G=1.4+1.4=2.8,H=16

走到9,9格子的时候G=1.4+1.4....=12.6,H=2,

我们简单理解按照这个公式走下去会发现,当我们每走一步的时候都会计算F,那么显而易见越是离终点远的格子F值就越大,相反就越小,那么我们只要一直去找F值最小的格子就能找到最终目标

这样我们就解决了第四个问题,如何知道周围的格子哪个是离目标最近的

2

那么你会想要是中途碰到障碍物怎么办,很简单,由于我们是按照格子走的,是一步一步走下去的,只要中途碰到了障碍物就把它的F值设为无限大或者直接跳过它

同时我们先解决第一个问题,如何找到周围的格子,这个就好理解了,当前坐标为(x,y)

周围的格子无非是+1-1计算一下就可以了。

那么我如何知道应该找哪些格子呢,我不可能把已经找到的格子在找一遍吧,或者把所有格子都找一遍那么岂不是就没有意义了,

所以就需要我们设置两个列表,一个为openList一个为closList

我们将openlist设置为周围的八个格子,从周围的格子中找到F值最小的格子再从这个格子继续遍历周围的格子(其中要是已经加入OpenList的格子就不需要再找了否则没意义),同时我们把这个F值最小的格子加入CloseList代表我们这个格子是已经找过的了,省的一直查找相同的路径。

其中有一点closeList中点并不一定就是最终路径,只是代表这个格子是被找到过的F值最小的格子,(如果我右边有一面墙,这个时候查找周围点的时候不管是向上还是向下他们的代价值是一样的,但是如果上方的距离终点更远的话,它就被加入了CloseList但他并不是终点路径点,当它向上查找的时候可能离终点越来越远,但是由于我们遍历的是OpenList中F值最小的点,这个时候走远路的话它就会从上面的路径点跳转到下面查找),但是你会想这怎么能跳着走呢

但是要知道我们循环遍历的一直都是F值最小的点,并不是直接查找的终点路径,只是将路过的F值最小的加入到CloseList罢了

3

那么如果我们要是一直这么查找,最终查找到了终点之后我该如何得到这个路径,很简单,没走一次格子就记录一下它是从哪个格子过来的,我们只要知道终点格子,就可以知道它的上一个格子,依次找到最终路径

理解这个思路,就可以知道如何去做

代码实现

代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AStarTool
{
	List<Node> openList;
	List<Node> closeList;
	public AStarTool()
	{
		openList = new List<Node>();
		closeList = new List<Node>();
	}
	/// <summary>
	/// 根据起点和终点找到路径
	/// </summary>
	/// <param name="start"></param>
	/// <param name="end"></param>
	public List<Node> FindePath(Node start, Node end, Node[,] map)
	{
		openList.Clear();
		closeList.Clear();
		Node startNode = start;
		startNode.parent = null;
		Node endNode = end;
		openList.Add(startNode);
		while (openList.Count > 0)
		{
			for (var i = -1; i <= 1; i += 2)
			{
				for (var j = -1; j <= 1; j += 2)
				{
					if (i == 0 && j == 0)
					{
						continue;
					}
					int x1 = startNode.x + i;
					int y1 = startNode.x + i;
					if (x1 < map.GetLength(0) && y1 < map.GetLength(1) && x1 > 0 && y1 > 0)
					{
						if (!map[x1, startNode.y].isWall && !map[startNode.x, y1].isWall)
						{
							GetAroundNode(startNode, endNode, i, j, map);
						}
					}
				}
			}
			GetAroundNode(startNode, endNode, 0, 1, map);
			GetAroundNode(startNode, endNode, 0, -1, map);
			GetAroundNode(startNode, endNode, -1, 0, map);
			GetAroundNode(startNode, endNode, 1, 0, map);
			Node minNode = GetMinNode();
			closeList.Add(minNode);
			openList.Remove(minNode);
			startNode = minNode;
			if (startNode == endNode)
			{

				List<Node> list = new List<Node>();
				list.Add(endNode);
				Node temp = endNode;
				while (temp.parent != null)
				{
					list.Add(temp.parent);
					temp = temp.parent;
				}
				list.Reverse();
				return list;
			}
		}
		return null;
	}
	void GetAroundNode(Node startNode, Node endNode, int x, int y, Node[,] map)
	{
		int xIndex = startNode.x + x;
		int yindex = startNode.y + y;
		if (xIndex >= map.GetLength(0) || yindex >= map.GetLength(1) || xIndex < 0 || yindex < 0)
		{
			return;
		}
		Node node = map[xIndex, yindex];
		if (node.isWall || openList.Contains(node) || closeList.Contains(node))
		{
			return;
		}
		node.parent = startNode;
		node.G = node.parent.G + Mathf.Sqrt(Mathf.Abs(x) + Mathf.Abs(y));
		node.H = Mathf.Abs(endNode.x - startNode.x) + Mathf.Abs(endNode.y - startNode.y);
		openList.Add(node);
	}
	Node GetMinNode()
	{
		if (openList.Count != 0)
		{
			Node resPoint = openList[0];
			foreach (Node point in openList)
				if (point.F < resPoint.F)
					resPoint = point;
			return resPoint;
		}
		return null;

	}



}

 寻路测试

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 挂载在物体上的唯一脚本
/// </summary>
public class Seeker : MonoBehaviour
{
	MapManager mapManager;
	bool isMonve;
	int indexMove;
	List<Node> posss;
	public Transform endPoint;
	TimeTool timeTool;
	float cd = 1;
	Node startNode;
	private void Start()
	{
		mapManager = MapManager.instance;
		if (mapManager == null)
		{
			return;
		}
		timeTool = new TimeTool();
		posss = new List<Node>();
		posss = mapManager.CreatPath(transform, endPoint);
		if (posss != null)
		{
			isMonve = true;
			indexMove = 0;
			startNode = posss[0];
		}
	}
	private void Update()
	{
		if (isMonve && posss != null)
		{

			startNode = posss[indexMove];
			Vector2 endPos = posss[indexMove].posi;
			transform.position = Vector2.MoveTowards(transform.position, endPos, Time.deltaTime);
			if (Vector2.Distance(transform.position, endPos) <= 0.2f)
			{
				indexMove++;
				if (indexMove >= posss.Count)
				{
					isMonve = false;
				}
			}
		}
		timeTool.Handle(ref cd, ActionTime, Random.Range(1, 2f));
	}
	void ActionTime()
	{
		
		posss = mapManager.RefreshPath(startNode, endPoint, transform);
		if (posss != null)
		{
			isMonve = true;
			indexMove = 0;
		}
		else
		{
			posss = mapManager.CreatPath(transform, endPoint);
		}
	}




}
public class Node
{
	private float proportion = 0.5f;
	public Node parent;
	public Vector2 posi;
	public bool isWall;
	//对应地图索引横坐标纵坐标
	public int x, y;
	public float G, H;
	public float F
	{
		get
		{
			return proportion * G + (1 - proportion) * H;
		}
	}
}
public interface Seek
{
	void SeekPath(Vector2[,] map, Transform destination);
}

 地图类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MapTool
{
	public MapTool(Vector2 center)
	{
		_center = center;
	}
	Vector2 _center;
	/// <summary>
	/// 生成普通地图
	/// </summary>
	/// <param name="width"></param>
	/// <param name="height"></param>
	/// <param name="interval"></param>
	/// <returns></returns>
	public Vector2[,] MapPoint(float width, float height, float interval)
	{
		float startPoint = -height / 2 - interval + _center.y;
		float yStartPoint = -width / 2 - interval + _center.x;
		int lieCount = (int)(width / interval);
		int hangCount = (int)(height / interval);
		Vector2[,] array = new Vector2[hangCount, lieCount];
		for (var hang = 0; hang < hangCount; hang++)
		{
			startPoint += interval;
			yStartPoint = -width / 2 - interval + _center.x;
			for (var lie = 0; lie < lieCount; lie++)
			{
				yStartPoint += interval;
				array[hang, lie] = new Vector2(yStartPoint, startPoint);
			}
		}
		return array;
	}
	/// <summary>
	/// 根据位置得到node.xy
	/// </summary>
	/// <param name="position"></param>
	/// <param name="map"></param>
	public Vector2 GetNode(Vector2 position, Node[,] map, float interval)
	{
		int hangIndex = map.GetLength(0) - 1;
		int lieIndex = map.GetLength(1) - 1;
		//当前坐标减去边界左坐标除间隔
		int lie = Mathf.RoundToInt((position.x - map[0, 0].posi.x) / interval);
		lie = Mathf.Clamp(lie, 0, lieIndex);
		int hang = Mathf.RoundToInt((position.y - map[0, 0].posi.y) / interval);
		hang = Mathf.Clamp(hang, 0, hangIndex);
		return new Vector2(hang, lie);
	}
	/// <summary>
	/// 生成节点地图
	/// </summary>
	/// <param name="x"></param>
	public Node[,] CreatMap(Vector2[,] map)
	{
		int hangCount = map.GetLength(0);
		int lieCount = map.GetLength(1);
		Node[,] nodeMap = new Node[hangCount, lieCount];
		for (var hang = 0; hang < hangCount; hang++)
		{
			for (var lie = 0; lie < lieCount; lie++)
			{
				nodeMap[hang, lie] = new Node();
				nodeMap[hang, lie].posi = map[hang, lie];
				nodeMap[hang, lie].x = hang;
				nodeMap[hang, lie].y = lie;

			}
		}
		return nodeMap;
	}
	/// <summary>
	/// 找到地图内的障碍物
	/// </summary>
	/// <param name="map"></param>
	/// <param name="mask"></param>
	/// <param name="radius"></param>
	public void InspectBarriar(Node[,] map, float radius, LayerMask mask)
	{
		int hangCount = map.GetLength(0);
		int lieCount = map.GetLength(1);
		for (var i = 0; i < hangCount; i++)
		{
			for (var lie = 0; lie < lieCount; lie++)
			{
				Vector2 curPoint = map[i, lie].posi;
				if (Physics2D.OverlapCircle(curPoint, radius, mask))
				{
					map[i, lie].isWall = true;
				}
			}
		}
	}

}

 地图管理

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MapManager : MonoBehaviour
{
	public static MapManager instance;
	MapTool mapTool;
	AStarTool aStarTool;
	public Vector2 center;
	public float width, height, interval;
	Node[,] nodeMap;
	List<Vector2> AIPosPath;
	public GameObject boxHidePoint;
	void Awake()
	{
		instance = this;

		Init();
	}
	void Init()
	{
		mapTool = new MapTool(Vector2.zero);
		aStarTool = new AStarTool();
		Vector2[,] boxPoint = mapTool.MapPoint(width, height, interval);
		foreach (Vector2 item in boxPoint)
		{
			Instantiate(boxHidePoint, item, Quaternion.identity);
		}
		nodeMap = mapTool.CreatMap(boxPoint);
		AIPosPath = new List<Vector2>();
	}
	/// <summary>
	/// 提供给外部AI的方法
	/// </summary>
	/*public List<Vector2> RefreshPath(Transform start, Transform end)
	{
		mapTool.InspectBarriar(nodeMap, 0.1f, LayerMask.GetMask("Barriar"));
		Vector2 st = mapTool.GetNode(start.position, nodeMap, interval);
		Vector2 st2 = mapTool.GetNode(end.position, nodeMap, interval);
		List<Node> lisNode = aStarTool.FindePath(nodeMap[(int)st.x, (int)st.y], nodeMap[(int)st2.x, (int)st2.y], nodeMap);
		AIPosPath.Clear();
		if (lisNode != null)
		{
			foreach (Node item in lisNode)
			{
				AIPosPath.Add(item.posi);
			}
		}

		return AIPosPath;
	}*/
	public List<Node> CreatPath(Transform start, Transform end)
	{
		mapTool.InspectBarriar(nodeMap, 0.1f, LayerMask.GetMask("Barriar"));
		Vector2 st = mapTool.GetNode(start.position, nodeMap, interval);
		Vector2 st2 = mapTool.GetNode(end.position, nodeMap, interval);
		List<Node> lisNode = aStarTool.FindePath(nodeMap[(int)st.x, (int)st.y], nodeMap[(int)st2.x, (int)st2.y], nodeMap);
		return lisNode;
	}
	public List<Node> RefreshPath(Node start, Transform end, Transform startposi)
	{
		Vector2 st2 = mapTool.GetNode(end.position, nodeMap, interval);
		if (start == null)
		{
			Vector2 st = mapTool.GetNode(startposi.position, nodeMap, interval);
			start = nodeMap[(int)st.x, (int)st.y];
		}
		List<Node> lisNode = aStarTool.FindePath(start, nodeMap[(int)st2.x, (int)st2.y], nodeMap);
		return lisNode;
	}
	public void RefreshBarriar()
	{
		mapTool.InspectBarriar(nodeMap, 0.1f, LayerMask.GetMask("Barriar"));
	}
}

 计时器简单工具

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TimeTool
{
	public void Handle(ref float cd, System.Action ac, float CD = 2)
	{
		cd -= Time.deltaTime;
		if (cd <= 0)
		{
			cd = CD;
			ac?.Invoke();
		}
	}
}

 资源连接

猜你喜欢

转载自blog.csdn.net/qq_55042292/article/details/125330671