(备忘录)基于AStar的六边形网格寻路算法

3 篇文章 0 订阅
1 篇文章 0 订阅

 

最近项目里有用到,记录一下,方便以后拿来即用

基本思路:

仍然是两个集合,一个保存待寻路的列表,一个保存已经走过or不考虑的,每次遍历拿出最优路径点.

另外,对于六边形网格,这里使用的是简单的基于Y轴(三维Z轴)偏移的二维坐标,因此,在获取当前网格周围六个相邻格子时,对于偶数列,要做相应偏移。

另外有一些可以优化的点(用二叉搜索树代替待寻列表,布兰森汉姆先检查两点是否直接连通,对路径进行平滑处理,缓存等)

下面是思路和测试代码,正式工程禁止

寻路相关HexPathFinder.CS:

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

public class HexPathFinder
{
    /// <summary>
    /// 该类表示寻路路径上的一个路径点
    /// </summary>
    internal class PathPoint
    {
        /// <summary>
        /// constructor
        /// </summary>
        public PathPoint (Hex hex, Hex end,PathPoint prev)
        {
            _hex = hex;
            //get manhattan distance
            var h = Mathf.Abs( hex.X - end.X ) + Mathf.Abs( hex.Y - end.Y ) + Mathf.Abs( hex.Z - end.Z );
            F = G + h / 2;
            _parent = prev;
        }

        public int F { get; private set; } = -1;

        public Hex _hex { get; private set; }
        private PathPoint _parent;

        public int X => _hex.X;
        public int Y => _hex.Y;

        /// <summary>
        /// 这里可以不用parent,因为每个Parent没有保存其上一级Parent的引用
        /// </summary>
        public PathPoint Parent => _parent;

        public int G => GetG();

        public int GetG ()
        {
            var step = 0;
            var temp = Parent;
            while (temp != null)
            {
                temp = temp.Parent;
                step++;
            }
            return step;
        }
    }

    /// <summary>
    /// 基于AStar的寻路算法,该函数接收一个起点,一个终点,返回由起点至终点按顺序排列的坐标集合,如果寻路失败则返回空列表
    /// </summary>
    public List<(int x, int y)> FindPath (Hex start, Hex end )//#todo如果无法到终点是否达到最近路径点?
    {
        //#优化 Bresenham检查两点是否可以直接连通,可以直接返回
        //路径点集合
        var pointDic = new Dictionary<int, PathPoint>();//检查引用集合
        var openLst = new List<PathPoint>();//待寻列表
        var closeDic = new Dictionary<int, Hex>();//走过的
        var pathLst = new List<(int, int)>();//结果

        var startPoint = new PathPoint( start,end,null );
        openLst.Add( startPoint );
        while (openLst.Count != 0)
        {
            var curr = openLst[0];
            pathLst.Add( curr._hex.Coordinate );
            if (openLst.Contains( curr ))
            {
                openLst.Remove( curr );
                pointDic.Remove( curr._hex.ID );//如果有就删掉,没有也没关系
            }
            //for test
            Entrance.Ins.GetHex( curr.X, curr.Y ).HighLight();

            if (curr._hex == end)//终点
                break;
            else
            {
                if (!closeDic.ContainsKey( curr._hex.ID ))
                    closeDic.Add( curr._hex.ID, curr._hex );
            }

            PathPoint nearPoint = null;
            Hex nearHex = null;
            //拿周围六格
            //这里使用的是简单的偏移二维坐标,具体需要结合实际地图
            for (int i = 0; i < 6; i++)
            {
                var isEven = curr.Y % 2 == 0;
                //六边形网格的偏移量
                var xOffset = 0;
                var yOffset = 0;

                switch (i)
                {
                    case 0://右上
                        xOffset = isEven ? 0 : 1;
                        yOffset = 1;
                        break;
                    case 1://正右
                        xOffset = 1;
                        break;
                    case 2://右下
                        xOffset = isEven ? 0 : 1;
                        yOffset = 1;
                        break;
                    case 3://左上
                        xOffset = isEven ? -1 : 0;
                        yOffset = 1;
                        break;
                    case 4://正左
                        xOffset = -1;
                        break;
                    case 5://左下
                        xOffset = isEven ? -1 : 0;
                        yOffset = -1;
                        break;
                }
                nearHex = Entrance.Ins.GetHex( curr.X + xOffset, curr.Y + yOffset );
                if (nearHex is null || !nearHex.IsPassable)//边界或不可通行
                    continue;

                if (closeDic.ContainsKey( nearHex.ID ))//走过了
                    continue;

                if (!pointDic.ContainsKey( nearHex.ID ))
                {
                    nearPoint = new PathPoint( nearHex, end, curr );
                    pointDic.Add( nearPoint._hex.ID, nearPoint );
                }
                else
                    nearPoint = pointDic[nearHex.ID];

                if (!openLst.Contains( nearPoint ))
                    openLst.Add( nearPoint );
            }

            //#优化 改成BST效率更高
            //保证每次都拿到最优路径点
            openLst.Sort( (x,y) =>
             {
                 return x.F <= y.F ? -1 : 1;
             } );
        }

        if (openLst.Count == 0)//没有可走的路径
        {
            pathLst.Clear();
            return pathLst;
        }
        return pathLst;
    }

