【Unity】A*算法搜索过程可视化

前言

A*算法是常用的游戏算法之一,也是初学者比较难掌握的一个算法。

本文实现的是在Unity引擎中,以可视化的方式形象表现A*算法搜索的具体过程步骤,

包括地图的搜索路径产生过程、FGH的计算以及开启关闭列表的变化等。


博文首发地址:http://blog.csdn.net/duzixi


一、GUI可视化版本

以下脚本为GUI可视化版本,将其添加给新场景的主摄像机,运行即可。

/// <summary>
/// A*算法 Unity GUI实现
/// Created by 杜子兮(duzixi.com) 2015.2.19
/// Edited by 马帅 2015.5.5
/// www.lanou3g.com All Rights Reserved
/// </summary>

using UnityEngine;
using System.Collections;
using System; // 用到排序接口

// 枚举:定义格子类型
public enum GridType
{
	Normal,    // 常规
	Obstacle,  // 障碍
	Start,     // 起点
	End        // 终点
}

// 定义格子类(继承可比较接口 IComparable)
public class Grid : IComparable
{
	public int x;		// x 坐标
	public int y;       // y 坐标
	public int F;       // 总评分
	public int G;       // 从起点到当前点的消耗值
	public int H;       // 从当前点到终点的估算值(直走10,斜走14)
	public GridType gridType;  // 格子类型
	public Grid fatherNode;
	
	// 可比较接口的实现(用于排序)
	public int CompareTo (object obj)
	{   
		Grid g1 = (Grid)obj; // 强制类型转换
		if (this.F < g1.F)    // 升序
			return -1;  
		if (this.F > g1.F)    // 降序
			return 1;  
		return 0;             // 相等
	}
}

// A*算法
public class AStar : MonoBehaviour
{

	private const int col = 7;          // 列数
	private const int row = 5;          // 行数
	private int size = 70;              // 大小
		
	private Grid[,] map;                // 地图(格子二维数组)
	private const int xStart = 1;       
	private const int yStart = 1;       
	private const int xEnd = 2;         
	private const int yEnd = 6;        
		
	ArrayList openList;                 // 开启列表(重要!!)
	ArrayList closeList;                // 关闭列表(重要!!)
		
	// 初始化
	void Start ()
	{
		map = new Grid[row, col];       // 创建地图
		for (int i = 0; i < row; i++) {
			for (int j = 0; j < col; j++) {
				map [i, j] = new Grid ();  // 实例化格子
				map [i, j].x = i;			// x坐标赋值
				map [i, j].y = j;			// y坐标赋值
			}
		}
			
		map [xStart, yStart].gridType = GridType.Start;  // 确定开始位置
		map [xStart, yStart].H = Manhattan (xEnd, yEnd); // 初始化开始位置的H值
		map [xEnd, yEnd].gridType = GridType.End;	     // 确定结束位置
		for (int i = 1; i <= 3; i++) {		             // 确定障碍位置
			map [i, 3].gridType = GridType.Obstacle;	
		}
			
		openList = new ArrayList ();		             // 初始化开启列表
		openList.Add (map [xStart, yStart]);  		     // 将开始节点放入开放列表中
		closeList = new ArrayList ();	                 // 初始化关闭列表
	}
		
	void OnGUI ()
	{
		// 绘制地图
		for (int i = 0; i < row; i++) {
			for (int j = 0; j < col; j++) {
				// 根据格子类型设置背景颜色
				Color bgColor;
				if (map [i, j].gridType == GridType.Start) {
					bgColor = Color.green;
				} else if (map [i, j].gridType == GridType.End) {
					bgColor = Color.red;
				} else if (map [i, j].gridType == GridType.Obstacle) {
					bgColor = Color.blue;
				} else if (closeList.Contains (map [i, j])) {
					bgColor = Color.black;
				} else {
					bgColor = Color.gray;
				}
				GUI.backgroundColor = bgColor;
				// 用按钮表示格子
				GUI.Button (new Rect (j * size, i * size, size, size), FGH (map [i, j]));
			}
		}
			
		if (GUI.Button (new Rect (col * size, 0, size, size), "Go Next")) {
			NextStep ();
		}
			
		// 绘制开启列表
		for (int j = 0; j < openList.Count; j++) {
			GUI.Button (new Rect (j * size, (row + 1) * size, size, size), FGH ((Grid)openList [j]));
		}
			
		// 绘制关闭列表
		for (int j = 0; j < closeList.Count; j++) {
			GUI.Button (new Rect (j * size, (row + 2) * size, size, size), FGH ((Grid)closeList [j]));
		}
	}
		
	// 通过逆向追溯找到路径
	void showFatherNode (Grid grid)
	{
		if (grid.fatherNode != null) {
			print (grid.fatherNode.x + "," + grid.fatherNode.y);
			showFatherNode (grid.fatherNode);	
		} 
	}
		
