A星算法——简易版

A星算法——简易版

A星管理器: AStarWrapper.cs类

using System.Collections.Generic;
using UnityEngine;

namespace AStar {

    /// <summary>
    /// A星寻路的单例封装
    /// 实现原理
    /// 1、首先有一张一定宽高的地图 (定义好 Point 点的地图,其中 Point 中有 IsWall 属性)
    /// 2、设定开始点,和目标点
    /// 3、传入 FindPath 开始寻找较短路径,找到返回true,否则 false
    /// 4、为 true 就可以通过 目标点的父亲点的父亲点的父亲点,直到父亲点为开始点,这些点集合即是路径
    /// 5、FindPath 寻找原理
    /// 1)开列表,关列表初始化
    /// 2)添加开始点到开列表,然后获得周围点集合,接着又把开始点从开列表中移除,并添加到关列表
    /// 3)判断这些周围点集合是否已经在开列表中,不在则更新这些点的F 和 父亲点,并添加到开列表;再则重新计算G值,G较小则更新GF 和父亲点
    /// 4)从周围点集合中找到 F 最小的点,然后获得周围点集合,接着又把找到 F 最小的点从开列表中移除,并添加到关列表
    /// 5)接着执行第 3) 步骤
    /// 6)直到目标点被添加到开列表中,则路径找到
    /// 7)否则,直到开列表中没有了数据,则说明没有合适路径
    /// </summary>
    public class AStarWrapper : Singleton<AStarWrapper>
    {
        /// <summary>
        /// 根据开始点和结束点,取得较短路径
        /// </summary>
        /// <param name="start">开始位置</param>
        /// <param name="end">结束位置</param>
        /// <param name="map">地图</param>
        /// <param name="mapWidth">地图宽</param>
        /// <param name="mapHeight">地图高</param>
        /// <returns>bool 找到路线/false 没有</returns>
        public bool FindPath(Point start, Point end, Point[,] map, int mapWidth, int mapHeight)
        {
            // 开列表(周围的点列表),关列表(每次比较后得到的最小 F 的点列表)
            List<Point> openList = new List<Point>();
            List<Point> closeList = new List<Point>();

            // 首先开始点添加进开列表
            openList.Add(start);
            while (openList.Count > 0)
            {
                // 寻找开列表中最小的 F 值
                Point point = FindMinFOfPoint(openList);
                
                // 把得到的 F 最小的点移除 开列表,添加到关列表中
                openList.Remove(point);
                closeList.Add(point);

                // 获取当前最小 F 点周围的点集合 
                List<Point> surroundPoints = GetSurroundPoints(point, map, mapWidth, mapHeight);

                // 过滤掉关列表中的数据
                PointFilter(surroundPoints, closeList);

                // 遍历获得的周围点
                foreach (Point item in surroundPoints)
                {
                    // 已存在开列表中的话
                    if (openList.IndexOf(item) > -1)
                    {
                        // 计算 G 值
                        float nowG = CalcG(item, point);

                        // 得到的 G 值小的话,更新 G 值和点的父节点
                        if (nowG < item.G)
                        {
                            item.UpdateParent(point, nowG);
                        }
                    }
                    else // 不在开列表中
                    {
                        //把 最小 F 点,设置为他的父节点
                        item.Parent = point;
                        // 计算更新 item的 FGH
                        CalcF(item, end);
                        // 添加到 开列表
                        openList.Add(item);
                    }

                }

                // 判断 end 是否在 开列表中,即找到路径结束
                if (openList.IndexOf(end) > -1)
                {
                    return true;
                }
            }

            // 开列表没有了点,说明找不到路径
            return false;
        }

      
        /// <summary>
        /// 把关列表中的点从周围点集合中过滤掉
        /// </summary>
        /// <param name="src">周围点集合</param>
        /// <param name="closeList">关列表</param>
        private void PointFilter(List<Point> src, List<Point> closeList)
        {
            // 遍历,存在则移除
            foreach (Point item in closeList)
            {
                if (src.IndexOf(item) > -1)
                {
                    src.Remove(item);
                }

            }
        }

