【Unity】六边形、菱形的AStar(A*)算法及死路优化

本文介绍了如何在六边形和菱形网格上实现A*算法,包括坐标转换和邻接节点查找。重点讨论了处理死路优化的方法,即在目标点不可达时,将障碍点加入openList并设置高移动消耗,确保玩家始终有行动路径。

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

前言

上一篇[一篇文章搞定AStar(A*)算法]​​​​​​写了一下四边形的A*算法。但是在游戏开发中,战棋类游戏往往并不会采用四边形网格而是采用六边形或菱形网格,一方面六边形或菱形网格看上去更具美感,另一方面六边形或菱形网格可玩性比四边形网格更高。那么本篇再来写一写六边形、菱形网格的A*算法是如何实现的。

六边形A*算法

在开始实现发算法之前,要先弄明白六边形网格的地图应该如何去生成,因为算法也是基于地图的,没有一个正确的地图就无法对算法有一个正确的理解。

在四边形的A*算法中,把x,y分别加1减1就获取到了某点周围的4个邻接节点,代码是这样的:

FindDestNode(currX, currY + 1, xTo, yTo, currNode, openList);
FindDestNode(currX, currY - 1, xTo, yTo, currNode, openList);
FindDestNode(currX - 1, currY, xTo, yTo, currNode, openList);
FindDestNode(currX + 1, currY, xTo, yTo, currNode, openList);

但六边形有六条边,每个格子就有6个邻接节点,无论怎样去安排这六个点的坐标,都不能通过把x,y加1减1的方式全部都获取到。所以要生成一张六边形网格,首先就是要解决六边形网格的坐标问题。

不妨找一张六边形网格观察一下,会发现每行每列的格子都是交错排列的,如果逻辑坐标也能这样交错排列就完美的与网格对应上了。

所以解决这个问题的方法就是把x坐标扩大2倍+y坐标%2,形成奇数行x坐标就进行奇数排列,偶数行x坐标就进行偶排列,y坐标不变。

return new Vector2Int(2 * x + y % 2, y);

确定了格子的坐标,获取邻接节点就很容易了

FindDestNode(currX + 1, currY + 1, xTo, yTo, currNode, openList);//右上
FindDestNode(currX - 1, currY + 1, xTo, yTo, currNode, openList);//左上
FindDestNode(currX + 1, currY - 1, xTo, yTo, currNode, openList);//右下
FindDestNode(currX - 1, currY - 1, xTo, yTo, currNode, openList);//左下
FindDestNode(currX + 2, currY, xTo, yTo, currNode, openList);//右
FindDestNode(currX - 2, currY, xTo, yTo, currNode, openList);//左

好了,现在一切准备就绪,来定几个障碍点跑一下试试。

完美!

菱形A*算法

地图坐标的处理和六边形A*算法的处理是完全一样的。

死路优化

有没有想过这两种情景:

  1. 玩家在地图上点击了一个点,但这个点却是一个障碍点
  2. 玩家在地图上点击了一个点,但很遗憾,这个点四面楚歌,并没有通往这个点的路线

以上这两种情况,玩家操控的角色该如何行动?

显然,在这两个前提下,A*算法不会得出结果,因为在之前的代码里,对于不可行走的格子直接就跳过检测了。没有路线,玩家角色自然就会巍然不动。

我的前上司曾对我说过,一个具有良好体验的游戏,应该做到“逢点击,必响应”,只是因为点击到了一个障碍点就不做出反馈,这实属是犯了大忌啊!

那么苦哈哈的程序员必须要优化:即使点击到了障碍甚至没有通路,也要让玩家角色移动到距离障碍点最近的点。

聪明伶俐的你一定已经发现了,之所以没有通路是因为目标点不可达,所以把目标点替换到它附近的可以到达的点不就行了吗?

既然我已经写到这了,那么这种方法肯定就是不可以的!

确实,当目标点不可达时,从目标点开始广度优先遍历,直到找到一个不是障碍的点来代替原来的目标点这种方法看起来可行,但是,如果代替点也是个空心的呢?它的周围也被障碍团团围住,那不还是死路一条?这样想下去就没完没了了,可能这个广度优先的替代法会执行到遍历完整个地图为止。

其实回过头重新仔仔细细的看一遍算法就会发现,之所以得不出路线的根本原因就是因为那些障碍点直接被跳过了,根本就没有机会被放进openList中等待检测,那么只要打破这个规则:即使目标点不可达,也要把它放进openList中去检测,不就能得到路线了吗?

回想我们的Dijkstra大师对我们的教导:最短路径的求解是不断寻找移动消耗最低的点直到终点。

看到【移动消耗】这个词,你是否已经有了灵感?

