简单AStar寻路2D示例

A*寻路

简单AStar寻路2D示例_第1张图片

牢记A*寻路的核心

F(n) = G(n) + H(n)
G(n): 从起始位置到n位置所需的实际消耗
H(n): 从n位置到目标位置的预计消耗
所以F(n)就是从起始位置经过n位置到目标位置的路径总消耗
A*寻路的过程就是不断找最小F(n)的过程

A*寻路流程

1.加入“开启列表”
先将起点作为当前检查点加入“开启列表”,“开启列表”是一个等待检查方格的列表,起点格子是最开始的检查点
2.检查周围
将当前检查点周围可到达的点加入“开启列表”,并设置它们的"父节点"为当前的检查点,并且计算出这些点的G(n)、H(n)、F(n)值
如果周围的点已经有在“开启列表” 中,则我们需要对比新旧路径的G值,如果新路径的G值更小,则使用新路径,需要更新“父节点”
如果是障碍物,则忽略
3.移入“关闭列表”
将这个检查点从“开启列表”移到“关闭列表”,"关闭列表"中存放的都是不需要再次检查的格子点
4.继续找直到找不到或者找到为止
再从“开启列表”中找出F值最小的格子,然后从上述的第1步开始重复继续,直到找到的F值最小的格子为终点或者“开启列表”中没有格子了,即为结束

几点注意点

  1. H(n)预计消耗的计算的准确度非常重要
    由于G(n)是已经走过的路,所需的消耗是明确的,只有H(n)是预计的,预计的越准,我们就能越快的找到目标位置,减少计算量,也能更贴近最短路径
  2. A*寻路的结果并不一定是最短
    由于H(n)是预计的消耗,这个预计的消耗可能会出现与实际情况相差较大的情况,这个时候寻路结果可能不是最短的

纸上得来终觉浅,绝知此事要躬行

简单做了个示例演示A*寻路的过程,以辅助理解
地图随机生成指定数量的障碍物,随机指定起始位置和终点位置
Game视图 Esc重置地图 Space 执行一步寻路步骤
白色:未检查过的格子
黑色:障碍物
黄色:在“开启列表”中的格子
灰色:在“关闭列表”中的格子
红色:目标格子
绿色:当前正在检查的格子
绿色链:最终的寻路结果
格子左上角:G值
格子右上角:H值
格子左下角:F值
格子右下角:坐标
箭头:指向寻路过程中的父格子

这里随机两次寻路过程来看下:
简单AStar寻路2D示例_第2张图片
简单AStar寻路2D示例_第3张图片
看下最终的寻路图
简单AStar寻路2D示例_第4张图片

代码

完整工程:
Unity版本:2020.3.10

AStarGrid

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

public class AStarGrid
{
    public int X { get; set; }  //坐标X
    public int Y { get; set; }  //坐标Y
    public float F { get { return G + H; } }  
    public float G { get; set; }
    public float H { get; set; }
    public bool IsObstacle { get; set; }
    public AStarGrid ParentGrid { get; set; }

    public AStarGrid(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }

    public void Reset()
    {
        G = 0;
        H = 0;
        IsObstacle = false;
        ParentGrid = null;
    }
}

AStarMap

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

public class AStarMap
{
    AStarGrid[,] map;
    int mapWidth;
    int mapHeight;

    //开启列表
    Dictionary<int, AStarGrid> openList = new Dictionary<int, AStarGrid>();
    //关闭列表
    Dictionary<int, AStarGrid> closeList = new Dictionary<int, AStarGrid>();

    public AStarMap(int mapWidth, int mapHeight)
    {
        this.mapWidth = mapWidth;
        this.mapHeight = mapHeight;
        map = new AStarGrid[mapWidth, mapHeight];
        for (int w = 0; w < mapWidth; w++)
        {
            for (int h = 0; h < mapHeight; h++)
            {
                map[w, h] = new AStarGrid(w, h);
            }
        }
    }

    public void SetObstacle(int x, int y, bool isObstacle)
    {
        map[x, y].IsObstacle = isObstacle;
    }