        /// <summary>
        /// 获得当前最小 F 的周围点
        /// </summary>
        /// <param name="point">当前最小 F 点</param>
        /// <param name="map">地图点集合</param>
        /// <param name="mapWidth">地图宽</param>
        /// <param name="mapHeight">地图高</param>
        /// <returns>返回获得的周围点集合</returns>
        private List<Point> GetSurroundPoints(Point point, Point[,] map, int mapWidth, int mapHeight)
        {
            // 一个点一般会有上、下、左、右,左上、左下、右上、右下 八个点
            Point up = null, down = null, left = null, right = null;
            Point lu = null, ru = null, ld = null, rd = null;

            // 如果 点 Y 小于 mapHeight - 1,则表明不是最顶端,有上点
            if (point.Y < mapHeight - 1)
            {
                up = map[point.X, point.Y + 1];
            }
            // 如果 点 Y 大于 0,则表明不是最下端,有下点
            if (point.Y > 0)
            {
                down = map[point.X, point.Y - 1];
            }
            // 如果 点 X 小于 mapWidth - 1,则表明不是最右端,有右点
            if (point.X < mapWidth - 1)
            {
                right = map[point.X + 1, point.Y];
            }
            // 如果 点 X 大于 0,则表明不是最左端,有左点
            if (point.X > 0)
            {
                left = map[point.X - 1, point.Y];
            }

            // 边角点
            // 有上点和左点,说明有左上点
            if (up != null && left != null)
            {
                lu = map[point.X - 1, point.Y + 1];
            }
            // 有上点和右点,说明有右上点
            if (up != null && right != null)
            {
                ru = map[point.X + 1, point.Y + 1];
            }
            // 有下点和左点,说明有左下点
            if (down != null && left != null)
            {
                ld = map[point.X - 1, point.Y - 1];
            }
            // 有下点和右点,说明有右下点
            if (down != null && right != null)
            {
                rd = map[point.X + 1, point.Y - 1];
            }

            // 新建一个列表
            List<Point> list = new List<Point>();
            // 把点添加到列表
            // 上点不为空,且不是障碍物,则添加到返回列表中,下面同理
            if (up != null && up.IsWall == false)
            {
                list.Add(up);
            }
            if (down != null && down.IsWall == false)
            {
                list.Add(down);
            }
            if (left != null && left.IsWall == false)
            {
                list.Add(left);
            }
            if (right != null && right.IsWall == false)
            {
                list.Add(right);
            }

            // 添加边角到列表
            // 左上点不为空且不是障碍物,并且 左点和上点都不是障碍物,则添加到返回列表,以下同理
            if (lu != null && lu.IsWall == false && left.IsWall == false && up.IsWall == false)
            {
                list.Add(lu);
            }
            if (ru != null && ru.IsWall == false && right.IsWall == false && up.IsWall == false)
            {
                list.Add(ru);
            }
            if (ld != null && ld.IsWall == false && left.IsWall == false && down.IsWall == false)
            {
                list.Add(ld);
            }
            if (rd != null && rd.IsWall == false && right.IsWall == false && down.IsWall == false)
            {
                list.Add(rd);
            }

            // 返回列表
            return list;

        }


        /// <summary>
        /// 比较开列表中所有点的 F 值,获得当前列表中最小的点
        /// </summary>
        /// <param name="openList">开列表</param>
        /// <returns></returns>
        private Point FindMinFOfPoint(List<Point> openList)
        {
            float f = float.MaxValue;
            Point tmp = null;

            foreach (Point p in openList)
            {
                if (p.F < f)
                {
                    tmp = p;
                    f = p.F;
                }
            }

            return tmp;

        }

        /// <summary>
        /// 计算 G 值
        /// </summary>
        /// <param name="now">当前点</param>
        /// <param name="parent">当前点的父节点</param>
        /// <returns></returns>
        private float CalcG(Point now, Point parent)
        {
            return Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(parent.X, parent.Y)) + parent.G;
        }

        /// <summary>
        /// 计算 F 值
        /// </summary>
        /// <param name="now">当前点</param>
        /// <param name="end">结束点</param>
        private void CalcF(Point now, Point end)
        {
            // F = G + H
            // H 值的计算方式不唯一,有意义就行,这里是结束点X和Y的和
            float h = Mathf.Abs(end.X - now.X) + Mathf.Abs(end.Y - now.Y);
            float g = 0;
            if (now.Parent == null)
            {
                g = 0;
            }
            else
            {
                // 当前点和父节点的距离
                g = Vector2.Distance(new Vector2(now.X, now.Y), new Vector2(now.Parent.X, now.Parent.Y)) + now.Parent.G;
            }

            // 更新当前点FGH的值
            float f = g + h;
            now.F = f;
            now.G = g;
            now.H = h;
        }
    }
}


路点:Point.cs类

using UnityEngine;

namespace AStar
{
    /// <summary>
    /// AStar 中点的定义
    /// </summary>
    public class Point
    {
        // 父亲节点
        public Point Parent { get; set; }

