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

非常简陋的版本的GIF图,放在开头。

Unity算法——A*(AStar)寻路算法概要及简单应用_第1张图片

前言:

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

什么是A*寻路算法:

为什么这个算法被称为A*算法呢,*又是什么呢,以一个网格为中心点,他周围八个方向的网格就是*,Unity算法——A*(AStar)寻路算法概要及简单应用_第2张图片

  • A寻路算法的估量代价*
    在A*算法中核心的寻路依据就是估量代价,在A*中通常用 F 表示。F = G + H
    其中G表示当前点到起始点的估量代价,H表示当前点到终点的代价。
Unity算法——A*(AStar)寻路算法概要及简单应用_第3张图片(起始点周围的八个点)

每个点里面的三个数字分别为:1.左下角是距离起始点的估量代价,记为G。A距离中心点的距离为1的直线距离,B距离中心点的距离为根号2,约1.4,在这里基础单位为10的话,A的起始点估量代价G=10,B的起始点估量代价G=14 。 2.右下角是距离终点的估量代价,记为H,为该点到终点的步数既几步可达终点。3.左上角是综合估量代价,记为F=G+H,起始点估量代价与终点估量代价的和;

Unity算法——A*(AStar)寻路算法概要及简单应用_第4张图片(网图,其中一些估量代价是错的,仅供参考,侵删)

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 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;
        }
    }
}



你可能感兴趣的:(Unity算法)