通过Unity展示A星寻路算法

逻辑思路

  • 对起点和终点的合法性进行判断
    1、起点和终点需要在地图范围内
    2、起点和终点其中一个不能是不可通过点
    3、起点和终点不能是同一个点
  • 清空上次寻路的信息
    1、将开放列表和关闭列表中的格子信息重置并清空开放列表和关闭列表
    2、将起点添加到关闭列表中,并设置其父节点信息为null
  • 寻路逻辑
    1、将起点设置为寻路逻辑起点A
    2、将A周围的8个符合条件的格子加入开放列表中,将A设置为其父节点并计算每个格子的消耗值h
    条件:
    (1)格子在地图范围内
    (2)格子是可通过的
    (3)格子未在开放列表和关闭列表中
    3、将开放列表根据消耗值(h)进行升序排序。
    4、如果开放列表为空,表示起点到终点的道路不通,寻路逻辑终止
    如果开放列表不为空,将开放列表中的第一个(即h值最小的)格子从开放列表移入到关闭列表中
    5、如果取出来的格子跟终点是一个,表示寻路结束,从终点开始,顺着其父节点往上找,直到顺到起点结束,这就是寻路的路径(注意该路径是从终点到起点的顺序,最后需要对该列表进行反转
    如果取出来的格子不是终点,将该格子设置为寻路逻辑的起点A,继续步骤2,直到满足步骤5的第一种情况

源码

在这里插入图片描述

using System.Collections.Generic;
using UnityEngine;

public class AStarTest : MonoBehaviour
{
    public float startX;
    public float startY;
    public float gapX = 2;
    public float gapY = 2;
    // 路径行数
    public int rows = 5;
    // 路径列数
    public int cols = 5;

    // 选中起点、终点时将方块改变成黄色显示
    public Material yellowMat;
    // 阻挡方块红色显示
    public Material redMat;
    // 可通过点白色显示
    public Material whiteMat;
    // 寻路结果绿色显示
    public Material greenMat;

    // 记录所有的格子的MeshRenderer,用来改变格子外观
    private Dictionary<string, MeshRenderer> gridsGo = new Dictionary<string, MeshRenderer>();

    // Start is called before the first frame update
    void Start()
    {
        AStarMgr.GetInstance().Init(rows, cols);

        InitGridGo();
        ResetGridGoDisplay();
    }

    /// <summary>
    /// 初始化格子
    /// </summary>
    void InitGridGo()
    {
        GameObject go;
        string gridIdStr;
        for (int i = 0; i < cols; i++)
        {
            for (int j = 0; j < rows; j++)
            {
                go = GameObject.CreatePrimitive(PrimitiveType.Cube);
                go.transform.position = new Vector3(startX + i * gapX, startY + j * gapY, 0);

                gridIdStr = AStarMgr.GetInstance().getGridIdStr(i, j);
                go.name = gridIdStr;

                gridsGo[gridIdStr] = go.GetComponent<MeshRenderer>();
            }
        }
    }

    /// <summary>
    /// 重置所有格子的外观显示
    /// </summary>
    void ResetGridGoDisplay()
    {
        GridInfo gridInfo;
        foreach (var item in gridsGo)
        {
            if (AStarMgr.GetInstance().GetAllGrids().TryGetValue(item.Key, out gridInfo))
            {
                GridType gridType = gridInfo.gridType;
                if (gridType == GridType.Unwalkable)
                {
                    item.Value.material = redMat;
                }
                else if(gridType == GridType.Walkable)
                {
                    item.Value.material = whiteMat;
                }
            }
        }
    }

    // 选中的起点坐标
    Vector2 start = Vector2.left;
    // 选中的终点坐标
    Vector2 end = Vector2.left;
    // Update is called once per frame
    void Update()
    {
        // 按下按键一次选择起点、终点,选中终点时执行寻路逻辑,将最终的路径用绿色显示。再次点击重新选择起点、终点
        if(Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit raycastInfo;
            if(Physics.Raycast(ray, out raycastInfo, 1000))
            {
                GameObject hitGo = raycastInfo.collider.gameObject;
                string hitGoName = hitGo.name;
                string[] tmp = hitGoName.Split('_');
                MeshRenderer mesh = gridsGo[hitGoName];
                
                if (start == Vector2.left)
                {
                    start = new Vector2(int.Parse(tmp[0]), int.Parse(tmp[1]));
                    mesh.material = yellowMat;
                }
                else if(end == Vector2.left)
                {
                    end = new Vector2(int.Parse(tmp[0]), int.Parse(tmp[1]));
                    mesh.material = yellowMat;
                    List<GridInfo> result = AStarMgr.GetInstance().GetPath(start, end);
                    foreach(var item in result)
                    {
                        mesh = gridsGo[AStarMgr.GetInstance().getGridIdStr(item.x, item.y)];
                        mesh.material = greenMat;
                    }
                }
                else
                {
                    ResetGridGoDisplay();
                    start = new Vector2(int.Parse(tmp[0]), int.Parse(tmp[1]));
                    mesh.material = yellowMat;
                    end = Vector2.left;
                }
            }
        }
    }
}
public enum GridType
{
    Walkable = 1,
    Unwalkable = 2,
}

public class GridInfo
{
    public GridType gridType;
    public GridInfo father;

    // 格子x坐标
    public int x;
    // 格子y坐标
    public int y;

    // 格子总消耗
    public float f;
    public float g;
    public float h;

    // 初始化格子信息
    public GridInfo(int x, int y, GridType gridType)
    {
        this.gridType = gridType;
        this.x = x;
        this.y = y;
    }

    // 重置格子信息
    public void Reset()
    {
        f = 0;
        g = 0;
        h = 0;
        father = null;
    }
}
using System.Collections.Generic;
using UnityEngine;

public class AStarMgr
{
    private static AStarMgr _instance;
    public static AStarMgr GetInstance()
    {
        if (_instance == null)
        {
            _instance = new AStarMgr();
        }
        return _instance;
    }

    // 所有格子信息
    private Dictionary<string, GridInfo> grids;
    // 行数
    private int rows;
    // 列数
    private int cols;

    // 开放列表
    private List<GridInfo> openList = new List<GridInfo>();
    // 关闭列表
    private List<GridInfo> closeList = new List<GridInfo>();

    // 起点格子信息
    private GridInfo startGridInfo;
    // 终点格子信息
    private GridInfo endGridInfo;

    public void Init(int rows, int cols)
    {
        this.rows = rows;
        this.cols = cols;

        if (grids == null)
        {
            grids = new Dictionary<string, GridInfo>();
        }
        grids.Clear();

        for (int i = 0; i < cols; i++)
        {
            for (int j = 0; j < rows; j++)
            {
                grids[getGridIdStr(i, j)] = new GridInfo(i, j, Random.Range(0, 100) < 20 ? GridType.Unwalkable : GridType.Walkable);
            }
        }
    }

    /// <summary>
    /// 获取路径
    /// </summary>
    /// <param name="start">起点坐标</param>
    /// <param name="end">终点坐标</param>
    /// <returns>路径</returns>
    public List<GridInfo> GetPath(Vector2 start, Vector2 end)
    {
        // 先判断传入的start和end是否合法:不是同一个点、在地图范围内并且格子状态为可到达状态
        if (start == null || end == null)
            return null;
        if ( start == end
            || start.x < 0 || start.x >= cols || start.x < 0 || start.y >= rows
            || end.x < 0 || end.x >= cols || end.x < 0 || end.y >= rows)
            return null;
        startGridInfo = this.getGridInfo((int)start.x, (int)start.y);
        endGridInfo = this.getGridInfo((int)end.x, (int)end.y);
        if (startGridInfo == null || startGridInfo.gridType == GridType.Unwalkable
            || endGridInfo == null || endGridInfo.gridType == GridType.Unwalkable)
            return null;

        foreach(var item in openList)
        {
            item.Reset();
        }
        openList.Clear();

        foreach(var item in closeList)
        {
            item.Reset();
        }
        closeList.Clear();
        // 将起始点加入关闭列表中
        startGridInfo.Reset();
        closeList.Add(startGridInfo);

        // 开始寻路
        return FindPath();
    }

    /// <summary>
    /// 寻路的逻辑
    /// </summary>
    /// <returns></returns>
    private List<GridInfo> FindPath()
    {
        GridInfo start = startGridInfo;
        while(true)
        {
            // 取到起始点的8个相邻点,符合条件(在地图范围内、状态为可通过且未在开放列表中和关闭列表中)的加入开放列表中
            for (int i = -1; i <= 1; i++)
            {
                for (int j = -1; j <= 1; j++)
                {
                    if (!(i == 0 && j == 0))
                    {
                        PutGridIntoOpenList(start.x + i, start.y + j, start);
                    }
                }
            }
            // 如果开放列表为空,表示起点无法到达终点
            if (openList.Count == 0)
            {
                Debug.Log("找不到路径");
                return null;
            }
            // 将开放列表中的节点按照消耗值从小到大排序
            openList.Sort((a, b) => a.f < b.f ? -1 : 1);
            // 将最小的消耗从开放列表移动到封闭列表
            GridInfo minCostGird = openList[0];
            closeList.Add(minCostGird);
            openList.RemoveAt(0);
            // 消耗最小的格子正好是目标点,寻路工作完成
            if (minCostGird == endGridInfo)
            {
                // 从最终的格子开始,顺着其父节点往上找,直到顺到起始点(因为起始点的父节点为null)即为最终的路径(注意该路径是从终点到起点的顺序),最后需要对该列表进行反转
                List<GridInfo> result = new List<GridInfo>();
                result.Add(minCostGird);
                while (minCostGird.father != null)
                {
                    result.Add(minCostGird.father);
                    minCostGird = minCostGird.father;
                }
                // 将列表反转
                result.Reverse();
                return result;
            }
            else
            {
                // 将该点作为起点重复执行上述逻辑
                start = minCostGird;
            }
        }
    }

    // 将格子放入到开放列表中,放入开放列表之前,需要计算出消耗相关的数值以及其父节点
    private void PutGridIntoOpenList(int x, int y, GridInfo father)
    {
        // 未超过地图范围
        if (x < 0 || x >= cols || y < 0 || y >= rows)
            return;
        GridInfo gridInfo = getGridInfo(x, y);
        if (gridInfo == null || gridInfo.gridType == GridType.Unwalkable)
            return;
        if (openList.Contains(gridInfo) || closeList.Contains(gridInfo))
            return;

        // 记录父节点
        gridInfo.father = father;
        // 计算消耗 对角线消耗为根号2,相邻的消耗为1
        float g = 0;
        if(x == father.x || y == father.y)
        {
            g = 1;
        }
        else
        {
            g = 1.414f;
        }
        // h的计算方法遵循曼哈顿街区规则
        float h = Mathf.Abs(endGridInfo.x - x) + Mathf.Abs(endGridInfo.y - y);
        gridInfo.f = father.g + g + h;
        // 将计算过的格子放入开放列表中
        openList.Add(gridInfo);
    }

    public Dictionary<string, GridInfo> GetAllGrids()
    {
        return grids;
    }

    public string getGridIdStr(int x, int y)
    {
        return $"{x}_{y}";
    }

    private GridInfo getGridInfo(int x, int y)
    {
        string gridIdStr = getGridIdStr(x, y);
        if(grids.ContainsKey(gridIdStr))
        {
            return grids[gridIdStr];
        }
        return null;
    }
}

结果演示

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
A*寻路算法是一种常用的路径规划算法,它在地图上寻找两个点之间的最短路径。在Unity中,我们可以使用以下步骤实现A*寻路算法: 1. 创建地图:我们需要创建一个网格地图,其中每个格子都代表着地图上的一个点。每个点都有一个坐标、可通过性和一些其他属性。 2. 创建节点类:我们需要创建一个节点类,用于保存每个点的信息。每个节点都有一个父节点、位置、代价和其他属性。 3. 创建Open和Close列表:我们需要创建两个列表,一个是Open列表,用于存储待搜索的节点,另一个是Close列表,用于存储已搜索过的节点。 4. 初始化起点和终点:我们需要初始化起点和终点,并将起点加入到Open列表中。 5. 搜索路径:我们需要重复以下步骤直到找到终点或者Open列表为空: a. 从Open列表中选取代价最小的节点作为当前节点。 b. 将当前节点从Open列表中删除,并将其加入到Close列表中。 c. 检查当前节点是否为终点,如果是则返回路径。 d. 遍历当前节点的相邻节点,计算它们的代价,并将它们加入到Open列表中。 6. 返回路径:如果找到了终点,则从终点开始沿着父节点一直往回走,直到回到起点。这样就得到了一条最短路径。 以下是示例代码: ```csharp using System.Collections; using System.Collections.Generic; using UnityEngine; public class AStar : MonoBehaviour { public LayerMask wallMask; public Vector2 gridSize; public float nodeRadius; Node[,] grid; float nodeDiameter; int gridSizeX, gridSizeY; void Start() { nodeDiameter = nodeRadius * 2; gridSizeX = Mathf.RoundToInt(gridSize.x / nodeDiameter); gridSizeY = Mathf.RoundToInt(gridSize.y / nodeDiameter); CreateGrid(); } void CreateGrid() { grid = new Node[gridSizeX, gridSizeY]; Vector3 worldBottomLeft = transform.position - Vector3.right * gridSize.x / 2 - Vector3.forward * gridSize.y / 2; for (int x = 0; x < gridSizeX; x++) { for (int y = 0; y < gridSizeY; y++) { Vector3 worldPoint = worldBottomLeft + Vector3.right * (x * nodeDiameter + nodeRadius) + Vector3.forward * (y * nodeDiameter + nodeRadius); bool walkable = !Physics.CheckSphere(worldPoint, nodeRadius, wallMask); grid[x, y] = new Node(walkable, worldPoint, x, y); } } } public List<Node> GetNeighbours(Node node) { List<Node> neighbours = new List<Node>(); for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { if (x == 0 && y == 0) continue; int checkX = node.gridX + x; int checkY = node.gridY + y; if (checkX >= 0 && checkX < gridSizeX && checkY >= 0 && checkY < gridSizeY) { neighbours.Add(grid[checkX, checkY]); } } } return neighbours; } public Node NodeFromWorldPoint(Vector3 worldPosition) { float percentX = (worldPosition.x + gridSize.x / 2) / gridSize.x; float percentY = (worldPosition.z + gridSize.y / 2) / gridSize.y; percentX = Mathf.Clamp01(percentX); percentY = Mathf.Clamp01(percentY); int x = Mathf.RoundToInt((gridSizeX - 1) * percentX); int y = Mathf.RoundToInt((gridSizeY - 1) * percentY); return grid[x, y]; } public List<Node> FindPath(Vector3 startPos, Vector3 targetPos) { Node startNode = NodeFromWorldPoint(startPos); Node targetNode = NodeFromWorldPoint(targetPos); List<Node> openSet = new List<Node>(); HashSet<Node> closedSet = new HashSet<Node>(); openSet.Add(startNode); while (openSet.Count > 0) { Node currentNode = openSet[0]; for (int i = 1; i < openSet.Count; i++) { if (openSet[i].fCost < currentNode.fCost || (openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost)) { currentNode = openSet[i]; } } openSet.Remove(currentNode); closedSet.Add(currentNode); if (currentNode == targetNode) { return RetracePath(startNode, targetNode); } foreach (Node neighbour in GetNeighbours(currentNode)) { if (!neighbour.walkable || closedSet.Contains(neighbour)) { continue; } int newCostToNeighbour = currentNode.gCost + GetDistance(currentNode, neighbour); if (newCostToNeighbour < neighbour.gCost || !openSet.Contains(neighbour)) { neighbour.gCost = newCostToNeighbour; neighbour.hCost = GetDistance(neighbour, targetNode); neighbour.parent = currentNode; if (!openSet.Contains(neighbour)) { openSet.Add(neighbour); } } } } return null; } List<Node> RetracePath(Node startNode, Node endNode) { List<Node> path = new List<Node>(); Node currentNode = endNode; while (currentNode != startNode) { path.Add(currentNode); currentNode = currentNode.parent; } path.Reverse(); return path; } int GetDistance(Node nodeA, Node nodeB) { int distX = Mathf.Abs(nodeA.gridX - nodeB.gridX); int distY = Mathf.Abs(nodeA.gridY - nodeB.gridY); if (distX > distY) { return 14 * distY + 10 * (distX - distY); } return 14 * distX + 10 * (distY - distX); } public class Node { public bool walkable; public Vector3 worldPosition; public int gridX; public int gridY; public int gCost; public int hCost; public Node parent; public Node(bool _walkable, Vector3 _worldPos, int _gridX, int _gridY) { walkable = _walkable; worldPosition = _worldPos; gridX = _gridX; gridY = _gridY; } public int fCost { get { return gCost + hCost; } } } } ``` 在上面的代码中,我们首先创建了一个网格地图,并在其中创建了节点类。然后,我们实现了A*算法的核心部分,并将其用于在网格地图上搜索路径。在搜索结束后,我们返回了一条最短路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值