    public AStarGrid GetGrid(int x, int y)
    {
        return map[x, y];
    }

    /// 
    /// 直接寻路,返回寻路结果
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    public List<AStarGrid> FindPath(int startX, int startY, int targetX, int targetY)
    {
        openList.Clear();
        closeList.Clear();

        var startGrid = map[startX, startY];
        var targetGrid = map[targetX, targetY];
        var grid = startGrid;
        AddToOpenList(grid, null, targetGrid);  //先将起点加入“开启列表”,“开启列表”是一个等待检查方格的列表,起点格子是最开始的检查点
        while (grid != null)  //null时意味着找不到路径
        {
            if (grid == targetGrid)  //找到了最终目标点
            {
                break;
            }
            //将当前检查点周围可到达的点加入“开启列表”,并设置它们的"父节点"为当前的检查点 
            //在这个过程中我们需要更新格子的F、G、H的值,F(n) = G(n) + H(n)
            //G(n): 从起始点到n点所需的消耗
            //H(n): 从n点到目标点预计的消耗
            //F(n): 当前经过n点的这条路径的总消耗
            //至于如何计算G、H值,需要自己定,G值是确定的,而H值是预计的,因此H值的计算准确直接会影响到寻路的效率(预计耗费有很多计算方法,我们要尽量找出最适合的计算方法,这样子才能更快速的找出“最短”路径)
            AddAroundGridToOpenList(grid, targetGrid);
            //将这个检查点移至“关闭列表”, "关闭列表"中存放的都是不需要再次检查的格子点
            MoveGridToCloseFromOpen(grid);
            //从“开启列表”中找出F值最低的格子作为下一个检查点,F值最低代表着可能是最快到达的路径
            grid = FindMinFGrid();
        }

        List<AStarGrid> path = new List<AStarGrid>();
        while (grid != null)
        {
            //通过不断的连接父节点,这样子列表的格子就是最终的路径
            path.Add(grid);
            grid = grid.ParentGrid;
        }
        return path;
    }

#region 一步一步寻路演示
    AStarGrid pathStartGrid, pathTargetGrid, pathCurrentGrid;
    List<AStarGrid> pathResult = new List<AStarGrid>();
    public void SetFindPath(int startX, int startY, int targetX, int targetY)
    {
        openList.Clear();
        closeList.Clear();
        pathResult.Clear();
        pathStartGrid = map[startX, startY];
        pathTargetGrid = map[targetX, targetY];
        pathCurrentGrid = pathStartGrid;
        AddToOpenList(pathCurrentGrid, null, pathTargetGrid);
    }

    /// 
    /// 执行一次寻路步骤
    /// 
    /// 寻路是否完成
    public bool DoFindPathStep()
    {
        if (pathCurrentGrid == null || pathCurrentGrid == pathTargetGrid)
            return true;

        AddAroundGridToOpenList(pathCurrentGrid, pathTargetGrid);
        MoveGridToCloseFromOpen(pathCurrentGrid);
        pathCurrentGrid = FindMinFGrid();

        if (pathCurrentGrid == null)
            return true;
        if (pathCurrentGrid == pathTargetGrid)
        {
            while (pathCurrentGrid != null)
            {
                pathResult.Add(pathCurrentGrid);
                pathCurrentGrid = pathCurrentGrid.ParentGrid;
            }
            return true;
        }
        return false;
    }

    public List<AStarGrid> GetPathResult()
    {
        return pathResult;
    }

    public bool IsCurrentCheckGrid(AStarGrid grid)
    {
        return grid == pathCurrentGrid;
    }

    public bool IsTargetGrid(AStarGrid grid)
    {
        return grid == pathTargetGrid;
    }
#endregion

    public bool IsInOpenList(AStarGrid grid)
    {
        return openList.ContainsKey(GetGridIndex(grid));
    }

    public bool IsInCloseList(AStarGrid grid)
    {
        return closeList.ContainsKey(GetGridIndex(grid));
    }

