非常简陋的版本的GIF图,放在开头。
前言:
再Unity中寻路导航是游戏开发的最基本的需求之一,但是使用unity自带的NavMeshAgent方法来做的话经常会达不到我们想要的效果,首先是Nav会极大的消耗性能,从游戏优化的角度来看的话是不推荐使用Nav的,再就是Nav是通过渲染网格来实现的,有时候会寻路不准,如果期望方向和期望速度不准确的话还会出现其他的 一些情况,那么如何运用A*来寻路导航呢,我们下面会给大家讲到
什么是A*寻路算法:
为什么这个算法被称为A*算法呢,*又是什么呢,以一个网格为中心点,他周围八个方向的网格就是*,
每个点里面的三个数字分别为:1.左下角是距离起始点的估量代价,记为G。A距离中心点的距离为1的直线距离,B距离中心点的距离为根号2,约1.4,在这里基础单位为10的话,A的起始点估量代价G=10,B的起始点估量代价G=14 。 2.右下角是距离终点的估量代价,记为H,为该点到终点的步数既几步可达终点。3.左上角是综合估量代价,记为F=G+H,起始点估量代价与终点估量代价的和;
A*算法的核心是两个集合分别为开放列表与关闭列表:Open List,CloseList
原理:
从中心位置对相邻格子
假设A是起始格子
OpenList CloseList
A
A1
...
A6
A7
从中心位置对相邻8个格子进行判断最小代价,将A移除并添加进CloseList中,并对OpenList进行排序
假设最小代价是是A4,将A4放在OpenList表头,并查找A4周围的8个格子,A41-A48
OpenList CloseList
A4 A
A1
...
A6
A7
A41
A42
...
A48
将A4从Open中移除并添加到CloseList中,
对OpenList进行排序,假设A42的最小代价最小
将A42放在Open的表头,并查询A42相邻8个格子
OpenList CloseList
A42 A
A1 A4
...
A6
A7
A41
...
A48
...........................
OpenList CloseList
A4234 A
A1 A4
... A42
A6 A423
A7 A4234
A41
...
A48
最后可能得到的CloseList如上所示,
A4->A4234就是A*算法得到的最短路径
实现步骤:
1.把起始格添加到开启列表。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AStarController : MonoBehaviour {
//起点坐标
public int startPosX, startPosY;
//终点坐标
public int endPosX, endPosY;
//障碍物比率
public int obstacleRate;
private GameObject gridPrefab;
private List openList;
private List closeList;
//结果栈
private Stack result;
//所有格子数组
private Grid[,] allGrids = null;
void Awake()
{
result = new Stack ();
openList = new List ();
closeList = new List ();
//设置数组长度
allGrids = new Grid[(int)(transform.localScale.x
* 20),(int)(transform.localScale.z * 20)];
}
void Start()
{
gridPrefab = Resources.Load ("Grid");
//遍历生成格子
for (int i = 0; i < transform.localScale.x * 20; i++) {
for (int j = 0; j < transform.localScale.z * 20; j++) {
//生成
Grid currentGrid = Instantiate (gridPrefab).
GetComponent();
//计算偏移量
Vector2 offset = new Vector2 (-4.7f * transform.
localScale.x,-4.7f * transform.localScale.z);
//设置方块的世界坐标
currentGrid.transform.position = new Vector3 (
offset.x + i * 0.5f, 0, offset.y + j * 0.5f);
//设置格子坐标
currentGrid.x = i;
currentGrid.y = j;
//存储起来
allGrids[i,j] = currentGrid;
//随机障碍物
int r = Random.Range(1,101);
if (r <= obstacleRate) {
currentGrid.MyGridType = GridType.Obstacle;
}
}
}
//设置起点和终点
allGrids[startPosX,startPosY].MyGridType = GridType.Start;
allGrids [endPosX, endPosY].MyGridType = GridType.End;
//调用AStar计算
AStarCount();
}
///
/// A*计算
///
void AStarCount()
{
//将起点放置到OpenList
openList.Add (allGrids [startPosX, startPosY]);
//获取当前要发现的中心格子
Grid currentGrid = openList[0];
//循环递归
//开启列表中有对象&&当前的中心不是终点
while (openList.Count > 0 &&
currentGrid.MyGridType != GridType.End) {
//重新排序
openList.Sort();
//获取新的中心格子
currentGrid = openList[0];
//判断最新的格子是否是终点
if (currentGrid.MyGridType == GridType.End) {
///TODO:生成结果
GetParent(currentGrid);
return;
}
//上下左右,左上右上左下右下
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
if (i != 0 || j != 0) {
//获取新格子的格子坐标
int x = currentGrid.x + i;
int y = currentGrid.y + j;
//判断格子坐标合法
//前四个条件判断坐标的合法性
//新格子不能是障碍物
//新格子没有被遍历过
if (x > 0 && y > 0 && x < allGrids.GetLength (0)
&& y < allGrids.GetLength (1) &&
allGrids [x, y].MyGridType != GridType.Obstacle &&
!closeList.Contains (allGrids [x, y])) {
//计算G值
int g = (int)(currentGrid.G +
Mathf.Sqrt(Mathf.Abs(i) + Mathf.Abs(j))*10);
//判断新格子是否被遍历过
//如果被遍历过,判断当前G值是否比之前的更小
if (allGrids [x, y].G == 0 || g < allGrids [x, y].G) {
//更新G值
allGrids[x,y].G = g;
//更新父格子
allGrids[x,y].parent = currentGrid;
}
//计算H
allGrids[x,y].H = (Mathf.Abs(x - endPosX) + Mathf.Abs(y- endPosY)) * 10;
//计算F
allGrids[x,y].F = allGrids[x,y].G + allGrids[x,y].H;
//加入到开启列表
if (!openList.Contains (allGrids [x, y])) {
Debug.Log (1);
openList.Add (allGrids [x, y]);
}
}
}
}
}
//将当前格子移除OpenList
openList.Remove(currentGrid);
//放到CloseList里
closeList.Add(currentGrid);
//OpenList空了
if (openList.Count == 0) {
Debug.Log ("Can Not Arrave!!!");
}
}
}
private void GetParent(Grid current)
{
//进栈
result.Push (current);
//判断是否继续递归
if (current.parent != null) {
GetParent (current.parent);
} else {
//展示结果
StartCoroutine (ShowResult ());
}
}
IEnumerator ShowResult()
{
//获取总长度
int resultCount = result.Count;
while (result.Count > 0) {
yield return new WaitForSeconds(0.1f);
//出栈
Grid currentResultGrid = result.Pop();
//计算比例
float scale = (resultCount - result.Count)/(float)resultCount;
//上色
Color currentC = Color.Lerp(Color.red,Color.green,scale);
currentResultGrid.SetColor(currentC);
}
}
void Update()
{
if (Input.GetKeyDown (KeyCode.Space)) {
UnityEngine.SceneManagement.SceneManager.LoadScene (0);
}
}
}
2.GridController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public enum GridType {
//正常类型
Normal,
//障碍物类型
Obstacle,
//起点类型
Start,
//终点类型
End
}
public class GridController : MonoBehaviour,IComparable {
//坐标
public int x ,y;
//FGH
public int F, G, H;
//坐标
public GridController parent;
//格子类型
private GridType gridType;
public GridType myGridType {
get {
return gridType;
}
set {
gridType = value;
//设置显示颜色
Color tempColor = Color.white;
switch (gridType) {
case GridType.Start:
tempColor = Color.red;
break;
case GridType.End:
tempColor = Color.green;
break;
case GridType.Obstacle:
tempColor = Color.blue;
break;
default:
break;
}
SetColor(tempColor);
}
}
private MeshRenderer meshRenderer;
private void Awake() {
meshRenderer = GetComponent();
}
public void SetColor(Color c) {
meshRenderer.material.color = c;
}
public int CompareTo(object obj) {
GridController target = obj as GridController;
if (F < target.F) {
return -1;
}else if (F > target.F) {
return 1;
}else {
return 0;
}
}
}