    private static HexPathFinder _ins;

    private static HexPathFinder GetIns ()
    {
        if (_ins is null)
            _ins = new HexPathFinder();

        return _ins;
    }

    /// <summary>
    /// 单例
    /// </summary>
    public static HexPathFinder Ins => _ins is null ? GetIns() : _ins;
}

启动场景挂载加GameObject挂Entrance脚本以测试:

Entrance.cs:

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

public class Entrance : MonoBehaviour
{
    public static Entrance Ins = null;

    //[SerializeField] private Hex[] _hexArr;
    [SerializeField] GameObject _hexRoot;

    /// <summary>
    /// 六边形网格集合
    /// </summary>
    private Dictionary<int, Hex> _hexDic;
    private HashSet<(int X,int Y)> _hexSet;
    private List<List<Hex>> _hexList;

    private void Awake ()
    {
        _hexDic = new Dictionary<int, Hex>();
        _hexSet = new HashSet<(int,int)>();
        _hexList = new List<List<Hex>>();
        Ins = this;
    }

    private void Start ()
    {
        //测试代码。。。
        var hexArr = _hexRoot.GetComponentsInChildren<Hex>();
        //Init
        foreach (var hex in hexArr)
        {
            if (_hexDic.ContainsKey( hex.ID ))
            {
                Debug.Log($"已经存在ID:{hex.ID}");
                continue;
            }

            if (_hexSet.Contains( hex.Coordinate ))
            {
                Debug.Log($"已经存在坐标:{hex.Coordinate}");
                continue;
            }

            _hexDic.Add( hex.ID, hex );
            _hexSet.Add( hex.Coordinate );
        }

        Test();
    }

    private void Test ()
    {
        //test
        var start = GetHex( 0,0);
        var end = GetHex( 5,2);
        if (start is null || end is null)
        {
            Debug.Log("<color=red>路径点不存在</color>");
            return;
        }
        var lst = HexPathFinder.Ins.FindPath( start, end );

        foreach (var item in lst)
        {
            GetHex( item.x, item.y ).HighLight();
            Debug.Log( $"{item.x},{item.y}" );
        }
    }

    private void Update ()
    {
        if (Input.GetKeyDown( KeyCode.Delete ))
            Test();
    }
}

网格类型Hex.cs:

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

public class Hex : MonoBehaviour
{
    //ID
    public int ID => _id < 0 ? GetID() : _id;

    /// <summary>
    /// 坐标
    /// </summary>
    public (int X, int Y) Coordinate => (_x, _y);

    public int X => _x;
    public int Y => _y;

    /// <summary>
    /// 立方体Z轴坐标
    /// </summary>
    public int Z => CubeCoordinate.z;//对于六边形网格,在计算两点距离时需要额外的一个轴

    /// <summary>
    /// 立方体坐标
    /// </summary>
    public Vector3Int CubeCoordinate => GetCubeCoordinate();

    /// <summary>
    /// 生成立方体坐标
    /// </summary>
    private Vector3Int GetCubeCoordinate ()
    {
        if (_cubeCoordinate == Vector3Int.one)
        {
            var z = _x + _y;
            z *= -1;
            _cubeCoordinate = new Vector3Int( _x, _y, z );
        }
        return _cubeCoordinate;
    }

    public static bool operator + (Hex thisHex, Hex other) => thisHex.Coordinate == other.Coordinate;

    /// <summary>
    /// 是否可通行,true代表可以
    /// </summary>
    public bool IsPassable => _passable;

    private int GetID ()
    {
        _id = IDPool.ID;
        return _id;
    }

    [SerializeField] private int _x;
    [SerializeField] private int _y;
    [SerializeField] private int _id = int.MinValue;
    [SerializeField] private bool _passable = true;

    private SpriteRenderer _spRender;

    public void HighLight ()
    {
        _text.color = Color.green;
    }

    /// <summary>
    /// 立方体坐标
    /// </summary>
    private Vector3Int _cubeCoordinate = Vector3Int.one;

    private Text _text;

    private void OnEnable ()
    {
        _text = GetComponentInChildren<Text>();
        _text.text = $"{_x},{_y},{Z}";//set text
        _text.rectTransform.sizeDelta = new Vector2( 120f, 80f );

        _spRender = GetComponentInChildren<SpriteRenderer>();
        _text.color = _passable ? Color.white : Color.red;

        gameObject.name = $"{_x}_{_y}_{Z}";
    }
}