    void AddToOpenList(AStarGrid grid, AStarGrid parentGrid, AStarGrid targetGrid)
    {
        int index = GetGridIndex(grid);
        if (openList.ContainsKey(index))
        {
            float newG = ComputeG(grid, parentGrid);
            if (newG < grid.G)
            {
                grid.ParentGrid = parentGrid;
                grid.G = newG;
            }
        }
        else
        {
            openList.Add(index, grid);
            grid.ParentGrid = parentGrid;
            UpdateGridGH(grid, targetGrid);
        }
    }

    int GetGridIndex(AStarGrid grid)
    {
        return grid.Y * mapWidth + grid.X;
    }

    int GetGridIndex(int x, int y)
    {
        return y * mapWidth + x;
    }

    void AddAroundGridToOpenList(AStarGrid grid, AStarGrid targetGrid)
    {
        int cx, cy;
        AStarGrid g;
        for (int x = -1; x <= 1; x++)
        {
            for (int y = -1; y <= 1; y++)
            {
                cx = grid.X + x;
                cy = grid.Y + y;
                if (cx >= 0 && cx < mapWidth && cy >= 0 && cy < mapHeight)
                {
                    g = map[cx, cy]; if (!g.IsObstacle && !closeList.ContainsKey(GetGridIndex(g)))
                    {
                        AddToOpenList(g, grid, targetGrid);
                    }
                }
            }
        }
    }

    void MoveGridToCloseFromOpen(AStarGrid grid)
    {
        int index = GetGridIndex(grid);
        openList.Remove(index);
        if (!closeList.ContainsKey(index))
        {
            closeList.Add(index, grid);
        }
    }

    void UpdateGridGH(AStarGrid grid, AStarGrid targetGrid)
    {
        var parentGrid = grid.ParentGrid;
        grid.G = ComputeG(grid, parentGrid);

        grid.H = Mathf.Sqrt((float)((targetGrid.X - grid.X) * (targetGrid.X - grid.X) + (targetGrid.Y - grid.Y) * (targetGrid.Y - grid.Y)));
    }

    float ComputeG(AStarGrid grid, AStarGrid parentGrid)
    {
        float g = 0;
        if (parentGrid != null)
        {
            g = parentGrid.G + Mathf.Sqrt((float)((parentGrid.X - grid.X) * (parentGrid.X - grid.X) + (parentGrid.Y - grid.Y) * (parentGrid.Y - grid.Y)));
        }
        return g;
    }

    AStarGrid FindMinFGrid()
    {
        AStarGrid minFGrid = null;
        foreach (var grid in openList.Values)
        {
            if (minFGrid == null)
            {
                minFGrid = grid;
            }
            else
            {
                if (grid.F < minFGrid.F)
                {
                    minFGrid = grid;
                }
            }
        }
        return minFGrid;
    }

}

GridCtrl

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

public class GridCtrl : MonoBehaviour
{
    public Text txtF;
    public Text txtG;
    public Text txtH;
    public Text txtCoordinate;
    public RectTransform directionObj;
    public Image bg;

    AStarMap map;
    AStarGrid grid;
    public void SetGrid(AStarGrid grid, AStarMap map)
    {
        this.map = map;
        this.grid = grid;
        var rect = (transform as RectTransform).rect;
        transform.position = new Vector3((rect.width + 5) * grid.X , (rect.height + 5) * grid.Y + 5, 0);
    }

    public void UpdateShow()
    {
        txtF.text = grid.F.ToString("f2");
        txtG.text = grid.G.ToString("f2");
        txtH.text = grid.H.ToString("f2");
        txtCoordinate.text = string.Format("({0},{1})", grid.X, grid.Y);
        if (grid.ParentGrid != null)
        {
            Vector2 parentGridDir = new Vector2(grid.ParentGrid.X - grid.X, grid.ParentGrid.Y - grid.Y);
            float angle = Vector2.SignedAngle(parentGridDir, new Vector2(1, 0));
            directionObj.rotation = Quaternion.Euler(0, 0, -angle);
            directionObj.gameObject.SetActive(true);
        }
        else
        {
            directionObj.gameObject.SetActive(false);
        }

        if(grid.IsObstacle)
        {
            bg.color = Color.black;
        }
        else if(map.IsCurrentCheckGrid(grid))
        {
            bg.color = Color.green;
        }
        else if(map.IsTargetGrid(grid))
        {
            bg.color = Color.red;
        }
        else if(map.IsInOpenList(grid))
        {
            bg.color = Color.yellow;
        }
        else if(map.IsInCloseList(grid))
        {
            bg.color = Color.grey;
        }
        else
        {
            bg.color = Color.white;
        }
    }

