A星寻路算法入门(Unity实现)
最近简单学习了一下A星寻路算法,来记录一下。
还是个萌新,如果写的不好,请谅解。
Unity版本:2018.3.2f1
A星寻路算法是什么
游戏开发中往往有这样的需求,让玩家控制的角色自动寻路到目标地点,或是让AI角色移动到目标位置,实际的情况可能很复杂,比如地图上有无法通过的障碍或者需要付出代价(时间或其他资源)才能通过的河流、沼泽等,想要让角色找到一条付出最小代价到达目标的路径,就需要使用一些特殊的算法,而A星寻路算法就是目前应用最广泛的寻路算法之一,unity asset store上广受好评的A* Pathfinding project插件也是基于A星寻路算法实现的,简单来说:A星算法是一种寻找最短路径并避开障碍物的算法。
A星算法的基本概念
要实现A星算法,首先需要将纷繁复杂的游戏地图抽象成寻路网格,最简单的方式是将游戏地图划分为多个正方形单元或正多边形单元,也可以划分为非均匀的凸多边形,这些网格可以看做是一个个“寻路点”,网格越精细,寻路的效果越好,但计算量也越大,所以针对实际的游戏环境,需要好好平衡一下性能和效果。
A星算法的基本思想就是借助这些网格实现寻路,从起点开始遍历四周的点,寻找最有可能在最短路径上的点,并以这个点为基准继续向四周遍历,直至遍历到终点,路径也就找到了。
通过这个思想也可以看出,A星算法其实只能得到一种近似最优解,实际上对于寻路问题,往往存在不止一个最优解,如果非要找出所有的解就只能遍历所有可能的路径一一比较,但这样效率太低,所以A星算法并不去遍历整个地图,而是只遍历了最短路径上的点和其周围的点,所以得到的是一种近似最优解。
那么遍历周围的点时怎样确定哪个点最有可能在最短路径上呢?这就是A星算法的核心:F=G+H
每个寻路点都有F、G、H这三个属性,F可以理解为通过这个点的总代价,代价越低,这个点当然就更有可能在最短路径上。G是从起点到这个点的代价,H是从这个点到终点的代价,这两个代价加起来就是这个点的总代价,关于具体如何计算,下面给出示例。
我们还需要两个集合,一个是open集合,一个是close集合,open集合里存放的是还未计算代价的点,close集合里是已经计算过的点。开始时open集合里只有起点,close集合没有元素,每次迭代将open集合里F最小的点作为基点,对于基点周围的相邻点做如下处理:
(1)如果这个点是障碍,直接无视。
(2)如果这个点不在open表和close表中,则加入open表
(3)如果这个点已经在open表中,并且当前基点所在路径代价更低,则更新它的G值和父亲
(4)如果这个点在close表中,忽略。
处理完之后将基点加入close集合。
当终点出现在open表中的时候,迭代结束。
如果到达终点前open表空了,说明没有路径可以到达终点。
A星算法实现
下面来动手实现最简单的A星算法,A星算法针对实际开发有着相当多的变化,怎样设计跟游戏的需求有关,这里用unity来实现一个最基本的2D正方形网格寻路,实际开发中也可以直接使用unity的导航网格或者A* Pathfinding Project插件。
在这个实现中,我定义了一个10x10的网格,网格中有一些无法通过的障碍。
public class Point
{
public int X;
public int Y;
public int F;
public int G;
public int H;
public Point parent=null; public bool isObstacle = false; public Point(int x,int y)
{
X = x;
Y = y;
} public void SetParent(Point parent,int g)
{
this.parent = parent;
G = g;
F = G + H;
}
}
这里定义了一个Point类代表每一个寻路点,X和Y代表坐标,F、G、H就是上面说的三个属性,isObstacle代表这个点是否是障碍(无法通过),parent则代表这个点的父亲结点,每当我们遍历到下一个可能在最短路径上的点时,就把它的父亲设为当前结点,这样寻路结束后我们可以从终点通过访问父亲结点一步步回溯到起点,将路径存储下来。
using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class AStar : MonoBehaviour
{
public const int width = ;
public const int height = ; public Point[,] map = new Point[height,width];
public SpriteRenderer[,] sprites = new SpriteRenderer[height, width];//图片和结点一一对应 public GameObject prefab; //代表结点的图片
public Point start;
public Point end; void Start()
{
InitMap();
//测试代码
AddObstacle(, );
AddObstacle(, );
AddObstacle(, );
AddObstacle(, );
AddObstacle(, );
AddObstacle(, );
SetStartAndEnd(, , , );
FindPath();
ShowPath();
} public void InitMap()//初始化地图
{
for(int i=;i<width;i++)
{
for (int j = ; j < height; j++)
{
sprites[i, j] = Instantiate(prefab, new Vector3(i, j, ),Quaternion.identity).GetComponent<SpriteRenderer>();
map[i, j] = new Point(i, j);
}
}
} public void AddObstacle(int x,int y)//添加障碍
{
map[x, y].isObstacle = true;
sprites[x, y].color = Color.black;
} public void SetStartAndEnd(int startX,int startY,int endX,int endY)//设置起点和终点
{
start = map[startX,startY];
sprites[startX, startY].color = Color.green;
end = map[endX, endY];
sprites[endX, endY].color = Color.red;
} public void ShowPath()//显示路径
{
Point temp = end.parent;
while(temp!=start)
{
sprites[temp.X, temp.Y].color = Color.gray;
temp = temp.parent;
}
} public void FindPath()
{
List<Point> openList = new List<Point>();
List<Point> closeList = new List<Point>();
openList.Add(start);
while(openList.Count>)//只要开放列表还存在元素就继续
{
Point point = GetMinFOfList(openList);//选出open集合中F值最小的点
openList.Remove(point);
closeList.Add(point);
List<Point> SurroundPoints = GetSurroundPoint(point.X,point.Y); foreach(Point p in closeList)//在周围点中把已经在关闭列表的点删除
{
if(SurroundPoints.Contains(p))
{
SurroundPoints.Remove(p);
}
} foreach (Point p in SurroundPoints)//遍历周围的点
{
if (openList.Contains(p))//周围点已经在开放列表中
{
//重新计算G,如果比原来的G更小,就更改这个点的父亲
int newG = + point.G;
if(newG<p.G)
{
p.SetParent(point, newG);
}
}
else
{
//设置父亲和F并加入开放列表
p.parent = point;
GetF(p);
openList.Add(p);
}
}
if (openList.Contains(end))//只要出现终点就结束
{
break;
}
}
} public List<Point> GetSurroundPoint(int x,int y)//得到一个点周围的点
{
List<Point> PointList = new List<Point>();
if(x>&&!map[x-,y].isObstacle)
{
PointList.Add(map[x - , y]);
}
if(y> && !map[x , y-].isObstacle)
{
PointList.Add(map[x, y - ]);
}
if(x<height- && !map[x + , y].isObstacle)
{
PointList.Add(map[x + , y]);
}
if(y<width- && !map[x , y+].isObstacle)
{
PointList.Add(map[x, y + ]);
}
return PointList;
} public void GetF(Point point)//计算某个点的F值
{
int G = ;
int H = Mathf.Abs(end.X - point.X) + Mathf.Abs(end.Y - point.Y);
if(point.parent!=null)
{
G = + point.parent.G;
}
int F = H + G;
point.H = H;
point.G = G;
point.F = F;
} public Point GetMinFOfList(List<Point> list)//得到一个集合中F值最小的点
{
int min = int.MaxValue;
Point point = null;
foreach(Point p in list)
{
if(p.F<min)
{
min = p.F;
point = p;
}
}
return point;
}
}
上面是A星算法的代码,我使用了一张100x100像素的图片代表每一个结点,修改它们的颜色用来表示起点、终点、障碍和路径。在这里我计算的方式是每移动一个格子代价为1,所以起点的G值为0,每次遍历把G+1,H则是当前结点和终点在x轴和y轴上的差之和。
最终效果(绿色代表起点,红色代表终点,黑色代表障碍,灰色代表路径)
寻路前
寻路结果
最后
A星寻路有相当多可以扩展的地方,只要抓住核心,就是不断计算周围点的代价,找出花费最小代价到达终点的路径,这个代价可以针对各种复杂的情况采取不同的计算方法,比如说一个FPS游戏的AI,游戏中玩家肯定会向火力范围内的敌人攻击,这时候如果为了走最短的路径而暴露在玩家的枪口下就得不偿失了,这时可以加大处在玩家攻击范围内的点的代价值,让AI在更短路径和受到攻击的风险之间做出权衡,或者某个地方有奖励道具,这时可以减少奖励道具附近的点的代价值,让AI更倾向于绕一些路去获取道具,总之理解了算法思想,就能灵活运用于各种寻路情境。
A星寻路算法入门(Unity实现)的更多相关文章
- 基于Unity的A星寻路算法(绝对简单完整版本)
前言 在上一篇文章,介绍了网格地图的实现方式,基于该文章,我们来实现一个A星寻路的算法,最终实现的效果为: 项目源码已上传Github:AStarNavigate 在阅读本篇文章,如果你对于里面提到的 ...
- A星寻路算法介绍
你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢? 如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! 在网上已经有很多篇关于A星寻路算法 ...
- A星寻路算法
A星寻路算法 1.准备一个close关闭列表(存放已被检索的点),一个open开启列表(存放未被检索的点),一个当前点的对象cur 2.将cur设成开始点 3.从cur起,将cur点放入close表中 ...
- cocos2d-x学习日志(13) --A星寻路算法demo
你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢?如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! A星算法简介: A*搜寻算法俗称A星 ...
- 无递归 A星寻路算法
整理硬盘的时候,发现我早些年写的A星寻路算法.特放上来,待有缘人拿去,无递归噢,性能那是杠杠的. 码上伺候 public class Node { public int X { get; set; } ...
- A星寻路算法(A* Search Algorithm)
你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢? 如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! 在网上已经有很多篇关于A星寻路算法 ...
- A星寻路算法-Mind&Hand(C++)
//注1:Mind & Hand,MIT校训,这里指的理解与实现(动脑也动手) //注2:博文分为两部分:(1)理解部分,为参考其他优秀博文的摘要梳理:(2)代码部分,是C++代码实现的,源码 ...
- 【Android】基于A星寻路算法的简单迷宫应用
简介 基于[漫画算法-小灰的算法之旅]上的A星寻路算法,开发的一个Demo.目前实现后退.重新载入.路径提示.地图刷新等功能.没有做太多的性能优化,算是深化对A星寻路算法的理解. 界面预览: 初始化: ...
- [转载]A星寻路算法介绍
转载自:http://www.raywenderlich.com/zh-hans/21503/a%E6%98%9F%E5%AF%BB%E8%B7%AF%E7%AE%97%E6%B3%95%E4%BB% ...
随机推荐
- 推荐一个国外C开发的PHP框架--Phalcon,性能相当好
本人亲自配置测试后.性能相当不错.不过有一点.使用极不符合国人习惯,甚至和大多数主流PHP框架如Zend Framework,Yii,Ci,Thinkphp都不一样. Phalcon 是一个开源的,全 ...
- 直播内容不合规怎么办?智能AI为您解决审核难题
背景 近些年来,视频直播快速发展,大量的直播平台如雨后春笋一般出现,但是这同样给直播内容的监管带来了巨大的挑战,一方面国家对于直播内容监管的要求日益严格,另一方面相对于文字内容的审核,多媒体内容的审核 ...
- [PC]可用于Windows Server 2008 R2的Xbox One手柄、接收器驱动
让客厅里的Gen8可以玩FC和PS1游戏,折腾了半天,终于将Xbox One手柄驱动弄好: http://www.drvsky.com/Microsoft/Xbox_One.htm http://ww ...
- 安装composer出现链接补上的问题
下载 Composer-Setup.exe 后安装出错: Composer Download Error Connection Error [ERR_CONNECTION]: Unable to co ...
- [python]pip 版本9.0.1升级到10.0.1故障解决办法
问题背景: 在做android自动化时使用到第三方库uiautomator时,提示要安装,但安装该uiautomator库时提示当前的pip版本偏低,需要安装10.0.1版本方可.但在升级到升级到该版 ...
- Python3中遇到UnicodeEncodeError: 'ascii' codec can't encode characters in ordinal not in range(128)
在 linux服务器上运行代码报错: Python3中遇到UnicodeEncodeError: ‘ascii’ codec can’t encode characters in ordinal no ...
- SAP客户端 测试机、开发机、生产机
SAP客户端 测试机.开发机.生产机 客户端(即Client),是SAP组织架构里最高层的组织单元,所有数据,包括静态数据(科目.客户.供应商.物料.资产等).业务数据(采购订单.销售订单. ...
- Maven 安装源码和文档到本地仓库
一: 1: mvn source:jar 生成源码的jar包 2: mvn source:jar install 将源码安装到本地仓库 ,可以直接mvn source:jar install 一部 ...
- Fuel 30 分钟快速安装OpenStack
一直以来,对于openstack 的初学者来讲,安装往往是入门的头大难题.在E版本之前,要搭建一个基本能用的openstack 环境那是相当麻烦,自己要装机,自己搞源,自己照着文档敲命令,又没有靠谱的 ...
- css3动画功能介绍
一:过渡动画---Transitions 含义:在css3中,Transitions功能通过将元素的某个属性从一个属性值在指定的时间内平滑过渡到另一个属性值来实现动画功能. Transitions属性 ...