A*算法之在U3d下实现简单的自动寻路
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了A*算法之在U3d下实现简单的自动寻路相关的知识,希望对你有一定的参考价值。
前言:
算法简介:
A*搜寻算法俗称A星算法。A*算法是比较流行的启发式搜索算法之一,被广泛应用于路径优化领域[。它的独特之处是检查最短路径中每个可能的节点时引入了全局信息,对当前节点距终点的距离做出估计,并作为评价该节点处于最短路线上的可能性的量度。[1] - 百度百科
通俗点说,就是在起点与目标点之中找出一条可通行的最短路线。常见于各类RPG游戏中的自动寻路功能:点击某个任务,人物会自动移动过去;点击地图上某个点,人物也会照着显示出来(或者隐藏了)的路线前进。玩过LoL,红色警戒等类似游戏的小伙伴都知道,右击小地图的某一处,小地图会出现一条从当前位置到所点击位置的红色路线,然后英雄就会随着这条路线一直走到目标点。这种功能,就是A*算法的在游戏中的常见应用之处。
算法原理:
详情见 :http://blog.csdn.net/hitwhylz/article/details/23089415
本篇目的:
运用A*算法在U3D中实现简单的自动寻路功能:鼠标点击地图上某处,人物根据A*算法计算出来的路线移动到点击处。
效果展示:
PS:
第一次弄gif图,视频也没有录好:文件太大,镜头远近切换太快;导进ps时帧数就很长,然后帧数选少点后,因为整个视频抖动太大,所以看起来就会非常突兀。然后在我的“努力”加工下,就成上面这样了,嘿嘿嘿,是不是丑的不要不要的????。
估计大家也看不懂,我解释一遍:场景中白色方块就是障碍物,不能穿过的。播放中出现的一条红色的线就是在鼠标点击某处后,A*算法计算出来的从玩家当前点到鼠标点击的点的移动路径。红线上有一个蓝色物体(反正就是一个在移动的物体),那个就是玩家在沿着移动路径往目标点(鼠标点击处)移动。
过程:
场景布置:
1 布置地面:场景中新建一个Plane(地面)重置一下Transform,然后将Scale拉伸至20倍(此时地面的大小是200x200),地面是不带y坐标的,即只有xz平面,此时平面上左下角坐标点是(-100,-100),右上角是(100,100)这个很重要,后面的判断坐标点是否越界(超出地面范围)就是依据这个地面的大小和坐标
2 布置障碍物:新建一个空物体Bars,在Bars下创建一个cube,将cube随便拉伸做成一堵墙,然后复制,摆放到场景上各个位置。
3 创建玩家:场景中新建一个Capsule(为了好控制,就选用胶囊体了),取名为Player,为它挂上Character Controller角色控制器。
4 路线:新建一个Sphere球,涂成红色,改名叫Way,卸载掉Collider(一定要卸载),做成预制体,用来表示计算出来的路线上的每一点。新建一个空物体Ways,用来存储路线
5 层级设置:为了代码中好检测,为地面Plane设立一个单独的层Plane,层级号位9.所有障碍物层级为Bars,层级号为8
布置好后就跟下面差不多了:
脚本编辑:
脚本没写的太过复杂,就编写了两个脚本:
PlayerCtrl.cs 用来控制玩家移动
AStarRun.cs 用A*算法来计算起点到目标点的最佳路径并返回
将这两个脚本都挂载在玩家上(Player)
PlayerCtrl.cs 用来控制玩家移动
using UnityEngine;
using System.Collections;
//玩家控制器
public class PlayerCtrl : MonoBehaviour {
public GameObject wayLook;//寻路线的红点
public float moveSpeed = 10f;//角色前进速度
private CharacterController cc;//角色控制器
private Transform waysParent;//寻路线的放置位置
private Ray ray;//射线检测鼠标点击点
private RaycastHit hit;
private bool IsMove = false;//是否正在寻路过程
void Start ()
{
cc = GetComponent<CharacterController>();
waysParent = GameObject.Find("Ways").transform;
}
void Update ()
{
//鼠标单击移动
if(Input.GetMouseButtonDown(0))
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);//获取主相机到鼠标点击点的射线
//检测射线是否碰撞到地面:地面的层级是9
if(Physics.Raycast(ray,out hit,1 << 9))
{
//往目标点移动过去
//return;
Vector3 starPoint = new Vector3(transform.position.x,0,transform.position.z);//寻路的起点
Vector3 targetPoint = new Vector3(hit.point.x,0,hit.point.z);//寻路的终点
if(!IsMove)
StartCoroutine(AutoMove(starPoint,targetPoint));//开启自动寻路
}
}
}
/// <summary>
/// 自动寻路协程
/// </summary>
/// <returns>The move.</returns>
/// <param name="starPoint">起点.</param>
/// <param name="targetPoint">目标点.</param>
IEnumerator AutoMove(Vector3 starPoint,Vector3 targetPoint)
{
IsMove = true;
yield return new WaitForFixedUpdate();
//运用A星算法计算出到起点到目标点的最佳路径
Vector3[] ways = GetComponent<AStarRun>().AStarFindWay(starPoint,targetPoint);
if(ways.Length == 0)
{
IsMove = false;
yield break;
}
//打印显示出寻路线
foreach(var v in ways)
{
GameObject way = Instantiate<GameObject>(wayLook);
way.transform.parent = waysParent;
way.transform.localPosition = v;
way.transform.rotation = Quaternion.identity;
way.transform.localScale = Vector3.one;
}
//让玩家开始沿着寻路线移动
int i = 0;
Vector3 target = new Vector3(ways[i].x,transform.position.y,ways[i].z);
transform.LookAt(target);
while(true)
{
yield return new WaitForFixedUpdate();
Debug.Log("run run run !!!");
cc.SimpleMove(transform.forward * moveSpeed * Time.deltaTime);
if(Vector3.Distance(transform.position,target) < 1f)
{
Debug.Log("run is ok !!!");
++i;
if(i >= ways.Length)
break;
target = new Vector3(ways[i].x,transform.position.y,ways[i].z);
transform.LookAt(target);
}
}
//移动完毕,删除移动路径
for(int child = waysParent.childCount - 1;child >= 0;--child)
Destroy(waysParent.GetChild(child).gameObject);
//等待执行下一次自动寻路
IsMove = false;
}
}
AStarRun.cs 用A*算法来计算起点到目标点的最佳路径并返回
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
//A*寻路
public class AStarRun : MonoBehaviour {
private Map map = new Map();//格子地图
////开启列表
private List<MapPoint> open_List = new List<MapPoint>();
//关闭列表
private List<MapPoint> close_List = new List<MapPoint>();
//定义一个路径数组
private ArrayList way = new ArrayList();
//判断某点是否在开启列表中
private bool IsInOpenList(int x,int z)
{
foreach(var v in open_List)
{
if(v.x == x && v.z == z)
return true;
}
return false;
}
//判断某点是否在关闭列表中
private bool IsInCloseList(int x,int z)
{
foreach(var v in close_List)
{
if(v.x == x && v.z == z)
return true;
}
return false;
}
//从开启列表中找到那个F值最小的格子
private MapPoint FindMinFInOpenList()
{
MapPoint minPoint = null;
foreach(var v in open_List)
{
if(minPoint == null || minPoint.GetF > v.GetF)
minPoint = v;
}
return minPoint;
}
//从开启列表中找到格子
private MapPoint FindInOpenList(int x,int z)
{
foreach(var v in open_List)
{
if(v.x == x && v.z == z)
return v;
}
return null;
}
/// <summary>
/// a星算法寻路
/// </summary>
/// <returns>寻到的路结果.</returns>
/// <param name="starPoint">起点 </param>
/// <param name="targetPoint">终点</param>
///
public Vector3[] AStarFindWay(Vector3 starPoint,Vector3 targetPoint)
{
//清空容器
way.Clear();
open_List.Clear();
close_List.Clear();
//初始化起点格子
MapPoint starMapPoint = new MapPoint();
starMapPoint.x = (int)starPoint.x;
starMapPoint.z = (int)starPoint.z;
//初始化终点格子
MapPoint targetMapPoint = new MapPoint();
targetMapPoint.x = (int)targetPoint.x;
targetMapPoint.z = (int)targetPoint.z;
//将起点格子添加到开启列表中
open_List.Add(starMapPoint);
//寻找最佳路径
//当目标点不在打开路径中时或者打开列表为空时循环执行
while(!IsInOpenList(targetMapPoint.x,targetMapPoint.z) || open_List.Count == 0)
{
//从开启列表中找到那个F值最小的格子
MapPoint minPoint = FindMinFInOpenList();
if(minPoint == null)
return null;
//将该点从开启列表中删除,同时添加到关闭列表中
open_List.Remove(minPoint);
close_List.Add(minPoint);
//检查改点周边的格子
CheckPerPointWithMap(minPoint,targetMapPoint);
}
//在开启列表中找到终点
MapPoint endPoint = FindInOpenList(targetMapPoint.x,targetMapPoint.z);
Vector3 everyWay = new Vector3(endPoint.x,0,endPoint.z);//保存单个路径点
way.Add(everyWay);//添加到路径数组中
//遍历终点,找到每一个父节点:即寻到的路
while(endPoint.fatherPoint != null)
{
everyWay.x = endPoint.fatherPoint.x;
everyWay.z = endPoint.fatherPoint.z;
everyWay.y = 0;
way.Add(everyWay);
endPoint = endPoint.fatherPoint;
}
//将路径数组从倒序变成正序并返回
Vector3[] ways = new Vector3[way.Count];
for(int i = way.Count - 1;i >= 0;--i)
{
ways[way.Count - i - 1] = (Vector3)way[i];
}
//清空容器
way.Clear();
open_List.Clear();
close_List.Clear();
//返回正序的路径数组
return ways;
}
//判断地图上某个坐标点是不是障碍点
private bool IsBar(int x,int z)
{
//判断地图上某个坐标点是不是障碍点
Vector3 p = new Vector3(x,0,z);
//检测该点周边是否有障碍物
//障碍物层级为8
Collider[] colliders = Physics.OverlapSphere(p,1,1 << 8);
if(colliders.Length > 0)
return true;//有障碍物,说明该点不可通过,是障碍物点
return false;
}
//计算某方块的G值
public int GetG(MapPoint p)
{
if(p.fatherPoint == null)
return 0;
if(p.x == p.fatherPoint.x || p.z == p.fatherPoint.z)
return p.fatherPoint.G + 10;
else
return p.fatherPoint.G + 14;
}
//计算某方块的H值
public int GetH(MapPoint p,MapPoint targetPoint)
{
return (Mathf.Abs(targetPoint.x - p.x) + Mathf.Abs(targetPoint.z - p.z)) * 10;
}
//检查某点周边的格子
private void CheckPerPointWithMap(MapPoint _point,MapPoint targetPoint)
{
for(int i = _point.x-1; i <= _point.x + 1;++i)
{
for(int j = _point.z - 1; j <= _point.z + 1; ++j)
{
//剔除超过地图的点
if(i < map.star_X || i > map.end_X || j < map.star_Z || j > map.end_Z)
continue;
//剔除该点是障碍点:即周围有墙的点
if(IsBar(i,j))
continue;
//剔除已经存在关闭列表或者本身点
if(IsInCloseList(i,j) || (i == _point.x && j == _point.z))
continue;
//剩下的就是没有判断过的点了
if(IsInOpenList(i,j))
{
//如果该点在开启列表中
//找到该点
MapPoint point = FindInOpenList(i,j);
int G = 0;
//计算出该点新的移动代价
if(point.x == _point.x || point.z == _point.z)
G = point.G + 10;
else
G = point.G + 14;
//如果该点的新G值比前一次小
if(G < point.G)
{
//更新新的G点
point.G = G;
point.fatherPoint = _point;
}
}
else
{
//如果该点不在开启列表内
//初始化该点,并将该点添加到开启列表中
MapPoint newPoint = new MapPoint();
newPoint.x = i;
newPoint.z = j;
newPoint.fatherPoint = _point;
//计算该点的G值和H值并赋值
newPoint.G = GetG(newPoint);
newPoint.H = GetH(newPoint,targetPoint);
//将初始化完毕的格子添加到开启列表中
open_List.Add(newPoint);
}
}
}
}
}
//地图类
public class Map
{
public int star_X;// 横坐标起点
public int star_Z;// 纵坐标起点
public int end_X;// 横坐标终点
public int end_Z;//纵坐标终点
public Map()
{
star_X = - 100;
star_Z = - 100;
end_X = 100;
end_Z = 100;
}
}
//每一个格子的信息
public class MapPoint
{
//F = G + H
//G 从起点A移动到指定方格的移动代价,父格子到本格子代价:直线为10,斜线为14
//H 使用 Manhattan 计算方法, 计算(当前方格到目标方格的横线上+竖线上所经过的方格数)* 10
public int x;//格子的x坐标
public int z;//格子的z坐标
public int G;
public int H;
public int GetF{
get
{
return G + H;
}
}
public MapPoint fatherPoint;//父格子
public MapPoint(){}
public MapPoint(int _x,int _z,int _G,int _H,MapPoint _fatherPoint)
{
this.x = _x;
this.z = _z;
this.G = _G;
this.H = _H;
this.fatherPoint = _fatherPoint;
}
}
结语:
至此,一个简单的自动寻路就完成了。代码逻辑感谢 园友 lipan 分享的 http://www.cnblogs.com/lipan/archive/2010/07/01/1769420.html 此篇正是在学习他的逻辑基础上完成的,特别感谢。
以上是关于A*算法之在U3d下实现简单的自动寻路的主要内容,如果未能解决你的问题,请参考以下文章