    public void SetBgColor(Color color)
    {
        bg.color = color;
    }
}

AStarMapGenerator

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

public class AStarMapGenerator : MonoBehaviour
{
    public int mapWidth = 5;
    public int mapHeight = 5;
    public GameObject GridPrefab;
    public Vector2 startGrid;
    public Vector2 targetGrid;
    public bool isRandomMap;
    public int randomObstacleCount;
    public Vector2[] obstacles;

    GridCtrl[,] gridCtrlMap;
    AStarMap map;

    void Start()
    {
        GenerateMap();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            DoPathFindStep();
        }
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            ResetMap();
        }
    }

    public void GenerateMap()
    {
        map = new AStarMap(mapWidth, mapHeight);
        gridCtrlMap = new GridCtrl[mapWidth, mapHeight];
        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
                var grid = map.GetGrid(x, y);
                var gridCtrl = GameObject.Instantiate<GameObject>(GridPrefab, transform).GetComponent<GridCtrl>();
                gridCtrl.SetGrid(grid, map);
                gridCtrlMap[x, y] = gridCtrl;
            }
        }
        InitMap();
    }

    void ResetMap()
    {
        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
                var grid = map.GetGrid(x, y);
                grid.Reset();
            }
        }
        InitMap();
    }

    void InitMap()
    {
        if (isRandomMap)
        {
            int obsCount = 0;
            int rx, ry;
            while (obsCount < randomObstacleCount)
            {
                rx = Random.Range(0, mapWidth);
                ry = Random.Range(0, mapHeight);
                if (!map.GetGrid(rx, ry).IsObstacle)
                {
                    map.SetObstacle(rx, ry, true);
                    obsCount++;
                }
            }
            AStarGrid sGrid, tGrid;
            while (true)
            {
                rx = Random.Range(0, mapWidth);
                ry = Random.Range(0, mapHeight);
                if (!map.GetGrid(rx, ry).IsObstacle)
                {
                    sGrid = map.GetGrid(rx, ry);
                    break;
                }
            }
            while (true)
            {
                rx = Random.Range(0, mapWidth);
                ry = Random.Range(0, mapHeight);
                tGrid = map.GetGrid(rx, ry);
                if (!tGrid.IsObstacle && tGrid != sGrid)
                {
                    break;
                }
            }
            map.SetFindPath(sGrid.X, sGrid.Y, tGrid.X, tGrid.Y);
            Debug.Log(sGrid.X + " " + sGrid.Y + "    " + tGrid.X + " " + tGrid.Y);
        }
        else
        {
            for (int i = 0; i < obstacles.Length; i++)
            {
                map.SetObstacle((int)obstacles[i].x, (int)obstacles[i].y, true);
            }
            map.SetFindPath((int)startGrid.x, (int)startGrid.y, (int)targetGrid.x, (int)targetGrid.y);
        }
        UpdateShow();
    }

    void DoPathFindStep()
    {
        bool isComplete = map.DoFindPathStep();
        UpdateShow();
        if (isComplete)
        {
            var path = map.GetPathResult();
            if (path.Count > 0)
            {
                foreach (var grid in path)
                {
                    gridCtrlMap[grid.X, grid.Y].SetBgColor(Color.green);
                }
            }
        }
    }

    void UpdateShow()
    {
        for (int y = 0; y < mapHeight; y++)
        {
            for (int x = 0; x < mapWidth; x++)
            {
                gridCtrlMap[x, y].UpdateShow();
            }
        }
    }
}

你可能感兴趣的:(Unity3D,unity,A星,寻路)