Unity里面A*算法的简单实现
现在各种A算法的资料有很多,大家可以从很多的网站其了解,今天我来讲一下我用Unity写的一个Demo去
实现一下A寻路算法的实现。
结点类 我们实现A*算法的最基本的字段/属性类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Node
{
//判断是否可以走
public bool walkable;
public int x;
public int y;
public Vector3 pos;
//当前点到终点的距离
public int hCost;
//起始点到当前点的距离
public int gCost;
//父节点
public Node parent;
/// <summary>
/// 有参构造
/// 格子的x坐标 格子的x坐标 是否可以走 当前点的位置
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="walkable"></param>
/// <param name="pos"></param>
public Node(int x,int y,bool walkable,Vector3 pos)
{
this.x = x;
this.y = y;
this.walkable = walkable;
this.pos = pos;
}
//A*算法里面的F=G+H公式
public int fCost { get { return gCost + hCost; } }
}
//创建格子、画出格子以及计算格子位置的方法
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ground : MonoBehaviour
{
//障碍物
public LayerMask unwalkable;
//当前网格尺寸
public Vector2 gridsize;
//节点半径
public float nodeRadius;
//节点直径
float nodeDiamter;
//按节点定义的网格
public Node[,] grid;
//节点数量x轴y轴
public int nodenumx, nodenumy;
//起点终点
public Transform Player, Target;
//存储节点路径的集合
public List<Node> path;
// Start is called before the first frame update
void Start()
{
nodeDiamter = 2 * nodeRadius;
//横纵坐标节点数量整型化
nodenumx = Mathf.RoundToInt(gridsize.x / nodeDiamter);
nodenumy = Mathf.RoundToInt(gridsize.y / nodeDiamter);
//创建网格的方法
CreateGrid();
}
//创建格子的方法
private void CreateGrid()
{
grid = new Node[nodenumx,nodenumy];
Vector3 startpos = transform.position - new Vector3(gridsize.x/2,gridsize.y/2,0);
for (int x = 0; x < nodenumx; x++)
{
for (int y = 0; y < nodenumy; y++)
{
//利用左下角的小标 加上每个小格子的直径 和半径就是中心点的位置
Vector3 currentpos = startpos +new Vector3(x*nodeDiamter+nodeRadius,y*nodeDiamter+nodeRadius,0);
bool walkable = !Physics.CheckSphere(currentpos,nodeRadius,unwalkable);
//创建每一个节点
grid[x, y] = new Node(x,y,walkable,currentpos);
}
}
}
/// <summary>
/// 获取点所在的Node
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
public Node GetNodePosition(Vector3 pos)
{
float percentx = (pos.x + gridsize.x / 2) / gridsize.x;
float percenty = (pos.y + gridsize.y / 2) / gridsize.y;
percentx = Mathf.Clamp01(percentx);
percenty = Mathf.Clamp01(percenty);
int x =Mathf.RoundToInt(percentx * (nodenumx - 1));
int y = Mathf.RoundToInt(percenty * (nodenumy - 1));
return grid[x, y];
}
/// <summary>
/// 画格子的方法
/// </summary>
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
Gizmos.DrawCube(transform.position, new Vector3(gridsize.x, gridsize.y, 1));
//区分成蓝色的小格子
if(grid!=null)
{
foreach (Node n in grid)
{
if (n.walkable)
{
Gizmos.color = Color.gray;
if (GetNodePosition(Player.position) == n)
{
Gizmos.color = Color.black;
}
if(GetNodePosition(Target.position) == n)
{
Gizmos.color = Color.red;
}
//判断路径 从后往前
if (path != null&&path.Contains(n))
{
Gizmos.color = Color.blue;
}
Gizmos.DrawCube(n.pos,new Vector3(nodeDiamter*0.9f,nodeDiamter*0.9f,1));
}
}
}
}
void Update()
{
}
}
A*算法执行的脚本,里面包括寻找路径的方法,判断两点距离的方法,获得邻居结点的方法
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FindingPath : MonoBehaviour
{
Ground ground;
//初始节点 目标节点
Node startnode, targetnode;
private void Awake()
{
ground = GetComponent<Ground>();
}
void Start()
{
}
public void FindPath(Vector3 startpos,Vector3 targetpos)
{
//起始节点
startnode = ground.GetNodePosition(startpos);
//目标节点
targetnode = ground.GetNodePosition(targetpos);
//开放节点 需要被评估的节点
List<Node> openList = new List<Node>();
//闭合节点 已经评估的节点
HashSet<Node> closeSet = new HashSet<Node>();
//起始点加入开放列表
openList.Add(startnode);
//先判断openlist里面是否有点,如果没有说明遍历完了没有找到路径
while (openList.Count > 0)
{
//当前结点
Node currentnode = openList[0];
for (int i = 1; i < openList.Count; i++)
{
//先判断fcost,如果相等去判断hcost
if (openList[i].fCost < currentnode.fCost || (openList[i].fCost == currentnode.fCost && openList[i].hCost < currentnode.hCost))
{
//确定下当前f值最小的结点
currentnode = openList[i];
}
}
openList.Remove(currentnode);//移除当前节点
closeSet.Add(currentnode);//添加到目标节点里面
//判断当前点是否是目标节点
if (currentnode == targetnode)
{
Debug.Log("Path was found");
RetriPath(currentnode);
return;
}
//遍历当前节点的所有邻居节点
foreach (Node n in GetNeighbors(currentnode))
{
//当前结点不可移动(有障碍物)或者在closelist集合里面
if (!n.walkable || closeSet.Contains(n))
{
continue;
}
//gCost重新赋值
int newGCost = currentnode.fCost + GetDistance(currentnode, n);
bool inOpenSet = openList.Contains(n);
if (newGCost < n.gCost || !inOpenSet)
{
n.gCost = newGCost;
n.hCost = GetDistance(n, targetnode);
//将当前邻居节点的父节点设为当前结点
n.parent = currentnode;
if (!inOpenSet)
{
openList.Add(n);
}
}
}
}
}
private void RetriPath(Node n)
{
List<Node> p = new List<Node>();
//n是目标点的值targetnode
while (n != startnode)
{
p.Add(n);
n = n.parent;
}
p.Reverse();
ground.path = p;
}
/// <summary>
/// 获取两个点之间距离的方法
/// </summary>
/// <param name="n1"></param>
/// <param name="n2"></param>
/// <returns></returns>
int GetDistance(Node n1,Node n2)
{
int distancex = Mathf.Abs(n1.x- n2.x);
int distancey = Mathf.Abs(n1.y - n2.y);
if (distancex > distancey)
{
return 14 * (distancey) + 10 * (distancex - distancey);
}
else
{
//y比x大
return 14 * (distancex) + 10 * (distancey - distancex);
}
}
/// <summary>
/// 找邻居节点的方法
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
private List<Node> GetNeighbors(Node n)
{
List<Node> neighborlist = new List<Node>();
//求出当前节点的xy值
int xx = n.x, yy = n.y;
//求出当前节点的八个邻居节点
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
if(x==0 && y == 0)
{
continue;
}
//把所有的邻居存储到邻居集合里面去
if (xx + x > 0 && xx + x < ground.nodenumx && yy + y > 0 && yy + y < ground.nodenumy)
{
neighborlist.Add(ground.grid[xx+x, yy+y]);
}
}
}
return neighborlist;
}
void Update()
{
//调用A*寻路的方法
FindPath(ground.Player.position,ground.Target.position);
}
}
具体实现效果如下
注意点:要打开Cizmos按钮,要不然我们所画出来的格子和线都看不到!
具体设置
1、Ground是一个3D物体地板用来当做背景X轴设置成-90度,Start和End表示起始点的3D物体。
2、IsParse是一个空物体挂载Ground和FindingPath脚本,Unwalkable是右上角设置它的Layer层级用来判断是否障碍物,Gridsize x,y表示x,y轴的长度,NodeRadius表示结点的半径,Player和Target表示起始点的3D物体。
3、Cube底下的都是我们手动设置的3D物体障碍物!
**总结:A*寻路并不是寻找的最短的路径,它只是在比较快的时间里找到一条从起点到目标点的路径的一种算法,它也有很多的优化,可以去了解一下!
**