一、算法用处
学习算法最重要的就是要用它。A* 算法在游戏中的应用常用于寻路计算。这里借用百度百科的一句话。
A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快。
说白了,就是一种搜索算法。
二、重要名词
在学习A* 算法之前,我们要对这个算法的很多重要名词做一个解释。要学习这个算法,这些名词是必须要理解的。
1.结点
为了方便理解,我将称以下所有的结点为格子。这么称呼会更形象,当然它不一定是格子,也可能是一个坐标,一个方格,一个六边形格子,甚至其他的东西。这也是A*算法的一个基本单位。
2.列表
开启列表(OpenList):保存待检查的格子的列表。
关闭列表(CloseList):保存检查完的格子的列表。
3.F=G+H
这是整个算法中的核心公式,可以说是整个算法的灵魂。对于整个地图来说,每一个格子,都在不同时候有不同的F、G、H。
专业术语版:
G:在状态空间中从初始状态到状态n的实际代价。
H:从状态n到目标状态的最佳路径的估计代价。
F:从初始状态经由状态n到目标状态的代价估计。
简单理解版:
G:起始格子到该格子走过的距离
H:该格子到目标格子估计的距离
F:G+H
查找了很多资料或者教学之后,多数的A* 算法都是这么算的:规定斜着一格距离为14,直着距离为10。首先原因是因为计算机处理浮点数效率远比整数低。因此首先这个数值一定要尽可能取整。如果设置两个格子的距离为1,那斜向根据勾股定理会取到1.414,因此多数人都把这个值乘以10。取10和14。
4.父节点
就是当前格子是从哪个格子走过来的。毕竟寻路是要返回路径的,而不是单纯判断能不能去。
三、算法思想
其实A*算法的思想并不难。只不过我当时学习的时候被那一大堆专业术语搞得糊涂了,才让我好久都无法理解。
1.确定起点和终点(当然提前要检查这两个点是不是可到达的)
2.把起点添加到开启列表
3.把起点周围所有可到达的点以父节点为起点加入开启列表
4.把起点放入关闭列表
5.计算每一个开启列表里结点的F、G、H
6.找寻开启列表里F最低的一个格子。把当前格子设为这个格子。
7.将当前格子周围所有可到达的格子加入开启列表
8.如果遇到已经加入过的格子,需计算从当前格子到这个加入过的格子的新G。如果G比之前要低,就把这个加入过的格子的父节点设为当前格子。然后赋新的F、G、H。
9.如果是没加入过的,直接把父节点设为当前格子
10.把当前格子放到关闭列表。然后再开启列表里寻找F最低的格子
7-10循环往复
最后一直循环之后会发现某个格子的周围格子正好是终点。这时将终点的父节点设置为这个格子,然后一直寻找父节点,就返回了一条寻路路径了。
这里引用一个大佬的教程。因为这里面有一个图解。会让人更加直观的了解这个算法(喜欢看图理解的可以看看!)unity A*算法简单实现
不必担心什么计算量特别大或者什么这样真的会找到终点之类的问题吗。我第一次学的时候也是这么想的,最后发现真的可以。我之前还担心他的计算会影响游戏运行效率,后来试着做一个6* 6终点的遍历A* 寻路,发现也是没有卡顿的感觉就计算完了。是我太低估了CPU。
四、实现过程
我当时写这段代码时参考了一个大佬的A*算法教程,但是我找不到这篇教程了。所以原大佬如果看到这篇教程感觉这个代码结构很像你的,别纠结,可能就是你的(捂脸)。当然我在这里吸收精华之后有修改了一些自己的理解。因此来学习的也不必担心我并不是讲一个不属于自己的代码。
首先定义了一个Grid类。这个类是每一个结点的类
public class Grid
{
public Grid Parent; //父节点
public int F;
public int G;
public int H;
public int X; //位置X
public int Y; //位置Y
public bool CantWalk; //是否能通行
public Grid(int x, int y) //构造函数
{
X = x;
Y = y;
CantWalk = false;
Parent = null;
}
public void ChangeParent(Grid parent, int g) //修改父节点
{
Parent = parent;
G = g;
F = G + H;//修改G之后需要重新计算
}
}
接下来是主类的定义部分我只留了一个地图,和一个单例。