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下实现简单的自动寻路的主要内容,如果未能解决你的问题,请参考以下文章

自动寻路之A星算法+Qt开发图形界面

(c++)迷宫自动寻路-队列-广度优先算法-附带寻路打印动画

关于自动寻路简单算法

寻路算法和逻辑算法之间异同点都有哪些

Unity A星(A Star/A*)寻路算法

C++寻路算法