« 卡牌构筑类桌游核心规则之七 | 返回首页

一个简单的 A star 寻路算法实现

我需要一个接口简单的寻路模块,所以今天写了一个 。其实之前也写过很多版本,在我上传代码时就发现我自己的 github 账号下早有同名仓库。不过,之前的版本的接口设计不太满意,直接删掉了,用这次的新版本复用老的仓库名字。

我希望达到的目标是,C 接口简单易用,且和地图本身的数据结构无关,只提供寻路功能。这样容易拓展到不同应用场景。

数据结构简单,内存开销固定,在算法执行过程中不额外分配内存。这可以方便的在多线程环境运行。

我不需要处理特别复杂和规模巨大的地图,那种场景应该额外做一些预处理。但在起点和终点的路线结果不长时(即使在大规模地图上),应该有较好的性能。

原始的 A star 算法实现最为简单,在大多数情况下有不错的表现,所以我选择了它。我知道算法可以有很多改进方法,但我觉得代码简单最为重要。

通常 A star 算法依赖一个优先队列,但我没有选择使用诸如平衡二叉树等复杂结构来实现它,而使用了最简单的单向链表。因为这样可以轻松的把全部数据全部塞在一块平坦内存中。

基础数据结构是一个用数组实现的闭散列 hash 表,使用者来决定使用多大的数组,通常使用预期路径长度的平方大小会比较合适。为了减少每次寻路的初始化成本,使用了一个 version 值表示每个 slot 的初始状态,每次调用寻路,都会把 version 递增( O(1) 操作),这样就可以让整个 hash 表的所有 slot 复位。

寻路过程中每个尝试的节点都会加入 hash 表中,在 hash 表使用率超过一半就会中止算法,防止性能恶化。但接口在这种情况下依然会返回已经找到的离目标最近的中途点。

在不复杂的大规模地图上,通常可以通过多次调用找到完整路径。但依然建议针对大地图做更高层次的预处理。在 Youtube 上有一个 Rimworld 作者讲解 Rimworld 中区域分割系统的视频值得一看,搜索 "RimWorld Technology - Region System" 可以找到。

A star 工作中的待展开节点集是用单向链表的形式串起了 hash 表中的 slot ,而没有使用额外的优先队列结构。虽然单向链表的插入操作是 O(n) 的,但我猜想在大部分场景中,这个 n 并不算大。尤其是估价函数理想工作状态下(朝着目标直线移动),新插入的节点都是在链表一端附近的。这个猜想需要足够多的测试数据验证。

为了调试算法工作中的内部状态,模块提供了一个函数可以输出整个 hash 表的当前状态图(仅限于每个 slot 的 gscore ,即离起点的路程)。合理使用这张图,可以把算法的内部状态可视化表现。test 中使用 ascii 字符展示,但用灰度图输出图像效果会更好。


代码刚写好,尚未充分测试。但我觉得接口设计还算通用,应该会有人愿意使用。期待有更多人使用而让代码的质量提升。

Comments

我几年前写过一个,通过将“获取邻居”的操作做成回调函数来实现接口简单,代码在: https://gist.github.com/starwing/2522ae70bf109137db71f1e3282ee313 使用方式: local path_find = require "pathfinding" local function neighbors(add, x, y, dist) add(x+1, y, hint, 1) end local r = path_find(sx, sy, tx, ty, neighbors)
我来看看
别人笑我太疯癫,,我笑他人看不穿 现在慢慢体会到这句话的精神了 佩服佩服,,(不是阴阳,是真的由衷佩服) 复杂的引擎框架自己造,,简单的事情像A星这种代码满天飞的代码也自己造 可能这就是不忘初心吧 俺们这些为生活奔波的牛牛马马却还总不屑看人家各种造轮子,,,,人家早就是另一个境界辣
NB 苦RecastNavigation 久矣,希望云风大神慢慢的扩展出更牛逼的功能。 距离目标还差9999次commit。
云哥这种状态真好,悠然自得
一个优秀的设计师,总是愿意一遍又一遍的造轮子。最后恨不得用一个新轮子体现一门哲学。这是一种自我修炼的过程。
刚好这两天也在找Astar实现,不过我没云风这么牛,我可耻的问了AI给了一份实现。。

Post a comment

非这个主题相关的留言请到:留言本