一、要实现的功能:
public enum E_Node_Type
{
Walk,//可以走的地方
Stop,//不能走的地方
}
1.2 每个节点都应包含的信息:x、y坐标,f=g+h评价函数,类型(Walk/Stop),节点类中建立构造函数:
public AStarNode(int x,int y,E_Node_Type type)
{
this.x = x;
this.y = y;
this.type = type;
}
//定义私有变量用来存储类的实例对象
private static AStarManager instance;
//定义公有方法提供一个全局访问点
public static AStarManager Instance
{
get //类的实例是只读的,所以只有get没有set
{
if (instance==null)
{
instance = new AStarManager();
}
return instance;
}
}
上面的单例是为了保证管理类只有一个实例,且为只读的。
2.2 建立一个方法,输入为开始、结束节点,返回的是寻找到的节点列表,方法的架构如下:
//1. 首先判断传入的两个点是否合法(假设传入的点就是整数) 是不是阻挡
//2. 如果不合法,应直接返回null,意味着不能寻路
//3. 应当得到起点和终点对应的节点
//4. 把开始点放入关闭列表中
//5. 从起点开始找周围的点并放入开启列表中
//6. 左上-上-右上-左-右-左下-下-右下
//7. 判断这些点是否是边界,是否是阻挡,是否已经在开启或者关闭别表中,如果都不是,则放进开启列表中;
//8. 选出开启列表中寻路消耗最小的点
//9. 放入关闭列表中,然后从开启列表中删除
//10. 如果这个点已经是终点了,那么得到最终的结果返回出去
//11. 如果这个点不是终点,那么继续寻路。
2.3 节点的搜寻状态:
判断某一个节点是否合法,然后再判断是否已经存在与当前的开启列表与关闭列表中,存储这个节点的父节点,并计算这个节点的代价函数:f=g+h;
其中h用的是欧几里得计算方法:忽略障碍时当前点距离终点的水平+垂直距离:
private void FindNearlyNodeToOPenList(int x, int y,float g,AStarNode father,AStarNode end)
{
if (x<0||x>=mapW||y<0||y>=mapH)
return;
AStarNode node = nodes[x, y];
if (node==null||
node.type==E_Node_Type.Stop||
closeList.Contains(node)||
openList.Contains(node))
return;
//计算f值
node.father = father;
node.g = father.g + g;
node.h = Mathf.Abs(end.x - node.x) + Mathf.Abs(end.y - node.y);
node.f = node.g + node.h;
openList.Add(node);
}
记得最后要把这个点存入开启列表中;
2.4 使用list.sort( )进行降序排列:
private int SortOpenList(AStarNode a, AStarNode b)
{
if (a.f>b.f)
return 1;
else
return -1;
}
openList.Sort(SortOpenList);
重新排列后的OpenList[0]就是我们代价最先的点,作为下一个start再次循环;
使用中记得将最后的TestAstar赋给我们的摄像机(也可其他物体),要是看的不全,摄像机调整下位置即可;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
///
/// 定义枚举类型对节点进行划分:能走/不能走
///
public enum E_Node_Type
{
Walk,//可以走的地方
Stop,//不能走的地方
}
///
/// A星格子类
///
public class AStarNode
{
//节点的x、y坐标
public int x;
public int y;
//每个节点都有评价函数f = g + h;
public float f;
public float g; //已经消耗的代价
public float h; //评估此点到终点的代价
//每个节点都有自己的父节点
public AStarNode father;
public E_Node_Type type;
//构造函数
///
/// 传入坐标与节点类型
///
///
///
///
public AStarNode(int x,int y,E_Node_Type type)
{
this.x = x;
this.y = y;
this.type = type;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AStarManager
{
//定义类的单例:
//定义私有变量用来存储类的实例对象
private static AStarManager instance;
//定义公有方法提供一个全局访问点
public static AStarManager Instance
{
get //类的实例是只读的,所以只有get没有set
{
if (instance==null)
{
instance = new AStarManager();
}
return instance;
}
}
//地图的宽、高尺寸:
public int mapW;
public int mapH;
//二维数组作为存储地图相关所有节点的对象容器
public AStarNode[,] nodes;
//开启列表
private List<AStarNode> openList=new List<AStarNode>();
//关闭列表
private List<AStarNode> closeList=new List<AStarNode>();
public void InitMapInfo(int w, int h)
{
//声明容器可以装多少个节点
nodes = new AStarNode[w, h];
//根据宽高,创建节点
//声明节点,装进去
this.mapW = w;
this.mapH = h;
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
AStarNode node = new AStarNode(i, j, Random.Range(0, 100) <25 ? E_Node_Type.Stop : E_Node_Type.Walk);
nodes[i, j] = node;
}
}
}
public List<AStarNode> FindPath(Vector2 startPos,Vector2 endPos)
{
//1. 首先判断传入的两个点是否合法(假设传入的点就是整数) 是不是阻挡
//2. 如果不合法,应直接返回null,意味着不能寻路
//3. 应当得到起点和终点对应的节点
//4. 把开始点放入关闭列表中
//5. 从起点开始找周围的点并放入开启列表中
//6. 左上-上-右上-左-右-左下-下-右下
//7. 判断这些点是否是边界,是否是阻挡,是否已经在开启或者关闭别表中,如果都不是,则放进开启列表中;
//8. 选出开启列表中寻路消耗最小的点
//9. 放入关闭列表中,然后从开启列表中删除
//10. 如果这个点已经是终点了,那么得到最终的结果返回出去
//11. 如果这个点不是终点,那么继续寻路。
//首先判断起始点是否超出边界
if (startPos.x < 0 || startPos.x >= mapW ||
startPos.y < 0 || startPos.y >= mapH ||
endPos.x < 0 || endPos.x >= mapW ||
endPos.y < 0 || endPos.y >= mapH)
{
Debug.Log("开始或结束点在地图外");
return null;
}
AStarNode start = nodes[(int)startPos.x, (int)startPos.y];
AStarNode end = nodes[(int)endPos.x, (int)endPos.y];
if (start.type == E_Node_Type.Stop || end.type == E_Node_Type.Stop)
{
Debug.Log("开始或结束点为阻挡");
return null;
}
closeList.Clear();
openList.Clear();
start.father = null;
start.f = 0;
start.g = 0;
start.h = 0;
closeList.Add(start);
while (true)
{
FindNearlyNodeToOPenList(start.x - 1, start.y - 1, 1.4f, start, end);
FindNearlyNodeToOPenList(start.x, start.y - 1, 1f, start, end);
FindNearlyNodeToOPenList(start.x + 1, start.y - 1, 1.4f, start, end);
FindNearlyNodeToOPenList(start.x - 1, start.y, 1f, start, end);
FindNearlyNodeToOPenList(start.x + 1, start.y, 1f, start, end);
FindNearlyNodeToOPenList(start.x - 1, start.y + 1, 1.4f, start, end);
FindNearlyNodeToOPenList(start.x, start.y + 1, 1f, start, end);
FindNearlyNodeToOPenList(start.x + 1, start.y + 1, 1.4f, start, end);
if (openList.Count == 0)
{
Debug.Log("死路");
return null;
}
openList.Sort(SortOpenList);
closeList.Add(openList[0]);
//找到的这个点又变成新的起点
start = openList[0];
openList.RemoveAt(0);
if (start == end)
{
List<AStarNode> path = new List<AStarNode>();
path.Add(end);
while (end.father != null)
{
path.Add(end.father);
end = end.father;
}
path.Reverse();//顺序反转
return path;
}
}
}
private int SortOpenList(AStarNode a, AStarNode b)
{
if (a.f>b.f)
return 1;
else
return -1;
}
///
/// 把临近的点放入开启列表中
///
///
///
private void FindNearlyNodeToOPenList(int x, int y,float g,AStarNode father,AStarNode end)
{
if (x<0||x>=mapW||y<0||y>=mapH)
return;
AStarNode node = nodes[x, y];
if (node==null||
node.type==E_Node_Type.Stop||
closeList.Contains(node)||
openList.Contains(node))
return;
//计算f值
node.father = father;
node.g = father.g + g;
node.h = Mathf.Abs(end.x - node.x) + Mathf.Abs(end.y - node.y);
node.f = node.g + node.h;
openList.Add(node);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestAStar : MonoBehaviour
{
public int beginX=3;
public int beginY=5;
//偏移位置
public int offsetX=2;
public int offsetY=-2;
//地图格子的宽与高
public int mapW=5;
public int mapH=5;
//开始点,给他一个为负的坐标点
private Vector2 beginPos = Vector2.right * -1;
List<AStarNode> list;
private Dictionary<string, GameObject> cubes = new Dictionary<string, GameObject>();
// Start is called before the first frame update
void Start()
{
AStarManager.Instance.InitMapInfo(mapW, mapH);
for (int i = 0; i < mapW; ++i)
{
for (int j = 0; j < mapH; ++j)
{
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.transform.position = new Vector3(beginX + i * offsetX, beginY + j * offsetY, 0);
obj.name = i + "_" + j;//起个名字
cubes.Add(obj.name,obj);
AStarNode node = AStarManager.Instance.nodes[i, j];
if (node.type==E_Node_Type.Stop)
{
obj.GetComponent<MeshRenderer>().material.color = Color.red;
}
}
}
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit info;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray,out info,1000))
{
if (beginPos==Vector2.right*-1)
{
//清理上一次路径
if (list != null)
{
for (int i = 0; i < list.Count; ++i)
{
cubes[list[i].x + "_" + list[i].y].GetComponent<MeshRenderer>().material.color = Color.white;
}
}
string[] strs = info.collider.gameObject.name.Split('_');
//得到行列位置,就是开始点的位置
beginPos = new Vector2(int.Parse(strs[0]), int.Parse(strs[1]));
//将起点改为黄色
info.collider.gameObject.GetComponent<MeshRenderer>().material.color = Color.yellow;
}
else
{
//得到终点
string[] strs = info.collider.gameObject.name.Split('_');
Vector2 endPos = new Vector2(int.Parse(strs[0]), int.Parse(strs[1]));
//寻路
list = AStarManager.Instance.FindPath(beginPos,endPos);
if (list!=null)
{
for (int i = 0; i < list.Count; ++i)
{
cubes[list[i].x + "_" + list[i].y].GetComponent<MeshRenderer>().material.color = Color.green;
}
}
beginPos = Vector2.right * -1;
}
}
}
}
}