        // F G H 值
        public float F { get; set; }
        public float G { get; set; }
        public float H { get; set; }

        // 坐标值
        public int X { get; set; }
        public int Y { get; set; }

        // 是否是障碍物(例如墙)
        public bool IsWall { get; set; }

        // 该点的游戏物体(根据需要可不用可删除)
        public GameObject gameObject;
        // 该点的空间位置(根据需要可不用可删除)
        public Vector3 position;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="go"></param>
        /// <param name="parent"></param>
        /// <param name="position"></param>
        public Point(int x, int y, GameObject go = null, Point parent = null, Vector3 position = default)
        {
            this.X = x;
            this.Y = y;
            this.gameObject = go;
            this.position = position;
            this.Parent = parent;
            IsWall = false;
        }

        /// <summary>
        /// 更新G,F 值,和父亲节点
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="g"></param>
        public void UpdateParent(Point parent, float g)
        {
            this.Parent = parent;
            this.G = g;
            F = G + H;
        }
    }
}

单例类:Singleton.cs

public class Singleton<T> where T :class,new()
{
    private static T instance;
    private static readonly object locker = new object();
    public static T Instance {
        get {

            lock (locker) {

                if (instance == null) {
                    instance = new T();
                }

                return instance;
            }
        }
    }
}

A星测试:Test_AStarWrapper.cs

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

public class Test_AStarWrapper : MonoBehaviour
{
    int mapWidth;//地图宽度
    int mapHeight;//地图高度
    Point[,] map = null;//所有地图点
    List<Point> wallList = null;//阻碍点
    int wallNum;//阻碍数量
    Point startPoint = null;//开始位置
    Point targetPoint = null;//目标位置
    List<Point> oldPath = null;//旧的路径
    List<Point> newPath = null;//新的路径

    //有关颜色
    Color normalColor = Color.white;//正常颜色
    Color wallColor = Color.black;//阻碍颜色
    Color startPosColor = Color.green;//开始颜色
    Color targetPosColor = Color.red;//目标颜色
    Color pathPosColor = Color.yellow;//路径颜色
    
    //有关相机
    Vector3 cameraTargetPosition;
    Camera cameraMain;

    void Start()
    {
        Init();
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            //点击鼠标左键,设置起始点
            SelectStartPoint(Input.mousePosition);
        }

