A*寻路是游戏设计使用比较频繁的寻路算法,主要原理是将平面划分为多个网格,来进行目标查找。
图例说明:
目标:
开始搜索:
启发式搜索:启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无谓的搜索路径,提到了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。
估价函数:从当前节点移动到目标节点的预估费用;这个估计就是启发式的。在寻路问题和迷宫问题中,我们通常用曼哈顿(manhattan)估价函数预估费用。
A*算法的特点:A*算法在理论上是时间最优的,但是也有缺点:它的空间增长是指数级别的。(优化:二叉堆)
在A*寻路算法中,我们通过从点A开始,检查相邻方格的方式,向外扩展直到找到目标。
开启列表:待检查方格的集合列表,寻找周围可以达到的点,加入到此表中,并保存中心点为父节点
关闭列表:列表中保存不需要再次检查的方格
路径评分:
G-与起始点的距离
H-与目标点的距离
F的值是G和H的和。F,G和H的评分被写在每个方格里。
正如在紧挨起始格右侧的方格所表示的,F被打印中间,G在左上角,H则在右上角。
选择路径中经过哪个方格的关键是 这个等式:F = G + H
开始搜索:
1,把起始格添加到开启列表。
2,寻找起点周围所有可到达或者可通过的方格,把他们加入开启列表。
3,从开启列表中删除点A,把它加 入到一个“关闭列表”,列表中保存所有不需要再次检查的方格。
4,把当前格子从开启列表中删除,然后添 加到关闭列表中。
5,检查所有相邻格子。跳过那些已经在关 闭列表中的或者不可通过的,把他们添加进开启列表,把选中的方格作为新的方格的父节点。
6,如果某个相邻格已经在开启列表里了,检查现在的这条路径G值是否会更低一些。如果新的G值更低,那就把相邻方格的父节点改为目前选中的方格,重新计算F和G的值。
起始格下方格子的父节点已经和前面不同的。之前它的G值是,并且指向右上方的格子。现在它的G值是,指向它上方的格子。这在寻路过程中的某处发生,当应用新路径时,G值经过检查变得低了-于是父节点被重新指定,G和F值被重新计算。
步骤总结:
1,把起始格添加到开启列表。
2,重复如下的工作:
a) 寻找开启列表中F值最低的格子。我们称它为当前格。
b) 把它切换到关闭列表。
c) 对相邻的格中的每一个
* 如果它不可通过或者已经在关闭列表中,略过它。反之如下。
* 如果它不在开启列表中,把它添加进去。把当前格作为这一格的父节点。记录
这一格的F,G,和H值。
* 如果它已经在开启列表中,用G值为参考检查新的路径是否更好。更低的G值
意味着更好的路径。如果是这样,就把这一格的父节点改成当前格,并且重新计算这一格的
G和F值。
d) 停止,当你
* 把目标格添加进了关闭列表,这时候路径被找到
* 没有找到目标格,开启列表已经空了。这时候,路径不存在。
3.保存路径。从目标格开始,沿着每一格的父节点移动直到回到起始格。
算法实现:
开启集合
关闭集合
添加起始点到开始集合中
循环如下步骤:
当前点=开启集合中最小F_Cost的点
将当前点移出开启集合中
将当前点添加到关闭集合中
如果当前点是目标点,结束查询
遍历当前点的每个相邻点
如果相邻点不能访问或者相邻点在关闭集合中,跳过此相邻点
如果新路径到相邻点的距离更短,或者相邻点不在开启集合中
重新设置F_Cost
重新设置其父节点为当前点
如果相邻点不在开启集合中
添加相邻点到开启集合中
具体代码:
Node 类:
using UnityEngine;
using System.Collections;
public class Node {
//能否行走
public bool _canWalk;
//世界坐标
public Vector3 _worldPos;
//位置
public int _gridX, _gridY;
//与目标节点和开始节点的距离
public int gCost;
public int hCost;
//总距离
public int fCost
{
get { return gCost + hCost; }
}
//父节点
public Node parent;
//构造函数
public Node(bool canwalk,Vector3 position,int x,int y)
{
_canWalk = canwalk;
_worldPos = position;
_gridX = x;
_gridY = y;
}
}
Grid类:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Grid : MonoBehaviour
{
//节点
private Node[,] grid;
public Vector2 gridSize;
public float nodeRadius;
private float nodeDiameter;
public LayerMask whatLayer;
//玩家
public Transform player;
public List<Node> path = new List<Node>();
public int gridCntX, gridCntY;
// Use this for initialization
void Start()
{
//初始化数据
nodeDiameter = nodeRadius * 2;
gridCntX = Mathf.RoundToInt(gridSize.x / nodeDiameter);
gridCntY = Mathf.RoundToInt(gridSize.y / nodeDiameter);
grid = new Node[gridCntX, gridCntY];
CreateGrid();
}
//创建grid
private void CreateGrid()
{
Vector3 startPoint = transform.position - gridSize.x / 2 * Vector3.right - Vector3
.forward * gridSize.y / 2;
for (int i = 0; i < gridCntX; i++)
for (int j = 0; j < gridCntY; j++)
{
Vector3 worldPoint = startPoint + Vector3.right * (i * nodeDiameter + nodeRadius) + Vector3.forward * (j * nodeDiameter + nodeRadius);
bool walkable = !Physics.CheckSphere(worldPoint, nodeRadius, whatLayer);
grid[i, j] = new Node(walkable, worldPoint, i, j);
}
Vector3 endPos = transform.position - gridSize.x / 2 * Vector3.right;
}
// Update is called once per frame
void Update()
{
}
/*
根据位置获取node节点
*/
public Node getFromPosition(Vector3 postition)
{
float percentX = (postition.x + gridSize.x / 2) / gridSize.x;
float percentY = (postition.z + gridSize.y / 2) / gridSize.y;
//确保在0和1之间
percentX = Mathf.Clamp01(percentX);
percentY = Mathf.Clamp01(percentY);
int x = Mathf.RoundToInt((gridCntX - 1) * percentX);
int y = Mathf.RoundToInt((gridCntY - 1) * percentY);
return grid[x, y];
}
//调用函数,进行初始化
void OnDrawGizmos()
{
Gizmos.DrawWireCube(transform.position, new Vector3(gridSize.x, 1, gridSize.y));
if (grid == null)
return;
Node playerNode = getFromPosition(player.position);
//遍历
foreach (var node in grid)
{
Gizmos.color = node._canWalk ? Color.white : Color.red;
Gizmos.DrawCube(node._worldPos, Vector3.one * (nodeDiameter - .1f));
}
//遍历路径
if (path != null)
{
foreach (var node in path)
{
Gizmos.color = Color.black;
Gizmos.DrawCube(node._worldPos, Vector3.one * (nodeDiameter - .1f));
}
}
//绘制玩家颜色
if (playerNode != null && playerNode._canWalk)
{
Gizmos.color = Color.cyan;
Gizmos.DrawCube(playerNode._worldPos, Vector3.one * (nodeDiameter - .1f));
}
}
/*
*
获取节点周围的八个节点
*/
public List<Node> getNeibourhood(Node node)
{
List<Node> neibourhood = new List<Node>();
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
if (i == 0 && j == 0)
continue;
int tempX = node._gridX + i;
int tempY = node._gridY + j;
if (tempX < gridCntX && tempX > 0 && tempY > 0 && tempY < gridCntY)
{
neibourhood.Add(grid[tempX, tempY]);
}
}
}
return neibourhood;
}
}
FindPath 类,也是核心类:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class FindPath : MonoBehaviour
{
//添加玩家和目标点
public Transform player, endPoint;
private Grid _grid;
// Use this for initialization
//初始化grid
void Start()
{
_grid = GetComponent<Grid>();
}
// Update is called once per frame
//调用获取路径的函数
void Update()
{
findingPath(player.position, endPoint.position);
}
//发现路径
void findingPath(Vector3 startPos, Vector3 endPos)
{
//开始节点和结束节点
Node startNode = _grid.getFromPosition(startPos);
Node endNode = _grid.getFromPosition(endPos);
//开启列表和关闭列表
List<Node> openSet = new List<Node>();
HashSet<Node> closeSet = new HashSet<Node>();
//添加到开启列表
openSet.Add(startNode);
while (openSet.Count > 0)
{
//获取第一个几点
Node currentNode = openSet[0];
//遍历所有在开启列表中的节点
for (int i = 0; i < openSet.Count; i++)
{
//判断是否满足条件
if (openSet[i].fCost < currentNode.fCost || openSet[i].fCost == currentNode.fCost
&& openSet[i].hCost < currentNode.hCost)
{
currentNode = openSet[i];
}
}
//从开启列表移除
openSet.Remove(currentNode);
//添加到关闭列表
closeSet.Add(currentNode);
//如果当前节点是目标节点,完成查找
if (currentNode == endNode)
{
//获取路径
generatePath(startNode,endNode);
return;
}
//根据当前节点判断周围的节点
foreach (var node in _grid.getNeibourhood(currentNode))
{
if (!node._canWalk || closeSet.Contains(node))
continue;
int newCost = currentNode.gCost + getDistanceNodes(currentNode, node);
//判断fCost,并更新fCost
if (newCost < node.gCost || !openSet.Contains(node))
{
node.gCost = newCost;
node.hCost = getDistanceNodes(node, endNode);
node.parent = currentNode;
if (!openSet.Contains(node))
{
openSet.Add(node);
}
}
}
}
}
//生成路径
private void generatePath(Node startNode, Node endNode)
{
List<Node> path = new List<Node>();
Node temp = endNode;
while (temp != startNode)
{
path.Add(temp);
temp = temp.parent;
}
//翻转
path.Reverse();
_grid.path=path;
}
//获取距离
int getDistanceNodes(Node a, Node b)
{
int cntX = Mathf.Abs(a._gridX - b._gridX);
int cntY = Mathf.Abs(a._gridY - b._gridY);
//判断横优先还是竖优先
if (cntX > cntY)
{
return 14 * cntY + 10 * (cntX - cntY);
}
else
{
return 14 * cntX + 10 * (cntY - cntX);
}
}
}
资源下载地址:http://download.csdn.net/detail/u012251421/8341159