您的位置:首页 > 其它

A*算法

2015-10-21 17:10 204 查看
麻麻说 好女孩都开始写博客了~

作为初级程序员<鼓励师>~

努力每周都写一篇吧~<说好的每天都写呢~ 羞羞~>

那么,第一篇文章,简单却常被问到的A*算法~

What is A*~

在游戏中,A*算法常被用做自动寻路。去寻找到一条最短路径,或者说找到一条耗资最小的路径,而这个地图一般是储存成一个二维的数组。

常用的状态空间搜索有深度优先和广度优先,广度优先是从初始状态一层一层的向下找,知道找到结果目标为止,深度优先是按照一定的顺序先查找完一个分支再查找另一个分支,直到找到目标结果为止。这两种搜索方法有的很大缺陷是它们都是在一个给定的状态空间中穷举。如若空间比较大,这些方法就不科学了。

A* 算法则属于启发式搜索方式,所谓启发式搜索就是在状态空间中对每一个搜索为止进行评估,直到找到最好的,再从这个位置进行搜索直到目标位置为止。

A*的原理

那么游戏物体如何从开始点移动到结束点呢。

公式: F = G+H (如图,左上角的数代表F,右下角代表G,左下角代表H)

F —— 作为移动的总估值,估值越小,耗资越少。

G —— 从起始点移动到结束点的耗资(水平和竖直的移动,也不去考虑障碍物)。

H —— 从上一步走到下一步所消耗的资源。H是启发式搜索的体现,若不是知道上一步的位置,H的值无法确定。地图中若是有墙(无法通过),或者水(耗资较大),它们H值是不同的。(图中水平和竖直耗资10,而斜着是14,是根据勾股定理,14 ≈ √2 * 10)。



具体过程:

1、建立一个开启列表,和一个关闭列表。
开启列表就像一个购物清单,包含了你可能会用到的方格。
最开始这个列表中只有起始点,可是它能不断加入已知点周围的点(蓝色边框方块),不断扩大。
关闭列表就是存放你已经检测过的方格。(中间的绿色方块)
2、把A加入开启列表,在起始点A周围寻找方格。
将这些方格的“父方格”设为A,把A从开启列表中移除,放入关闭列表中。
3、将周围要检测的格子放入开启列表中,在开启列表中选择F值最小的方格B。
把它从开启列表中移除,放入关闭列表,检查其周围的方块。
若其不在开启列表中,就将他们加入开启列表,同时将其“父方格”设为B。
若其在开启列表中,则要考虑现在的路径是否更好。
即更新的G值会比“父方格”加上从父方格到现在位置的G值更低。
是则重新设父物体,重新计算F(H不会变),不是就算了。
4、重复3直到找到结束点,或者开启列表已经空了还没找到结束点,则说明没有路径到达结束点。


找回路径:

从结束点依次寻找其“父方块”,最终找到开始点。

这样一条路径,就是最短路径。此时,我们应该会想到使用栈结构。

代码解释

节点的脚本属性和方法:

using UnityEngine;
using System.Collections;

public class Global{
public static int mapHeight = 5;        //      地图的高
public static int mapWidth = 5;     //      地图的宽
}


生成地图:

using UnityEngine;
using System.Collections;

public class Map {
public NodeModel[,] models;     //      存储方块模型

#region 单例
private static Map instance;
private Map(){
InitMap();
}
public static Map GetInstance(){
if (instance == null) {
instance = new Map();
}
return instance;
}

#endregion 单例结束

//      初始化地图
void InitMap(){
models = new NodeModel[Global.mapWidth,Global.mapHeight];
for (int i = 0; i < Global.mapWidth; i++) {
for (int j = 0; j < Global.mapHeight; j++) {
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
//      位置调整
obj.transform.position = new Vector3(j,0,-i);
obj.transform.localScale = Vector3.one * 0.9f;
NodeModel model = new NodeModel(i,j);
if (i == 0 && j ==0) {
model.type = NodeTypes.Start;
model.G = 0;
obj.GetComponent<Renderer>().material.color = Color.yellow;
}
else if (i == Global.mapWidth - 1 && j == Global.mapHeight - 1) {
model.type = NodeTypes.End;
model.G = 0;
obj.GetComponent<Renderer>().material.color = Color.yellow;
}
else {
int num =  Random.Range(0,4);
if (num < 2) {
model.type = NodeTypes.Normal;
obj.GetComponent<Renderer>().material.color = Color.green;
}
else if (num == 2) {
model.type = NodeTypes.Water;
obj.GetComponent<Renderer>().material.color = Color.blue;
}
else {
model.type = NodeTypes.Obstacle;
obj.GetComponent<Renderer>().material.color = Color.red;
}
}
model.pos = obj.transform.position;
models[i, j] = model;
}
}
}
}