没错,这个问题的终极解决办法就是:

  1. 我们要打破铁律,我们要为底层劳苦大众发声,障碍点低人一等吗?障碍点也有进入openList的资格
  2. 但障碍点终归是障碍点,在openList里它终归是个异类,即使允许它进入openList,它也是最后一个被检测的,只有实在没有其他选择的时候,A*才会垂怜它一下

把障碍点也放进openList,但它的移动消耗G是无穷大!这样一来,在没有任何通路的情况下,A*会回头来检测从这些障碍点身上通过是否能到达终点,无论如何都会给玩家一条路走。

private void FindDestNode(int x, int y, int xTo, int yTo, Node currNode, List<Node> openList)
{
    if (x >= 0 && x / 2 < m_MapWidth && y >= 0 && y < m_MapHeight)
    {
        int index = x / 2 + y * m_MapWidth + 1;
        int toIndex = xTo / 2 + yTo * m_MapWidth + 1;
        int mulity = 1;

        if (m_MapData[index - 1] == -1)
        {
            mulity = 1000;//如果是障碍点,把它的移动消耗G扩大一千倍
        }

        Node temp = openList.Find(obj => obj.index == index);

        if (temp == null)
        {
            temp = new Node(index, currNode, (currNode.g + 1) * mulity, GetDistance(index, toIndex));
            openList.Add(temp);
        }
        else if (temp.isOpen && currNode.g + 1 + temp.h < temp.f)
        {
            temp.g = (currNode.g + 1) * mulity;
            temp.f = temp.g + temp.h;
            temp.parentNode = currNode;
        }
    }
}

但路是有了,那回溯的时候不就把障碍点以及后面那些进不去的地方也算进来了吗?其实这个就更简单了,回溯的时候一但发现障碍点就把该障碍点以及前面的所有节点全部干掉就可以了。

while (currNode != null)
{
    if (m_MapData[currNode.index - 1] != -1)
    {
        m_QueuePath.Enqueue(currNode.index);
        currNode = currNode.parent;
    }
    else if (m_QueuePath.Count > 0)//遇到障碍点则把之前入队的点全部出队
    {
        m_QueuePath.Dequeue();
    }
    else
    {
        currNode = currNode.parent;
    }
}

这样一来即使玩家点击到了障碍物也有路可走,真正做到了“逢点击,必响应”。

结语

这几天A*也研究的差不多了,但是鄙人水平实在有限,代码效率并不是很高,只能期待以后再进步吧,祝大家新的一年不出bug,出了bug也能找到人背锅!

工程地址:

GitHub - WuWu03/AStar: A*算法Untiy工程A*算法Untiy工程. Contribute to WuWu03/AStar development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/WuWu03/AStar

 个人博客

六边形、菱形的AStar(A*)算法及死路优化 | 幻想乡の红魔馆 (ming-e.space)icon-default.png?t=N7T8https://ming-e.space/post/44f2b67e/

Unity A*寻路算法是一种常用的路径规划算法,它可以帮助我们快速高效地找到两点之间最短路径。A*寻路算法的核心思想是在搜索过程中综合利用已知信息和预测信息,以高效地选择下一个最佳节点。 在Unity中,可以使用A*相关的插件或者自己实现A*算法来实现寻路功能。首先,需要生成一个网格或者地图,将地图划分成一个个小格子。每个格子表示一个节点,节点之间的连接关系可以通过建立链接或者使用权重来表示。 接下来,需要实现A*算法的估价函数、开启列表和关闭列表等数据结构。估价函数用来评估每个节点距离目标的预测代价,开启列表用来保存待搜索的节点,关闭列表用来保存已经搜索过的节点。 然后,从起点开始,将其加入到开启列表中,并开始循环搜索。在每一次循环中,选择开启列表中估价函数最小的节点作为当前节点,然后将其从开启列表中删除,并将其加入到关闭列表中。接着,对当前节点的邻居节点进行处理,如果邻居节点已经在关闭列表中,则忽略;如果邻居节点不在开启列表中,则将其加入开启列表,并更新其父节点和估价函数;如果邻居节点已经在开启列表中,就更新其父节点和估价函数,但是不改变它在开启列表中的位置。 当目标节点被加入到关闭列表中时,表示找到了一条最短路径。此时,可以从目标节点开始,依次访问它们的父节点,直到回到起点,这样就可以得到具体的路径。 最后,根据节点的位置信息,将路径可视化展示出来,使人们可以看到实际的路径。 总体来说,Unity A*寻路算法是一种十分高效和常用的路径规划算法,通过合理利用已知信息和预测信息,可以帮助我们找到两点之间最短路径,实现游戏中角色的自动导航等功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值