/// <summary>
/// ID池
/// </summary>
public class IDPool
{
    private static int _id = int.MaxValue;

    public static int ID => _id--;
}

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 六边形A*算法是一种基于六边形网格结构的路径规划算法。与常规的A*算法相比,六边形A*算法更适用于六边形地图的路径规划问题。 六边形A*算法的基本原理与常规的A*算法相似,都是通过优先级队列和启发式函数来搜索最短路径。但是,六边形A*算法在计算启发式函数时与常规的A*算法有所不同。 在六边形A*算法中,常规的A*算法中的距离代价函数需要进行修改。因为六边形的相邻关系与常规的网格结构不同,所以计算六边形之间的距离需要考虑六边形的形状和位置关系。 此外,六边形A*算法还需要特殊处理六边形的邻居关系,以确保在路径搜索过程中不会出现越过实际不可通过的区域。 六边形A*算法的主要步骤如下: 1. 初始化起点和终点,并将起点放入优先级队列中。同时将起点标记为已访问。 2. 从优先级队列中取出最低优先级的节点,并将其标记为已访问。 3. 对当前节点的所有邻居进行遍历,如果邻居未被访问过,则计算邻居的总代价,并将邻居放入优先级队列中。 4. 如果终点被访问到,则搜索结束,找到了最短路径。 5. 如果优先级队列为空且没有找到终点,则认为不存在可行路径。 六边形A*算法在路径规划问题中具有一定的优势,特别是在六边形地图中。它能够高效地搜索最短路径,同时避免越过不可通过的区域。然而,六边形A*算法的实现和优化还需要考虑到具体的应用场景和问题特点。 ### 回答2: 六边形 A* 算法是一种在六边形地图上进行路径搜索的算法。该算法是对经典的 A* 算法进行扩展,以适应六边形地图的特殊性。 六边形地图是一种类似蜂窝状结构的地图,每个六边形都有六个邻居。相比于传统的方块地图,六边形地图更加符合实际地理形态。而六边形 A* 算法就是为了应对这种六边形地图的特殊情况而设计的。 六边形 A* 算法的步骤与经典 A* 算法类似,首先需要确定起点和终点。然后,通过估计启发函数(heuristic function)计算每个节点的代价值,并选择代价最小的节点进行扩展。 与传统的 A* 算法不同的是,对于一个六边形节点,它有六个邻居,即六个可能的扩展方向。为了确保路径的连续性,六边形 A* 算法中使用了特殊的邻居选取策略。具体而言,当选择一个节点的邻居进行扩展时,优先考虑相对路径最短的邻居。 为了实现这一策略,可以使用一个特殊的六边形地图数据结构,对每个节点进行编码并记录其邻居关系。在搜索过程中,通过合理选择邻居节点,可以有效地减少搜索空间,提高算法的效率。 总而言之,六边形 A* 算法是一种适用于六边形地图的路径搜索算法。通过合理选择邻居节点并使用特殊的启发函数,该算法能够快速、准确地找到起点到终点的最短路径。 ### 回答3: 六边形 A* 算法是一种寻路算法,常用于处理六边形网格地图中的路径搜索问题。它是基于 A* 算法的扩展,通过改进 A* 算法来适应六边形网格的特殊结构。 六边形网格地图由一系列紧密相连的六边形组成,而不是传统的方形格子。在六边形 A* 算法中,我们需要定义合适的邻居节点关系以及估价函数。 首先,我们定义每个六边形的六个邻居节点,即相邻的六个六边形格子。这样我们可以通过遍历这些邻居节点来探索路径。 其次,估价函数的计算需要根据六边形网格的特点进行调整。传统的 Manhattan 距离或欧几里得距离并不适用于六边形。一种常用的估价函数是六边形的中心点到目标点的距离。 在算法运行过程中,我们使用优先队列来存储待访问的节点,并按照 F 值的大小对节点进行排序。F 值是由 G 值和 H 值相加得到的,其中 G 值表示起始节点到当前节点的实际代价,H 值表示当前节点到目标节点的估计代价。 在每次迭代中,我们从优先队列中弹出 F 值最小的节点,即代价最低的节点。然后计算该节点的邻居节点的 F 值,并将它们加入优先队列中。为了记录路径,我们还需要维护一个父节点指针指向每个节点的前驱节点。 最后,当目标节点被加入优先队列并弹出时,我们就找到了一条最短路径。通过回溯父节点指针,我们可以从目标节点一直追溯到起始节点,得到完整的路径。 六边形 A* 算法通过调整邻居节点关系和估价函数,能够有效地在六边形网格地图中寻找最短路径。它在游戏设计、机器人导航等领域都有广泛的应用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值