【算法运用】---A*算法由浅入深理解

欢迎加入Unity业内qq交流群:956187480

qq扫描二维码加群


A*算法是众多算法里面的明星算法,网络上很多流传的版本要么是实战改版出来的要么就是纯理论的,让很多初学者难以下手,我也很久没在看过这个算法,只记得零星片段,今天就重新捡起来重新过一遍。
可以做一个展想:平面的A*和3D的A*区别在哪?另外如果地图足够大,我们是否需要做A*分层处理等等,A*今天我们只是了解了入门基础,在此之上的变形扩展有很多

源工程:https://blog.csdn.net/qq_37310110/article/details/113336900

一:构建网格地形,建立起止点

   //创建格子
        for (int i = 0; i < gridSize; i++)
        {
            for (int j = 0; j < gridSize; j++)
            {
                GameObject obj= GameObject.Instantiate<GameObject>(CellAsset, transform);
                RectTransform trans = obj.transform as RectTransform;
                trans.anchoredPosition = new Vector2(i,j) * cellSize;
                bool isWall=false;
                trans.Find("Pos").GetComponent<Text>().text = i + "," + j;
                if(wallList.Contains( i+"_"+j))
                {
                    isWall = true;
                    obj.GetComponent<Image>().color=Color.black;
                }
                Cell cell = new Cell(i, j, isWall, obj);
                cells.Add(cell);
            }
        }
 //创建起点
        GameObject obj1 = GameObject.Instantiate<GameObject>(PointAsset, transform);
        RectTransform trans1 = obj1.transform as RectTransform;
        trans1.anchoredPosition = startPos * cellSize;
        obj1.GetComponent<Image>().color = Color.red;
        trans1.localScale = Vector3.one * 1.2f;
        obj1.name = "start";

        //创建终点
        GameObject obj2 = Instantiate<GameObject>(PointAsset, transform);
        RectTransform trans2 = obj2.transform as RectTransform;
        trans2.anchoredPosition = endPos * cellSize;
        obj2.GetComponent<Image>().color = Color.green;
        trans2.localScale = Vector3.one * 1.2f;
        obj2.name = "end";

1.如下图红色点为起点,绿色点为终点,我们的目的就是找到最优路径绘制出来。我们把搜索区域分割成方块格子,每个格子都有自己的二维位置数组,通过计算红点到绿点要走哪些路径,在A*寻路的做法,我们从开始点A做起,检查它周围的方块,并且向外普通的搜索,直到找到目标。
2.从开始点A起,添加它到待考虑的方块的“开放列表”。开放列表有点象购物列表。此时只有一个项目在里面,但很快我们会得到更多。它包含了你可能取用的沿途的方块,也可能不用它。基本上,这是需要检查的方块的列表。

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

3.观察开始点邻近的所有可到达或可行走的方块,忽略障碍物。也把它们添加到开放列表。对每一个方块,保存A 点作为它们的关联节点。这个关联节点方块在跟踪路径时非常重要。
4.把开始方块A从开放列表中取出,并放到“封闭列表”内,它是所有现在不需要再关注的方块的列表。

二:路径排序
 

  // 与起点的移动代价
    public int gCost;
    // 与目标点的评估移动代价
    //这种方式通常称作试探法,有点让人混乱。因为这是一个猜测,所以得到这个称谓。在找到路径之前,我们真的不知道实际的距离,因为途中有各种东西(墙,水,等等)
    public int hCost;
    // 总的移动代价
    //我们需要的路径是这样生成的:反复的遍历开放列表,选择具有最小F值的方块。
    public int fCost
    {
        get { return gCost + hCost; }
    }

找到形成路径的方块的关键是下面的等式:F = G + H
G = 从开始 起点到格子中当前计算点的移动代价,沿着到达该方块而生成的那个路径
H = 从当前计算点到最终目标 绿点的评估移动代价。这种方式通常称作试探法,有点让人混乱。因为这是一个猜测,所以得到这个称谓。在找到路径之前,我们真的不知道实际的距离,因为途中有各种东西(墙,水,等等)。在本教程里给出了一种计算H的方法,但在网上你能找到很多其他的文章。
F=G+H 就是表示从起点到终点的总位移代价