	// 走下一步
	void NextStep ()
	{
		//  0. 只要开启列表有节点, 就进行下一个过程
		if (openList.Count == 0) {
			print ("Over !");
			return;
		}
			
		//	1. 从开放列表中选择第一个节点并将其作为当前节点
		Grid grid = (Grid)openList [0];
		if (grid.gridType == GridType.End) {
			showFatherNode (grid);
			print ("Over !");
			return;	
		}
			
		//	2. 获得这个当前节点不是障碍物的邻近节点
		for (int m = -1; m <= 1; m++) {
			for (int n = -1; n <= 1; n++) {
				if (!(m == 0 && n == 0)) {
					int x = grid.x + m;
					int y = grid.y + n;
					//	3. 对于每一个邻近节点,查看是否已在关闭列表中.
					if (x >= 0 && x < row && y >= 0 && y < col &&
						map [x, y].gridType != GridType.Obstacle && 
						!closeList.Contains (map [x, y])) {

						// 4.计算G 
						//  2015.5.5 move
						int g = grid.G + (int)(Mathf.Sqrt (Mathf.Abs (m) + Mathf.Abs (n)) * 10);
						// 如果不在closeList 
						if (!openList.Contains (map [x, y])) { //如果不在集合里
							//  2015.5.5 delete
//								if (map[x, y].G == 0 || g < map[x, y].G) {
//									map [x, y].G = g;
//									
//								}
							map [x, y].G = g;
							map [x, y].H = Manhattan (x, y);
							map [x, y].F = map [x, y].G + map [x, y].H;
								
							//	5.将代价数据存储在邻近节点中,并且将当前节点保存为该邻近节点的父节点.
							//    最后我们将使用这个父节点数据来追踪实际路径.
							map [x, y].fatherNode = grid;
							//	6.将邻近节点存储在开放列表中.
							
							openList.Add (map [x, y]);
						} else { // 如果在集合里 2015.5.5 add
							if (map [x, y].G > g) { // 2015.7.15
								map [x, y].fatherNode = grid;
								map [x, y].G = g;
								map [x, y].F = map [x, y].G + map [x, y].H;
							}
						}
						//  7.根据F,以升序排列开放列表.
						openList.Sort ();
					}
				}
			}	
		}
		//	8. 如果没有邻近节点需要处理, 将当前节点放入关闭列表并将其从开放列表中移除.
		closeList.Add (grid);
		openList.Remove (grid);
	}
		
	// H值(曼哈顿估算法)
	int Manhattan (int x, int y)
	{
		return (int)(Mathf.Abs (xEnd - x) + Mathf.Abs (yEnd - y)) * 10;
	}
		
	// 将格子FGH 以字符串形式显示
	string FGH (Grid grid)
	{
		string fgh = "F:" + grid.F + "\n";
		fgh += "G:" + grid.G + "\n";
		fgh += "H:" + grid.H + "\n";
		fgh += "(" + grid.x + "," + grid.y + ")";
		return fgh;
	}
}



运行后点击画面上的“Go Next”按钮,即可观察每部计算详情。




二、游戏对象及线性渲染可视化版本


核心逻辑与第一种方式相同,代码不再赘述。

工程下载链接请参见4楼评论。

所有的参数可在工程中GameController对象上的脚本属性里设置,

运行后Game场景里点击鼠标即可观察路径搜索过程。



后语

A*算法的具体实现细节有很多,本文脚本只是给出了其中一种。

另外,按照这个算法障碍墙是可以斜穿的,若要避免斜穿还需进一步修改。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
烘培桥是指将场景中的网格图(NavMesh)生成并保存在磁盘上,以便在运行时进行快速的路径计算。要在Unity中使用A*路径查找算法,需要使用Unity的NavMesh系统,并使用A*寻路插件来执行路径计算。 以下是在Unity中使用A*路径查找算法的步骤: 1. 安装A*寻路插件:从Unity Asset Store中下载并导入A* Pathfinding Project Pro插件。 2. 创建场景:在Unity中创建一个场景,并添加游戏对象和地形。 3. 创建NavMesh:使用Unity的NavMesh系统来创建场景的NavMesh。在场景中选择菜单栏的“Window”->“Navigation”,打开“Navigation”窗口,然后单击“Bake”按钮来烘焙NavMesh。 4. 添加A*路径查找组件:在场景中选择要使用A*路径查找算法游戏对象,然后右键单击并选择“Add Component”->“A* Pathfinding”->“A* Pathfinding Component”。这将添加一个A*路径查找组件到游戏对象上。 5. 设置路径查找组件:在A*路径查找组件的属性窗口中,设置起点和终点,以及其他路径查找参数,例如搜索半径、移动速度和路径平滑等。 6. 执行路径查找:在游戏运行时,调用A*路径查找组件的路径查找函数来执行路径查找。例如,在脚本中调用“GetComponent<AstarPath>().Scan()”函数来执行路径查找。 7. 显示路径:在游戏运行时,使用A*路径查找组件的路径可视化功能来显示路径。例如,在脚本中调用“GetComponent<AstarPath>().DrawPath()”函数来显示路径。 以上是在Unity中使用A*路径查找算法的基本步骤。通过使用A*寻路插件和Unity的NavMesh系统,可以轻松地实现高效的路径查找功能,从而为游戏开发带来更好的用户体验。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值