A*算法理解(unity C#)

最近发现A*算法忘的一干二净啊,记忆是个好东西,可惜吾没有啊,只能整理一篇文章以备日后翻看
这里只谈A*算法的实现,不谈A算法的优化*
这里的工程是unity版本的,当然理解A*算法是通用的

这里先放上A*算法的unity工程(unity2017.3.1) unity工程(github)

0X01 A*算法基本概念

启发式搜索: 启发式搜索就是在状态空间中的搜索对每一个搜索的位置进行评估,得到最好的位置,再从这个位置进行搜索直到目标。这样可以省略大量无畏的搜索路径,提到了效率。在启发式搜索中,对位置的估价是十分重要的。采用了不同的估价可以有不同的效果。
IDA*算法: 这种算法被称为迭代加深A算法,可以有效的解决A空间增长带来的问题,甚至可以不用到优先级队列。如果要知道详细:google一下。
搜索区域(The Search Area): 图中的搜索区域被划分为了简单的二维数组,数组每个元素对应一个小方格,当然我们也可以将区域等分成是五角星,矩形等,通常将一个单位的中心点称之为搜索区域节点(Node)。  
开放列表(Open List): 我们将路径规划过程中待检测的节点存放于Open List中,而已检测过的格子则存放于Close List中。
关闭列表(Close List): 我们将路径规划过程中已经检查过的节点放在Close List。
启发函数(Heuristics Function)(估价函数): H为启发函数,也被认为是一种试探,由于在找到唯一路径前,我们不确定在前面会出现什么障碍物,因此用了一种计算H的算法,具体根据实际场景决定。在我们简化的模型中,H采用的是传统的曼哈顿距离(Manhattan Distance)估价函数,也就是横纵向走的距离之和。
F(n) = G + H。F代表当前检查点的总花费,G代表起点到当前检查点的花费,H代表当前检查点到终点的预估花费。
父节点(parent): 在路径规划中用于回溯的节点。
A*算法的特点: A*算法在理论上是时间最优的,但是也有缺点:它的空间增长是指数级别的。

0X02 A*算法寻路过程

  1. 将起点A添加到open列表中(A没有计算花费F是因为当前open列表只有这一个节点)。
  2. 检查open列表,选取花费F最小的节点M(检查M如果为终点是则结束寻路,如果open列表没有则寻路失败,直接结束)。
  3. 对于与M相邻的每一节点N:(下面本来没有序号的,csdn markdown的bug)
    • 如果N是阻挡障碍,那么不管它。
    • 如果N在closed列表中,那么不管它。
    • 如果N不在open列表中:添加它然后计算出它的花费F(n)=G+H。
    • 如果N已在open列表中:当我们使用当前生成的路径时,检查F花费是否更小。如果是,更新它的花费F和它的父节点。
  4. 重复2,3步。

0X03 A*算法寻路详细描述

寻路关键代码:

    /// 
    /// 使用A*算法寻路
    /// 
    /// 
    /// 
    void FindPath(Vector2 start,Vector2 end)
    {
        //和A*算法无关,只是为了显示使用
        int showFindNum=1;

        //开启列表
        List<Cell> openLs = new List<Cell>();
        //关闭列表
        List<Cell> closeLs = new List<Cell>();

        //起点
        Cell startCell = grid.GetCell(start);
        //终点
        Cell endCell = grid.GetCell(end);
        Debug.LogFormat("寻路开始,start({0}),end({1})!",start,end);

        //将起点作为待处理的点放入开启列表,
        openLs.Add(startCell);

        //如果开启列表没有待处理点表示寻路失败,此路不通
        while(openLs.Count>0)
        {
            //遍历开启列表,找到消费最小的点作为检查点
            Cell cur = openLs[0];
            for (int i = 0; i < openLs.Count; i++)
            {
                if(openLs[i].fCost<cur.fCost&&openLs[i].hCost<cur.hCost)
                {
                    cur = openLs[i];
                }
            }
            Debug.Log("当前检查点:" + cur.ToString()+" 编号:"+showFindNum+"  open列表节点数量:"+openLs.Count);
            //显示在界面,和A*算法无关
            cur.obj.transform.Find("Num").GetComponent<Text>().text=showFindNum.ToString();
            showFindNum++;

            //从开启列表中删除检查点,把它加入到一个“关闭列表”,列表中保存所有不需要再次检查的方格。
            openLs.Remove(cur);
            closeLs.Add(cur);

            //检查是否找到终点
            if(cur==endCell)
            {
                Debug.Log("寻路结束!");
                grid.CreatePath(cur);
                return;
            }

            //根据检查点来找到周围可行走的点
            //1.如果是墙或者在关闭列表中则跳过
            //2.如果点不在开启列表中则添加
            //3.如果点在开启列表中且当前的总花费比之前的总花费小,则更新该点信息
            List<Cell> aroundCells = GetAllAroundCells(cur);
            foreach (var cell in aroundCells)
            {
                
                if (cell.isWall || closeLs.Contains(cell))
                    continue;

                int cost= cur.gCost+ GetDistanceCost(cell, cur);

                if(cost<cell.gCost||!openLs.Contains(cell))
                {
                    cell.gCost = cost;
                    cell.hCost = GetDistanceCost(cell,endCell);
                    cell.parent = cur;
                    Debug.Log("cell:" + cell.ToString() + "  parent:" + cur.ToString() + "  " + cell.PrintCost());
                    if(!openLs.Contains(cell))
                    {
                        openLs.Add(cell);
                    }

                    //显示在界面,和A*算法无关
                    cell.obj.transform.Find("Cost").GetComponent<Text>().text = cell.fCost.ToString();
                }


            }

        }

        Debug.Log("寻路失败!");
    }

根据上面的工程得到这个图:
A*算法理解(unity C#)_第1张图片
圆点说明:黑色是障碍物体,红色是起点,绿色是终点,浅紫色是寻路路径
红色数字:总花费F
蓝色数字:寻路的过程描述

过程描述:

  • 将起点(3,6)放到open列表中。

  • 选择open列表中花费最小的点M(3,6),将M从open中移除,添加到closed列表中,后面检查时不再检查该点。

  • 计算(3,6)相邻的点,一共8个点,并且分别计算花费F(红色数字),都添加到open中
    +A*算法理解(unity C#)_第2张图片

  • 选择open列表中花费最小的点M(4,6),将M从open中移除,添加到closed列表中,后面检查时不再检查该点。

  • 计算(4,6)相邻的点,一共8个点,右侧是障碍物,其它5个点都在open中,分别计算当前路径花费和原来对比,都大,所以没有更新花费和父节点

  • 选择open列表中花费最小的点M(4,5),将M从open中移除,添加到closed列表中,后面检查时不再检查该点。

  • 计算M相邻的点,在下面有三个新节点添加open中,其它五个点要么是障碍要么是已经在open中且花费比原来大,
    A*算法理解(unity C#)_第3张图片

  • 选择open列表中花费最小的点M(4,7),将M从open中移除,添加到closed列表中,后面检查时不再检查该点。

  • 计算M相邻的点,在上面有三个新节点添加open中,其它五个点要么是障碍要么是已经在open中且花费比原来大,
    A*算法理解(unity C#)_第4张图片

  • 选择open列表中花费最小的点M(3,5),将M从open中移除,添加到closed列表中,后面检查时不再检查该点。

  • 计算M相邻的点,在左下有一个新节点添加open中,在正下面有一个点(3,4)的花费比原来小,更新该节点信息,其它的点已经在open中且花费比原来大,
    在这里插入图片描述

  • 选择open列表中花费最小的点M(3,7),将M从open中移除,添加到closed列表中,后面检查时不再检查该点。

  • 计算M相邻的点,在左下有一个新节点添加open中,在正下面有一个点(3,8)的花费比原来小,更新该节点信息,其它的点已经在open中且花费比原来大,
    A*算法理解(unity C#)_第5张图片

  • 选择open列表中花费最小的点M(5,4),将M从open中移除,添加到closed列表中,后面检查时不再检查该点。

  • 计算M相邻的点,有五个新节点添加open中,其它三个点要么是障碍要么是已经在open中且花费比原来大,
    A*算法理解(unity C#)_第6张图片

  • 选择open列表中花费最小的点M(5,8),将M从open中移除,添加到closed列表中,后面检查时不再检查该点。

  • 计算M相邻的点,有五个新节点添加open中,其它三个点要么是障碍要么是已经在open中且花费比原来大,
    A*算法理解(unity C#)_第7张图片

  • 选择open列表中花费最小的点M(6,5),将M从open中移除,添加到closed列表中,后面检查时不再检查该点。

  • 计算M相邻的点,有四个新节点添加open中,其它四个点要么是障碍要么是已经在open中且花费比原来大或者相等,
    A*算法理解(unity C#)_第8张图片

  • 选择open列表中花费最小的点M(6,7),将M从open中移除,添加到closed列表中,后面检查时不再检查该点。

  • 计算M相邻的点,有两个新节点添加open中,其它六个点要么是障碍要么是已经在open中且花费比原来大或者相等,
    A*算法理解(unity C#)_第9张图片

  • 选择open列表中花费最小的点M(7,6),将M从open中移除,添加到closed列表中,和终点对比相等,寻路结束。
    在这里插入图片描述

根据当前代码寻路有个情况就是寻路到终点前临近终点时,可以提前一步结束,这里没有结束,是为了方便演示

参考文章:
A算法完全理解
堪称最好最全的A算法详解(译文)
A星寻路算法介绍
这里的unity工程是参考一篇文章的,是两年前的文章,没有找到,就不放参考链接了

你可能感兴趣的:(unity,数据结构)