A星的具体代码:

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

public class AStar : MonoBehaviour {
private NodeModel[,] models;        //      方块模型
private GameObject player;      //      玩家
private List<NodeModel> openList;       //      开启列表
private List<NodeModel> closeList;      //      关闭列表
private int[,] dir;     //      上下左右,左上,左下,右上,右下   八个方向
private NodeModel start;        //      开始节点
private NodeModel end;      //      结束节点
private Stack<Vector3> road;            //      存储路径的栈结构
private bool canMove = false;       //      是否可以移动
void Start () {
//      初始化
openList = new List<NodeModel>();
closeList = new List<NodeModel>();
road = new Stack<Vector3>();
dir = new int[,]{{0,1},{0,-1},{1,0},{-1,0},{1,1},{1,-1},{-1,1},{-1,-1}};
//      在单列的构造函数中生成地图,并把地图中的模型拿过来
models = Map.GetInstance().models;
start = models[0,0];
end = models[Global.mapWidth - 1,Global.mapHeight - 1];
Begin();
//      找到路, 填充栈,然后移动
if (FindEnd()) {
print("FindPath");
NodeModel line = end;
while (line.type != NodeTypes.Start) {
road.Push(line.pos);
line = line.parent;
}
canMove = true;
}
}

//      用Capsule模拟一个玩家
void Begin(){
player = GameObject.CreatePrimitive(PrimitiveType.Capsule);
player.transform.position = start.pos + Vector3.up * 1.5f;
}

//      寻路,返回寻路结果
bool FindEnd(){
openList.Add(start);
closeList.Add(start);
//      开启列表中 还有值 就继续寻找其他节点
while (openList.Count > 0) {
//      寻找到结束节点
if (openList[0].type == NodeTypes.End) {
return true;
}
else {
for (int i = 0; i < dir.GetLength(0); i++) {
//      去到每个方向的x和y的偏差
int x = openList[0].x + dir[i,0];
int y = openList[0].y + dir[i,1];
int value = 0;
//      正方向value是10,斜着是14
value = i < 4 ? 10 : 14;
//      对即将操作的节点进行判断
if (IsOK(x,y,openList[0],value)) {
//      设置父节点,重置G,H,F值,加入开启列表中
models[x,y].parent = openList[0];
models[x,y].SetG(openList[0],value);
models[x,y].SetH(end);
models[x,y].SetF();
openList.Add(models[x,y]);
}
}
//      循环完毕,将该节点加入关闭列表,并从开启列表中移除
closeList.Add(openList[0]);
openList.Remove(openList[0]);
//      重新排序(Comperasion中按F值给开启列表重新排序)
openList.Sort(Comperasion);
}
}
//      开启列表中没值了,也没找到结束点,说明没有路径到达
return false;
}

//      排序函数
int Comperasion(NodeModel a, NodeModel b){
if (a.F > b.F) {
return 1;
}
else if (a.F < b.F) {
return -1;
}
else {
return 0;
}
}

//      判断节点是否检测过了
bool IsOK(int x,int y,NodeModel parent,int value){
//      判断节点是否有效
if (x < 0 || x >= models.GetLength(0) || y < 0 || y>= models.GetLength(1) || models[x,y].type == NodeTypes.Obstacle) {
return false;
}
//      是否已在开启列表中,重新判断G值
if (openList.Contains(models[x,y])) {
switch (models[x,y].type) {
case NodeTypes.Water:
value += 5;
break;
}
//      如果G值小,更新父物体
if(models[x,y].G > parent.G + value){
models[x,y].G = parent.G + value;
models[x,y].parent = parent;
}
return false;
}
else {
return true;
}
}

void Update(){
if (canMove) {
Move();
}
}

//      玩家移动
void Move(){
if (road.Count > 0) {
Vector3 v1 = road.Peek();
Vector3 v2 = player.transform.position;
//      竖直方向没有位移
v2.y = v1.y;
//      单位化向量
Vector3 v = (v1 - v2).normalized;
//      移动
player.transform.Translate(v * Time.deltaTime * 2);
//      接近该方块,调整位置,移除栈中顶部元素
if (Vector3.Distance(v1,v2) < 0.1f) {
player.transform.position =v1 + new Vector3(0f,1.5f,0f);
if (road.Count > 0) {
road.Pop();
}
}
}
//      栈已空,取消移动
else {
canMove = false;
}

}
}


注 : 把AStar脚本挂载在场景任何位置就可以运行了。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: