# A*寻路算法在Unity中的简单应用

### 前言

• A寻路算法的估量代价*
在A*算法中核心的寻路依据就是估量代价，在A*中通常用 F 表示。

F = G + H

FGH示意图
``````                图中格子左下角为G值，右下角为H值，左上角为F值
``````

A*行走路线
##### 下面我写了一个小例子，方便大家学习。
• 简单效果

简单效果
• 首先需要创建一个格子类Grid

``````using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

/// <summary>
/// 格子类型
/// </summary>
public enum GridType
{
//正常类型
Normal,
//障碍物类型
Obstacle,
//起点类型
Start,
//终点类型
End
}

/// <summary>
/// 格子类（实现IComparable方便排序）
/// </summary>
public class Grid : IComparable
{
//格子坐标x-y
public int x;
public int y;
//格子A*三属性f-g-h
public int f;
public int g;
public int h;
//格子类型
public GridType type;
//格子的归属（父格子）
public Grid parent;
//构造赋值
public Grid (int x, int y)
{
this.x = x;
this.y = y;
}

/// <summary>
/// 实现排序接口方法
/// </summary>
/// <returns>The to.</returns>
/// <param name="obj">Object.</param>
public int CompareTo (object obj)
{
Grid grid = (Grid)obj;
if (this.f < grid.f) {
//升序
return -1;
}
if (this.f > grid.f) {
//降序
return 1;
}
return 0;
}
}

``````
• 然后主逻辑AStar类
``````using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class MyAStar : MonoBehaviour
{
/// <summary>
/// 单例脚本
/// </summary>
public static MyAStar instance;

//参考物体预设体
public GameObject reference;
//格子数组
public Grid[,] grids;
//格子数组对应的参考物（方块）对象
public GameObject[,] objs;
//开启列表
public ArrayList openList;
//关闭列表
public ArrayList closeList;
//目标点坐标
public int targetX;
public int targetY;
//起始点坐标
public int startX;
public int startY;

//格子行列数
private int row;
private int colomn;
//结果栈
private Stack<string> parentList;
//基础物体
private Transform plane;
private Transform start;
private Transform end;
private Transform obstacle;
//流颜色参数
private float alpha = 0;
private float incrementPer = 0;

void Awake ()
{
instance = this;
plane = GameObject.Find ("Plane").transform;
start = GameObject.Find ("Start").transform;
end = GameObject.Find ("End").transform;
obstacle = GameObject.Find ("Obstacle").transform;
parentList = new Stack<string> ();
openList = new ArrayList ();
closeList = new ArrayList ();
}

/// <summary>
/// 初始化操作
/// </summary>
void Init ()
{
//计算行列数
int x = (int)(plane.localScale.x * 20);
int y = (int)(plane.localScale.z * 20);
row = x;
colomn = y;
grids = new Grid[x, y];
objs = new GameObject[x, y];
//起始坐标
Vector3 startPos =
new Vector3 (plane.localScale.x * -5, 0, plane.localScale.z * -5);
//生成参考物体（Cube）
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
grids [i, j] = new Grid (i, j);
GameObject item = (GameObject)Instantiate (reference,
new Vector3 (i * 0.5f, 0, j * 0.5f) + startPos,
Quaternion.identity);
item.transform.GetChild (0).GetComponent<Reference> ().x = i;
item.transform.GetChild (0).GetComponent<Reference> ().y = j;
objs [i, j] = item;
}
}
}

/// <summary>
/// A*计算
/// </summary>
IEnumerator Count ()
{
//等待前面操作完成
yield return new WaitForSeconds (0.1f);
//添加起始点
//声明当前格子变量，并赋初值
Grid currentGrid = openList [0] as Grid;
//循环遍历路径最小F的点
while (openList.Count > 0 && currentGrid.type != GridType.End) {
//获取此时最小F点
currentGrid = openList [0] as Grid;
//如果当前点就是目标
if (currentGrid.type == GridType.End) {
Debug.Log ("Find");
//生成结果
GenerateResult (currentGrid);
}
//上下左右，左上左下，右上右下，遍历
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 < row && y < colomn
&& grids [x, y].type != GridType.Obstacle
&& !closeList.Contains (grids [x, y])) {
//计算G值
int g = currentGrid.g + (int)(Mathf.Sqrt ((Mathf.Abs (i) + Mathf.Abs (j))) * 10);
//与原G值对照
if (grids [x, y].g == 0 || grids [x, y].g > g) {
//更新G值
grids [x, y].g = g;
//更新父格子
grids [x, y].parent = currentGrid;
}
//计算H值
grids [x, y].h = Manhattan (x, y);
//计算F值
grids [x, y].f = grids [x, y].g + grids [x, y].h;
//如果未添加到开启列表
if (!openList.Contains (grids [x, y])) {
//添加
}
//重新排序
openList.Sort ();
}
}
}
}
//完成遍历添加该点到关闭列表
//从开启列表中移除
openList.Remove (currentGrid);
//如果开启列表空，未能找到路径
if (openList.Count == 0) {
Debug.Log ("Can not Find");
}
}

}

