zoukankan      html  css  js  c++  java
  • unity利用A*算法进行2D寻路

    找了份新工作之后,忙的要死,都没时间鼓捣博客了,深深的感受到资本家的剥削,端午节连粽子都没有,每天下班累得跟条咸鱼一样(可能就是)。

    刚好最近忙里偷闲,就来写写unity在2D下的AStar寻路算法。

    地图用untiy的tilemap来贴。

    大概的效果,没有去找好看的图片,将就弄点颜色表示:

    黑色表示障碍,绿色表示路径,开头和结尾也是用的绿色,好懒o(╥﹏╥)o

    原理和详细解释,还是参考的这位国外的大神:

    https://www.redblobgames.com/pathfinding/a-star/introduction.html

    解说如下:

    A*算法其实可以理解为是贪心算法和广度优先搜索算法的结合体。

    广度优先搜索算法,每次都可以找到最短的路径,每走一步都会记下起点到当前点的步数,优点是绝对能找到最短的路径,缺点就是地图越大计算量会变得很巨大。

    贪心算法,每次都是走当前点距离终点最近的格子,在没有障碍的情况下效率很高,但是如果有障碍的话,就很绕路。

    A*算法结合两者,计算当前走过的步数 与 当前点到终点的距离 之和作为走格子的依据,优点就是当有障碍物时,能找到最短距离并且计算量没有广度优先搜索大,没有障碍物时,效率和贪心算法一样高。

    其实代码量没多少,直接贴出来了,具体也不解释,看注释吧,我好懒。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Tilemaps;
    
    public class MapBehaviour : MonoBehaviour
    {
        public Vector2Int mapSize;//地图尺寸
    
        public Tilemap tilemap;
        public Tile normalTile;//白色tile
        public Tile obstacleTile;//黑色tile
        public Tile pathTile;//绿色tile
    
        public int obstacleCount;//要生成的障碍物数量
    
        public Vector3Int startPos;//起点
        public Vector3Int endPos;//终点
    
        private bool hasStartPosSet;//是否设置了起点
        private bool hasEndPosSet;//是否设置了终点
    
        private Dictionary<Vector3Int, int> search = new Dictionary<Vector3Int, int>();//要进行的查找任务
        private Dictionary<Vector3Int, int> cost = new Dictionary<Vector3Int, int>();//起点到当前点的消耗
        private Dictionary<Vector3Int, Vector3Int> pathSave = new Dictionary<Vector3Int, Vector3Int>();//保存回溯路径
        private List<Vector3Int> hadSearch = new List<Vector3Int>();//已经查找过的坐标
    
        private List<Vector3Int> obstacle = new List<Vector3Int>();//障碍物坐标
    
        private void Start()
        {
            CreateNormalTiles();
            CreateObstacleTiles();
        }
    
        private void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                if (!hasStartPosSet)//第一次点击设置起点
                {
                    startPos = tilemap.WorldToCell(Camera.main.ScreenToWorldPoint(Input.mousePosition));
                    tilemap.SetTile(startPos, pathTile);
                    hasStartPosSet = true;
                }
                else if (!hasEndPosSet)//第二次点击设置终点
                {
                    endPos = tilemap.WorldToCell(Camera.main.ScreenToWorldPoint(Input.mousePosition));
                    tilemap.SetTile(endPos, pathTile);
                    hasEndPosSet = true;
    
                    AStarSearchPath();
                }
                else//重置
                {
                    hasStartPosSet = false;
                    hasEndPosSet = false;
    
                    foreach (var item in pathSave)
                    {
                        tilemap.SetTile(item.Key, normalTile);
                    }
    
                    search.Clear();
                    cost.Clear();
                    pathSave.Clear();
                    hadSearch.Clear();
    
                }
            }
        }
        //创建白色地图
        public void CreateNormalTiles()
        {
            for (int i = 0; i < mapSize.x; i++)
            {
                for (int j = 0; j < mapSize.y; j++)
                {
                    Vector3Int position = new Vector3Int(i, j, 0);
                    tilemap.SetTile(position, normalTile);
                }
            }
        }
        //创建黑色障碍
        public void CreateObstacleTiles()
        {
            List<Vector3Int> blankTiles = new List<Vector3Int>();
    
            for (int i = 0; i < mapSize.x; i++)
            {
                for (int j = 0; j < mapSize.y; j++)
                {
                    blankTiles.Add(new Vector3Int(i, j, 0));
                }
            }
    
            for (int i = 0; i < obstacleCount; i++)
            {
                int index = Random.Range(0, blankTiles.Count);
                Vector3Int obstaclePos = blankTiles[index];
                blankTiles.RemoveAt(index);
                obstacle.Add(obstaclePos);
    
                tilemap.SetTile(obstaclePos, obstacleTile);
            }
        }
        //AStar算法查找
        public void AStarSearchPath()
        {
            //初始化
            search.Add(startPos, GetHeuristic(startPos, endPos));
            cost.Add(startPos, 0);
            hadSearch.Add(startPos);
            pathSave.Add(startPos, startPos);
    
            while (search.Count > 0)
            {
                Vector3Int current = GetShortestPos();//获取任务列表里的最少消耗的那个坐标
    
                if (current.Equals(endPos))
                    break;
    
                List<Vector3Int> neighbors = GetNeighbors(current);//获取当前坐标的邻居
    
                foreach (var next in neighbors)
                {
                    if (!hadSearch.Contains(next))
                    {
                        cost.Add(next, cost[current] + 1);//计算当前格子的消耗,其实就是上一个格子加1步
                        search.Add(next, cost[next] + GetHeuristic(next, endPos));//添加要查找的任务,消耗值为当前消耗加上当前点到终点的距离
                        pathSave.Add(next, current);//保存路径
                        hadSearch.Add(next);//添加该点为已经查询过
                    }
                }
            }
            
            if (pathSave.ContainsKey(endPos))
                ShowPath();
            else
                print("No road");
        }
        //获取周围可用的邻居
        private List<Vector3Int> GetNeighbors(Vector3Int target)
        {
            List<Vector3Int> neighbors = new List<Vector3Int>();
    
            Vector3Int up = target + Vector3Int.up;
            Vector3Int right = target + Vector3Int.right;
            Vector3Int left = target - Vector3Int.right;
            Vector3Int down = target - Vector3Int.up;
    
            //Up
            if (up.y < mapSize.y && !obstacle.Contains(up))
            {
                neighbors.Add(up);
            }
            //Right
            if (right.x < mapSize.x && !obstacle.Contains(right))
            {
                neighbors.Add(target + Vector3Int.right);
            }
            //Left
            if (left.x >= 0 && !obstacle.Contains(left))
            {
                neighbors.Add(target - Vector3Int.right);
            }
            //Down
            if (down.y >= 0 && !obstacle.Contains(down))
            {
                neighbors.Add(target - Vector3Int.up);
            }
    
            return neighbors;
        }
        //获取当前位置到终点的消耗
        private int GetHeuristic(Vector3Int posA, Vector3Int posB)
        {
            return Mathf.Abs(posA.x - posB.x) + Mathf.Abs(posA.y - posB.y);
        }
        //获取任务字典里面最少消耗的坐标
        private Vector3Int GetShortestPos()
        {
            KeyValuePair<Vector3Int, int> shortest = new KeyValuePair<Vector3Int, int>(Vector3Int.zero, int.MaxValue);
    
            foreach (var item in search)
            {
                if (item.Value < shortest.Value)
                {
                    shortest = item;
                }
            }
    
            search.Remove(shortest.Key);
    
            return shortest.Key;
        }
        //显示查找完成的路径
        private void ShowPath()
        {
            print(pathSave.Count);
            Vector3Int current = endPos;
    
            while (current != startPos)
            {
                Vector3Int next = pathSave[current];
    
                tilemap.SetTile(current, pathTile);
    
                current = next;
            }
        }
    }
    

      

    其实没什么难点,主要是理解,每次取出来计算下一步的点,是字典里面最少消耗值的那个点,然后每个点的消耗值都是,起点到当前的步数与当前到终点的距离(专业名词叫曼哈顿距离Manhattan distance2333)之和。

    完结。

    欢迎交流,转载注明出处!

  • 相关阅读:
    Linux关机命令详解
    这二个月都在失眠中。
    GMAIL绑定自己的域名MX解析出现错误
    又上火了。
    FTP命令
    怎样做反向域名解析?
    linux 一些常用到的命令。
    LINUX怎么换IP。
    关于 MOD 和 \ 选择
    WIN2000和WIN2003连备份机的疑问
  • 原文地址:https://www.cnblogs.com/JinT-Hwang/p/11141202.html
Copyright © 2011-2022 走看看