unity A星寻路教程
核心代码
使用说明:
需要自行设置,地图数据,起点,终点
直接调用 AStarPath.FindPath 即可
using System.Collections;
using System.Collections.Generic;
using UnityEngine; /// <summary>
/// A星寻路
/// </summary>
public class AStarPath
{
/// <summary>
/// 寻路
/// </summary>
/// <param name="map">地图数据</param>
/// <param name="start">起点</param>
/// <param name="end">终点</param>
/// <returns></returns>
public static List<Point> FindPath(Point[,] map, Point start, Point end)
{
List<Point> openList = new List<Point>();
List<Point> closeList = new List<Point>();
openList.Add(start);
while(openList.Count>0)//只要开放列表还存在元素就继续
{
Point point = GetMinFOfList(openList);//选出open集合中F值最小的点
openList.Remove(point);
closeList.Add(point);
List<Point> SurroundPoints = GetSurroundPoint(map, 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 = 1 + point.G;
if(newG<p.G)
{
p.SetParent(point, newG);
}
}
else
{
//设置父亲和F并加入开放列表
p.parent = point;
GetF(p, end);
openList.Add(p);
}
}
if (openList.Contains(end))//只要出现终点就结束
{
break;
}
} List<Point> pathList = new List<Point>();
Point temp = end.parent;
if (temp == null)
{
//路线不通
Debug.Log("选择的路线不通");
return null;
}
while(temp!=start)
{
pathList.Add(temp);
temp = temp.parent;
} return pathList;
} //得到一个点周围的点
static List<Point> GetSurroundPoint(Point[,] map, int x,int y)
{
List<Point> PointList = new List<Point>();
if(x > 0 && !map[x-1,y].isObstacle)
{
PointList.Add(map[x - 1, y]);
}
if(y > 0 && !map[x , y-1].isObstacle)
{
PointList.Add(map[x, y - 1]);
}
if(x < map.GetLength(0)-1 && !map[x + 1, y].isObstacle)
{
PointList.Add(map[x + 1, y]);
}
if(y < map.GetLength(1)-1 && !map[x , y+1].isObstacle)
{
PointList.Add(map[x, y + 1]);
}
return PointList;
} //计算某个点的F值
static void GetF(Point point, Point end)
{
int G = 0;
int H = Mathf.Abs(end.X - point.X) + Mathf.Abs(end.Y - point.Y);
if(point.parent!=null)
{
G = 1 + point.parent.G;
}
int F = H + G;
point.H = H;
point.G = G;
point.F = F;
} //得到一个集合中F值最小的点
static Point GetMinFOfList(List<Point> list)
{
int min = int.MaxValue;
Point point = null;
foreach(Point p in list)
{
if(p.F<min)
{
min = p.F;
point = p;
}
}
return point;
}
} 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;//障碍物
//类型 空0 障碍物1 棋子2
//public int typeNum; 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;
}
}
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 = 10;
public const int height = 10; 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(2, 4);
AddObstacle(2, 3);
AddObstacle(2, 2);
AddObstacle(2, 0);
AddObstacle(6, 4);
AddObstacle(8, 4);
SetStartAndEnd(0, 0, 7, 7);
FindPath();
ShowPath();
} public void InitMap()//初始化地图
{
for(int i=0;i<width;i++)
{
for (int j = 0; j < height; j++)
{
sprites[i, j] = Instantiate(prefab, new Vector3(i, j, 0),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>0)//只要开放列表还存在元素就继续
{
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 = 1 + 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>0&&!map[x-1,y].isObstacle)
{
PointList.Add(map[x - 1, y]);
}
if(y>0 && !map[x , y-1].isObstacle)
{
PointList.Add(map[x, y - 1]);
}
if(x<height-1 && !map[x + 1, y].isObstacle)
{
PointList.Add(map[x + 1, y]);
}
if(y<width-1 && !map[x , y+1].isObstacle)
{
PointList.Add(map[x, y + 1]);
}
return PointList;
} public void GetF(Point point)//计算某个点的F值
{
int G = 0;
int H = Mathf.Abs(end.X - point.X) + Mathf.Abs(end.Y - point.Y);
if(point.parent!=null)
{
G = 1 + 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更倾向于绕一些路去获取道具,总之理解了算法思想,就能灵活运用于各种寻路情境。
转载地址:https://www.cnblogs.com/LiveForGame/p/10528393.html
unity A星寻路教程的更多相关文章
- A星寻路算法入门(Unity实现)
最近简单学习了一下A星寻路算法,来记录一下.还是个萌新,如果写的不好,请谅解.Unity版本:2018.3.2f1 A星寻路算法是什么 游戏开发中往往有这样的需求,让玩家控制的角色自动寻路到目标地点, ...
- 基于Unity的A星寻路算法(绝对简单完整版本)
前言 在上一篇文章,介绍了网格地图的实现方式,基于该文章,我们来实现一个A星寻路的算法,最终实现的效果为: 项目源码已上传Github:AStarNavigate 在阅读本篇文章,如果你对于里面提到的 ...
- A星寻路算法介绍
你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢? 如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! 在网上已经有很多篇关于A星寻路算法 ...
- 用简单直白的方式讲解A星寻路算法原理
很多游戏特别是rts,rpg类游戏,都需要用到寻路.寻路算法有深度优先搜索(DFS),广度优先搜索(BFS),A星算法等,而A星算法是一种具备启发性策略的算法,效率是几种算法中最高的,因此也成为游戏中 ...
- cocos2d-x学习日志(13) --A星寻路算法demo
你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢?如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! A星算法简介: A*搜寻算法俗称A星 ...
- A星寻路算法(A* Search Algorithm)
你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢? 如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! 在网上已经有很多篇关于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% ...
- A星寻路算法
A星寻路算法 1.准备一个close关闭列表(存放已被检索的点),一个open开启列表(存放未被检索的点),一个当前点的对象cur 2.将cur设成开始点 3.从cur起,将cur点放入close表中 ...
- 无递归 A星寻路算法
整理硬盘的时候,发现我早些年写的A星寻路算法.特放上来,待有缘人拿去,无递归噢,性能那是杠杠的. 码上伺候 public class Node { public int X { get; set; } ...
- 零积分下载,2014年辛星mysql教程秋季版第一本已经完工,期待您的支持
经过一段时间的不懈努力.终于,2014年辛星mysql教程秋季版的第一本,即夯实基础已经完工,在csdn的下载地址为:去下载地址 ,假设左边地址跪了,能够去http://download.csdn.n ...
随机推荐
- ASP.NET Core相关下载资源汇总
1.Net.6的Host Bundle的下载地址(IIS)服务配套组件: https://dotnet.microsoft.com/zh-cn/download/dotnet/6.0 2.docker ...
- Palindrome Number——LeetCode进阶路⑨
//原题链接https://leetcode.com/problems/palindrome-number/ 题目描述 Determine whether an integer is a palind ...
- AI写程序:让Manus分析一个github项目生成一个官方网站
提出问题,使用Manus帮我生成一个官方网站 提问: 我有一个github项目,是https://github.com/dependon/simple-image-filter ,请根据这个项目的内容 ...
- IntelliJ IDEA 2023.1 破解教程mac,windows,linux均适用/JetBrains产品全版本激活
前言 该激活方式不限于IDEA,同样也适用于JetBrains 全家桶的所有工具, 包括 IntelliJ IDEA.Pycharm.WebStorm.PhpStorm.AppCode.Datagri ...
- .NET SDK样式项目打包时如何将引用项目打进同一个包
此篇为上一篇[<.NET SDK样式项目打包时如何将项目引用转为包依赖>](https://www.cnblogs.com/cnsharp/p/18819771 ")的姊妹篇. ...
- pytorch入门 - VGG16神经网络
1. VGG16背景介绍 VGG-16是由牛津大学Visual Geometry Group(VGG)在2014年提出的深度卷积神经网络模型,它在当年的ImageNet大规模视觉识别挑战赛(ILSVR ...
- Android stuidio 上传项目代码至码云
这居然是我的第一篇博客,如何用Android studio上传项目至码云呢?话不多说直接开始,我找了很多方法碰壁很多最终看到了这篇博客:https://blog.csdn.net/shuijianba ...
- SAP采购订单屏幕控制
这里看交货计划的屏幕控制:交货计划增强,不可编辑 采购订单的程序是用类+屏幕写的. 所以直接看采购订单的程序:MEGUI 找到我们需要增强的屏幕,屏幕号1320 对应的类对象. BUILD_DYNPR ...
- [书籍精读] 《你不知道的JavaScript(上卷)》精读笔记分享
写在前面 书籍介绍:JavaScript这门语言简单易用,很容易上手,但其语言机制复杂微妙,即使是经验丰富的JavaScript开发人员,如果没有认真学习的话也无法真正理解.本套书直面当前JavaSc ...
- 致谢每一位ChunJun Contributor!这里有一份礼物等你领取!
作为一个批流统一的数据集成框架,秉承着易用.稳定.高效的目标,ChunJun于2018年4月29日在Github上将内核源码正式开放. 从还被叫作FlinkX,写下第一行代码开始,ChunJun已经走 ...