1.在本例中,我们为每个水平/垂直的移动指定代价为10,而斜角的移动代价为14。我们使用这些值,因为斜角移动的实际距离是2的平方根(别害怕),或者大概1.414倍的水平/垂直的移动代价。出于简化的目的使用了10和14。比例大致是正确的,而我们却避免了方根和小数的计算。倒不是我们没有能力做或者不喜欢数学。使用这些数字也能让计算更快一些。以后你就会发现,如果不使用这些技巧,寻路的计算非常慢。
2.既然我们沿着到达给定方块的路径来计算G的值,找出那个方块的G值的方法就是找到其关联节点的G值,再加上10或者14而得,这依赖于他处于其关联节点的斜角或者直角(非斜角)而定。这在本例后面会更加清晰,随着我们从开始点离开而得到更多的方块。
3.H能通过多种方法估算。我们这里用到的方法叫做Manhattan方法,计算从当前方块经过水平/垂直移动而到达目标方块的方块总数。然后将总数乘以 10。这种方法之所以叫做Manhattan方法,因为他很象计算从一个地点到达另一个地点的城市街区数量计算,此时你不能斜向的穿越街区。重要的是,当计算H的时候,要忽略任何路径中的障碍。这是一个对剩余距离的 估算值,而不是实际值,这就是试探法的称谓由来。
4.G和H相加就算出了F。F,G,和H值都写入了每个方块。如开始方块相邻右边的方块,F显示在左上方,G显示在右上方,而 H显示在右下方。
5.持续搜索,把当前目标点从开放列表取出,并加入封闭列表。然后把所有的相邻点添加进开放表里面(如果不在开放表里面就加进去,如果这些点有障碍物就忽略掉),将选定方块作为这些新加入方块的关联节点。
6.如果一个相邻方块已经存在于开放列表,检查到达那个方块的路径是否更优。换句话说,检查经由当前方块到达那里是否具有更小的G 值。如果没有,不做任何事。
相反,如果新路径的G值更小,把这个相邻方块的关联节点改为当前选定的方块。最后,重新计算那个方块的F和G值。

 //如果是墙或者在关闭列表中则跳过
                if (cell.isWall || closeLs.Contains(cell))
                    continue;

                int cost = cur.gCost + GetDistanceCost(cell, cur);
                //检查经由当前方块到达那里是否具有更小的G 值,则更新该点信息
                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("FCost").GetComponent<Text>().text = cell.fCost.ToString();
                        cell.obj.transform.Find("GCost").GetComponent<Text>().text = cell.gCost.ToString();
                        cell.obj.transform.Find("HCost").GetComponent<Text>().text = cell.hCost.ToString();
                    }
                }

三:源代码:源工程:https://blog.csdn.net/qq_37310110/article/details/113336900

#region 模块信息
// **********************************************************************
// Copyright (C) 2021
// Please contact me if you have any questions
// File Name:             AssetBundleManager
// Author:                幻世界
// QQ:                    853394528 
// **********************************************************************
#endregion
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


/// <summary>
/// 单独的格子
/// </summary>
public class Cell
{
    public int x;
    public int y;
    public bool isWall;
    // 与起点的移动代价
    public int gCost;
    // 与目标点的评估移动代价
    //这种方式通常称作试探法,有点让人混乱。因为这是一个猜测,所以得到这个称谓。在找到路径之前,我们真的不知道实际的距离,因为途中有各种东西(墙,水,等等)
    public int hCost;
    // 总的移动代价
    //我们需要的路径是这样生成的:反复的遍历开放列表,选择具有最小F值的方块。
    public int fCost
    {
        get { return gCost + hCost; }
    }

    // 父节点
    public Cell parent=null;
    public GameObject obj;

    public Cell(int x,int y,bool isWall,GameObject obj)
    {
        this.x = x;
        this.y = y;
        this.isWall = isWall;
        this.obj = obj;
    }

    public override string ToString()
    {
        return "("+x+","+y+")";
    }
    public  string PrintCost()
    {
        return "{ fCost:" + fCost + ",hCost:" + hCost + "}";
    }
}

public class AstarGrid : MonoBehaviour {

    public GameObject CellAsset;
    public GameObject PointAsset;

    public int gridSize = 10;
    int cellSize = 70;

    List<string> wallList = new List<string>();
    public List<Cell> cells = new List<Cell>();

    public Vector2 startPos = new Vector2 (3,6);
    public Vector2 endPos = new Vector2 (7,6);

    public void FindPath()
    {
        FindPath(startPos, endPos);
    }

    public void CreateGridMap()
    {
        if (cells.Count != 0)
        {
            foreach (var item in transform.GetComponentsInChildren<Image>())
            {
                Destroy(item.gameObject);
            }
            cells.Clear();
        }
        gridSize = Random.Range(10, 15);
        UpDateGridMap();
    }


