通过Unity展示A星寻路算法

本文详细介绍了一种基于A星算法的寻路实现方法,包括起点与终点的合法性验证、地图格子的状态设置及寻路过程的具体逻辑。通过具体代码展示了如何在Unity环境中实现这一算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

逻辑思路

  • 对起点和终点的合法性进行判断
    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;
    }
}

结果演示

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值