您的位置:首页 > 移动开发 > Unity3D

Unity算法——A*(AStar)寻路算法概要及简单应用

2018-01-19 20:45 471 查看
非常简陋的版本的GIF图,放在开头。



前言:

再Unity中寻路导航是游戏开发的最基本的需求之一,但是使用unity自带的NavMeshAgent方法来做的话经常会达不到我们想要的效果,首先是Nav会极大的消耗性能,从游戏优化的角度来看的话是不推荐使用Nav的,再就是Nav是通过渲染网格来实现的,有时候会寻路不准,如果期望方向和期望速度不准确的话还会出现其他的 一些情况,那么如何运用A*来寻路导航呢,我们下面会给大家讲到

什么是A*寻路算法:

为什么这个算法被称为A*算法呢,*又是什么呢,以一个网格为中心点,他周围八个方向的网格就是*,


A寻路算法的估量代价*
在A*算法中核心的寻路依据就是估量代价,在A*中通常用 F 表示。F
= G + H
其中G表示当前点到起始点的估量代价,H表示当前点到终点的代价。


(起始点周围的八个点)

每个点里面的三个数字分别为: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.把起始格添加到开启列表。

2.重复如下的工作:

      a) 寻找开启列表中估量代价F值最低的格子。我们称它为当前格。

      b) 把它切换到关闭列表。

      c) 对相邻的8格中的每一个进行如下操作

          * 如果它不可通过或者已经在关闭列表中,略过它。反之如下。

          * 如果它不在开启列表中,把它添加进去。把当前格作为这一格的父节点。记录这一格的F,G,和H值。

          * 如果它已经在开启列表中,用G值为参考检查新的路径是否更好。更低的G值意味着更好的路径。如果是这样,就把这一格的父节点改成当前格,并且重新计算这一格的G和F值。如果你保持你的开启列表按F值排序,改变之后你可能需要重新对开启列表排序。

      d) 停止,当你

          * 把目标格添加进了关闭列表(注解),这时候路径被找到,或者

          * 没有找到目标格,开启列表已经空了。这时候,路径不存在。

3.保存路径。从目标格开始,沿着每一格的父节点移动直到回到起始格。这就是你的路径。

代码实现: 

1.AStarController

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<Grid> openList;
private List<Grid> closeList;
//结果栈
private Stack<Grid> result;

//所有格子数组
private Grid[,] allGrids = null;

void Awake()
{
result = new Stack<Grid> ();
openList = new List<Grid> ();
closeList = new List<Grid> ();
//设置数组长度
allGrids = new Grid[(int)(transform.localScale.x
* 20),(int)(transform.localScale.z * 20)];
}

void Start()
{
gridPrefab = Resources.Load<GameObject> ("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<Grid>();
//计算偏移量
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();
}
/// <summary>
/// A*计算
/// </summary>
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<MeshRenderer>();
}

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;
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息