    public void UpDateGridMap()
    {
        //墙
        if (wallList.Count!=0)
        {
            wallList.Clear();
        }
        for (int i = 0; i < Random.Range(10, 15); i++)
        {
            wallList.Add(Random.Range(0, gridSize).ToString() + '_' + Random.Range(0, gridSize));
        }

        //起止点
        startPos = new Vector2(Random.Range(0, gridSize), Random.Range(0, gridSize));
        endPos = new Vector2(Random.Range(0, gridSize), Random.Range(0, gridSize));

        while (wallList.Contains(startPos.x + "_" + startPos.y) || wallList.Contains(endPos.x + "_" + endPos.y))
        {
            startPos = new Vector2(Random.Range(0, gridSize), Random.Range(0, gridSize));
            endPos = new Vector2(Random.Range(0, gridSize), Random.Range(0, gridSize));
        }

        //创建格子
        for (int i = 0; i < gridSize; i++)
        {
            for (int j = 0; j < gridSize; j++)
            {
                GameObject obj= GameObject.Instantiate<GameObject>(CellAsset, transform);
                RectTransform trans = obj.transform as RectTransform;
                trans.anchoredPosition = new Vector2(i,j) * cellSize;
                bool isWall=false;
                trans.Find("Pos").GetComponent<Text>().text = i + "," + j;
                if(wallList.Contains( i+"_"+j))
                {
                    isWall = true;
                    obj.GetComponent<Image>().color=Color.black;
                }
                Cell cell = new Cell(i, j, isWall, obj);
                cells.Add(cell);
            }
        }

        //创建起点
        GameObject obj1 = GameObject.Instantiate<GameObject>(PointAsset, transform);
        RectTransform trans1 = obj1.transform as RectTransform;
        trans1.anchoredPosition = startPos * cellSize;
        obj1.GetComponent<Image>().color = Color.red;
        trans1.localScale = Vector3.one * 1.2f;
        obj1.name = "start";

        //创建终点
        GameObject obj2 = Instantiate<GameObject>(PointAsset, transform);
        RectTransform trans2 = obj2.transform as RectTransform;
        trans2.anchoredPosition = endPos * cellSize;
        obj2.GetComponent<Image>().color = Color.green;
        trans2.localScale = Vector3.one * 1.2f;
        obj2.name = "end";
    }


    public Cell GetCell(Vector2 pos)
    {
        return cells.Find((c) => { return c.x == (int)pos.x && c.y == (int)pos.y; });
    }


    public void CreatePath(Cell endPos)
    {
        Cell cell = endPos;
        while(cell!=null)
        {
            GameObject obj1 = GameObject.Instantiate<GameObject>(PointAsset, transform);
            RectTransform trans1 = obj1.transform as RectTransform;
            trans1.anchoredPosition = cellSize*new Vector2(cell.x,cell.y);
            obj1.GetComponent<Image>().color = new Color(Color.blue.r, Color.blue.g, Color.blue.b,0.5f);
            cell = cell.parent;
        }
    }

    /// <summary>
    /// 使用A*算法寻路
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    void FindPath(Vector2 start, Vector2 end)
    {
        //开启列表
        List<Cell> openLs = new List<Cell>();
        //关闭列表
        List<Cell> closeLs = new List<Cell>();

        //起点
        Cell startCell = GetCell(start);
        //终点
        Cell endCell = 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() +"  open列表节点数量:" + openLs.Count);

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

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

            //根据检查点来找到周围可行走的点
            List<Cell> aroundCells = GetAllAroundCells(cur);
            foreach (var cell in aroundCells)
            {
                //如果是墙或者在关闭列表中则跳过
                if (cell.isWall || closeLs.Contains(cell))
                    continue;

                int cost = cur.gCost + GetDistanceCost(cell, cur);
                //检查经由当前方块到达那里是否具有更小的G 值,则更新该点信息
                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("FCost").GetComponent<Text>().text = cell.fCost.ToString();
                        cell.obj.transform.Find("GCost").GetComponent<Text>().text = cell.gCost.ToString();
                        cell.obj.transform.Find("HCost").GetComponent<Text>().text = cell.hCost.ToString();
                    }
                }
            }
        }
        Debug.Log("寻路失败!");
    }


    // 取得周围的节点
    public List<Cell> GetAllAroundCells(Cell cell)
    {
        List<Cell> list = new List<Cell>();
        for (int i = -1; i <= 1; i++)
        {
            for (int j = -1; j <= 1; j++)
            {
                // 如果是自己,则跳过
                if (i == 0 && j == 0)
                    continue;
                int x = cell.x + i;
                int y = cell.y + j;
                // 判断是否越界,如果没有,加到列表中
                if (x < gridSize && x >= 0 && y < gridSize && y >= 0)
                {
                    list.Add(GetCell(new Vector2(x, y)));
                }
            }
        }
        return list;
    }


    /// <summary>
    /// 估价函数,试探函数
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    /// 在本例中,我们为每个水平/垂直的移动指定代价为10,而斜角的移动代价为14。
    /// 我们使用这些值,因为斜角移动的实际距离是2的平方根(别害怕),或者大概1.414倍的水平/垂直的移动代价。
    /// 出于简化的目的使用了10和14
    int GetDistanceCost(Cell a, Cell b)
    {
        int cntX = Mathf.Abs(a.x - b.x);
        int cntY = Mathf.Abs(a.y - b.y);
        // 判断到底是那个轴相差的距离更远
        if (cntX > cntY)
        {
            return 14 * cntY + 10 * (cntX - cntY);
        }
        else
        {
            return 14 * cntX + 10 * (cntY - cntX);
        }
    }

}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幻世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值