/// <summary>
/// 生成结果
/// </summary>
/// <param name="currentGrid">Current grid.</param>
void GenerateResult (Grid currentGrid)
{
//如果当前格子有父格子
if (currentGrid.parent != null) {
//添加到父对象栈（即结果栈）
parentList.Push (currentGrid.x + "|" + currentGrid.y);
//递归获取
GenerateResult (currentGrid.parent);
}
}

/// <summary>
/// 显示结果
/// </summary>
/// <returns>The result.</returns>
IEnumerator ShowResult ()
{
//等待前面计算完成
yield return new WaitForSeconds (0.3f);
//计算每帧颜色值增量
incrementPer = 1 / (float)parentList.Count;
//展示结果
while (parentList.Count != 0) {
//出栈
string str = parentList.Pop ();
//等0.3秒
yield return new WaitForSeconds (0.3f);
//拆分获取坐标
string[] xy = str.Split (new char[]{ '|' });
int x = int.Parse (xy [0]);
int y = int.Parse (xy [1]);
//当前颜色值
alpha += incrementPer;
//以颜色方式绘制路径
objs [x, y].transform.GetChild (0).GetComponent<MeshRenderer> ().material.color
= new Color (1 - alpha, alpha, 0, 1);
}
}

/// <summary>
/// 曼哈顿方式计算H值
/// </summary>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
int Manhattan (int x, int y)
{
return (int)(Mathf.Abs (targetX - x) + Mathf.Abs (targetY - y)) * 10;
}

void Start ()
{
Init ();
StartCoroutine (Count ());
StartCoroutine (ShowResult ());
}
}
``````
• 最后是参考预设体方块的简单实现
``````using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class Reference : MonoBehaviour
{
//颜色材质区分
public Material startMat;
public Material endMat;
public Material obstacleMat;
//显示信息Text
private Text text;
//当前格子坐标
public int x;
public int y;

void Awake ()
{
text = GameObject.Find ("Text").GetComponent<Text> ();
}
//判断当前格子的类型
void OnTriggerEnter (Collider other)
{
if (other.name == "Start") {
GetComponent<MeshRenderer> ().material = startMat;
MyAStar.instance.grids [x, y].type = GridType.Start;
MyAStar.instance.startX = x;
MyAStar.instance.startY = y;
} else if (other.name == "End") {
GetComponent<MeshRenderer> ().material = endMat;
MyAStar.instance.grids [x, y].type = GridType.End;
MyAStar.instance.targetX = x;
MyAStar.instance.targetY = y;
} else if (other.name == "Obstacle") {
GetComponent<MeshRenderer> ().material = obstacleMat;
MyAStar.instance.grids [x, y].type = GridType.Obstacle;
}
}

/// <summary>
/// 鼠标点击显示当前格子基础信息
/// </summary>
void OnMouseDown ()
{
text.text = "XY(" + x + "," + y + ")" + "\n" +
"FGH(" + MyAStar.instance.grids [x, y].f + "," +
MyAStar.instance.grids [x, y].g + "," +
MyAStar.instance.grids [x, y].h + ")";
text.color = GetComponent<MeshRenderer> ().material.color;
}
}
``````
• 多障碍效果图

多障碍效果图
• 遍历流程监测
其实A*遍历的格子还是蛮多的，因为曼哈顿计算的H值是不考虑障碍物的，所以会有很多格子的F值会很小，但不一定此时很小的F值格子就是要走的路径，最终的最优路径是通过，终点格子反推回来的，就如代码中GenerateResult递归方法所示，为了方便大家理解，我做了一个小动画，方便大家对A*的理解。（粉色是此时F值最小的格子，蓝色是最小F值格子周边正在遍历的格子，黄色格子是从未设置父物体，第一次被遍历的格子）

遍历流程监测

慢放版

### 结束语

A*只是游戏算法中的凤毛麟角，其中的逻辑也相对简单，所以想要提升编码质量，想要写出高效的游戏逻辑，还需要更多的算法学习。还是那个道理，编程 = 数据结构 + 算法，不带班的这段时间我会尽量分享一些东西给大家，同仁们加油。本文项目下载链接：http://pan.baidu.com/s/1hs13F8K 密码: rbs1

Unity