        if (Input.GetMouseButtonDown(1))
        {
            //点击鼠标右键,设置目标点
            SelectTargetPoint(Input.mousePosition);
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            //按下空格重置地图
            RefreshMap();
        }
    }
    
    void Init() {
        mapWidth = 12;
        mapHeight = 10;
        map = new Point[mapWidth, mapHeight];
        wallNum = (int)(mapWidth * mapHeight * 0.1f);
        wallList = new List<Point>();
        oldPath = new List<Point>();
        newPath = new List<Point>();
        InitMap(mapWidth, mapHeight);//创建地图
        SetWall();//设置地图障碍物

        //有关相机
        cameraMain = Camera.main;
        cameraTargetPosition = new Vector3(mapWidth / 2, mapHeight / 2, (mapWidth> mapHeight)?-mapWidth:-mapHeight);
        cameraMain.transform.position = cameraTargetPosition;
    }

    void InitMap(int mapWidth, int mapHeight)
    {
        for (int x = 0; x < mapWidth; x++)
        {
            for (int y = 0; y < mapHeight; y++)
            {
                //创建格子
                GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
                go.transform.position = new Vector3(x, y, 0);
                go.transform.localScale = Vector3.one * 0.9f;
                go.GetComponent<Renderer>().material.color = normalColor;
                //存入地图
                map[x, y] = new Point(x, y, go);
            }
        }
    }

    /// <summary>
    /// 查找路径并且显示出来
    /// </summary>
    private void FindPathAndShowPath()
    {
        if (startPoint != null && targetPoint != null)
        {
            bool isFind = AStarWrapper.Instance.FindPath(startPoint, targetPoint, map, mapWidth, mapHeight);

            ClearOldPath();

            if (isFind == true)
                ShowPath(startPoint, targetPoint);//显示路径
            else
                Debug.Log("没有可到达的路径");
        }
    }

    private void ShowPath(Point startPoint, Point targetPoint)
    {      

        newPath.Clear();
        Point temp = targetPoint.Parent;
        while (true)
        {
            if (temp == startPoint)
            {
                break;
            }
            if (temp != null)
            {
                newPath.Add(temp);
            }

            temp = temp.Parent;
        }

        StartCoroutine(PaintnewPath());
    }

    IEnumerator PaintnewPath()
    {
        yield return new WaitForEndOfFrame();
        if (newPath.Count > 0)
        {
            for (int i = newPath.Count - 1; i >= 0; i--)
            {
                SetCubeColor(newPath[i].X, newPath[i].Y, pathPosColor);
                yield return new WaitForSeconds(0.05f);
            }
        }

    }

    

    //设置格子颜色
    private void SetCubeColor(int x, int y, Color c)
    {
        map[x, y].gameObject.GetComponent<Renderer>().material.color = c;
    }

    //设置障碍物
    private void SetWall()
    {
        if (wallList != null)
        {
            //障碍点不为空,将所有障碍点设置为正常颜色
            foreach (Point wall in wallList)
            {
                wall.IsWall = false;
                SetCubeColor(wall.X, wall.Y, normalColor);
            }
            //清空障碍点
            wallList.Clear();
        }
        while (true)
        {
            if (wallList.Count >= wallNum)
            {
                break;
            }
            //随机生成障碍点
            Point p = map[Random.Range(0, mapWidth), Random.Range(0, mapHeight)];
            if (wallList.IndexOf(p) == -1)//如果障碍点不存在
            {
                p.IsWall = true;
                SetCubeColor(p.X, p.Y, wallColor);//设置Cube颜色
                wallList.Add(p);//添加障碍点
            }
        }
    }

    
    void SelectStartPoint(Vector3 mousePos)
    {
        Point tmp = PhysicCAst(mousePos);
        if (tmp != null)
        {
            ClearOldStartPoint();

            startPoint = tmp;
            SetCubeColor(startPoint.X, startPoint.Y, startPosColor);

            // 更新路径
            FindPathAndShowPath();
        }
    }

    private void SelectTargetPoint(Vector3 mousePos)
    {
        Point tmp = PhysicCAst(mousePos);
        if (tmp != null)
        {
            ClearOldTargetPoint();
            targetPoint = tmp;
            SetCubeColor(targetPoint.X, targetPoint.Y, targetPosColor);

            // 更新路径
            FindPathAndShowPath();
        }
    }

    /// <summary>
    /// 重置地图
    /// </summary>
    private void RefreshMap()
    {
        ClearOldStartPoint();//清除旧的开始点
        ClearOldTargetPoint();//清除旧的目标点
        ClearOldPath();//清除路径

        oldPath.Clear();//清空旧路径
        newPath.Clear();//清除新路径

        SetWall();
    }

    /// <summary>
    /// 清除旧的开始点
    /// </summary>
    void ClearOldStartPoint()
    {
        if (startPoint != null)
        {
            if (startPoint == targetPoint)
                SetCubeColor(startPoint.X, startPoint.Y, targetPosColor);
            else 
                SetCubeColor(startPoint.X, startPoint.Y, normalColor);
            
            startPoint = null;
        }
    }

    /// <summary>
    /// 清除旧目标点
    /// </summary>
    void ClearOldTargetPoint()
    {
        if (targetPoint != null)
        {
            if (targetPoint == startPoint)
                SetCubeColor(targetPoint.X, targetPoint.Y, startPosColor);
            else
                SetCubeColor(targetPoint.X, targetPoint.Y, normalColor);

            targetPoint = null;
        }
    }

    /// <summary>
    /// 清除旧路径
    /// </summary>
    void ClearOldPath()
    {
        oldPath = newPath;

        foreach (Point pathItem in oldPath)
        {
            SetCubeColor(pathItem.X, pathItem.Y, normalColor);
        }
        // 避免开始点或者目标点在老路径上被清掉,所以重新绘制一次开始点和目标点的颜色
        if (startPoint != null) {
            SetCubeColor(startPoint.X, startPoint.Y, startPosColor);
        }
        if (targetPoint != null) {
            SetCubeColor(targetPoint.X, targetPoint.Y, targetPosColor);
        }
        
    }

    /// <summary>
    /// 射线检测
    /// </summary>
    /// <param name="mousePos">鼠标位置</param>
    /// <returns>返回一个路点</returns>
    Point PhysicCAst(Vector3 mousePos)
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hitInfo;
        if (Physics.Raycast(ray, out hitInfo, 20))
        {
            Transform tmp = hitInfo.collider.transform;
            if (map[(int)tmp.position.x, (int)tmp.position.y].IsWall == false)
            {
                //如果该点不为障碍点,则返回路店
                return map[(int)tmp.position.x, (int)tmp.position.y];
            }
        }
        return null;
    }
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

你賴東東不錯嘛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值