#路径查找 #迪杰斯特拉 #A*算法 #视野 #游戏开发 #图形

bracket-pathfinding

路径查找和视野工具。A*和迪杰斯特拉。Bracket-lib系列的一部分。

8个版本

0.8.7 2022年10月4日
0.8.4 2021年2月15日
0.8.1 2020年4月29日
0.7.0 2020年2月22日
0.1.0 2020年2月21日

#592 in 游戏开发

Download history 858/week @ 2024-03-13 1302/week @ 2024-03-20 1174/week @ 2024-03-27 1458/week @ 2024-04-03 1235/week @ 2024-04-10 1215/week @ 2024-04-17 1236/week @ 2024-04-24 1056/week @ 2024-05-01 1139/week @ 2024-05-08 1100/week @ 2024-05-15 1290/week @ 2024-05-22 1306/week @ 2024-05-29 1181/week @ 2024-06-05 871/week @ 2024-06-12 999/week @ 2024-06-19 1029/week @ 2024-06-26

4,280 每月下载量
9crates (4 直接) 中使用

MIT 许可证

150KB
3K SLoC

bracket-pathfinding

此crate是整体bracket-lib系统的一部分,并且(与bracket-algorithm-traits一起)提供路径查找功能。支持A*(A*)和迪杰斯特拉。它还提供了视野(FOV)功能。

特性行为实现

至少,您需要为您地图定义get_available_exitsget_pathing_distance(从BaseMap特性行为)。对于视野,需要is_opaque。这些来自bracket-algorithm-traits crate,但作为prelude的一部分公开,以方便使用。这些还可以通过实现Algorithm2D(来自同一crate)来受益。这些用于为库提供您的地图格式的接口:库非常努力地不去对您如何存储地图数据提出意见。

大多数Algorithm2D可以通过只提供维度来推导,如果您对bracket-lib提供的简单对齐满意。

impl Algorithm2D for Map {
    fn dimensions(&self) -> Point {
        Point::new(MAP_WIDTH, MAP_HEIGHT)
    }
}

对于视野,您需要指明一个瓦片是否会阻挡视线。例如

impl BaseMap for Map {
    fn is_opaque(&self, idx: usize) -> bool {
        self.tiles[idx as usize] == '#' // Change this to your map definition!
    }
}

迪杰斯特拉和A*需要知道从一个瓦片出发的有效出口,以及移动到该瓦片的“成本”(大多数情况下您可以使用1.0)。例如

impl BaseMap for Map {
    fn is_opaque(&self, idx: usize) -> bool {
        self.tiles[idx as usize] == '#'
    }

    fn get_available_exits(&self, idx: usize) -> SmallVec<[(usize, f32); 10]> {
        let mut exits = SmallVec::new();
        let location = self.index_to_point2d(idx);

        if let Some(idx) = self.valid_exit(location, Point::new(-1, 0)) {
            exits.push((idx, 1.0))
        }
        if let Some(idx) = self.valid_exit(location, Point::new(1, 0)) {
            exits.push((idx, 1.0))
        }
        if let Some(idx) = self.valid_exit(location, Point::new(0, -1)) {
            exits.push((idx, 1.0))
        }
        if let Some(idx) = self.valid_exit(location, Point::new(0, 1)) {
            exits.push((idx, 1.0))
        }

        exits
    }
}

A*还需要知道给定瓦片的目标距离。如果您不提供有用的数字,这将成为一个非常低效的搜索。您可以通过使用毕达哥拉斯、曼哈顿或其他算法来指定距离启发式函数来改变搜索的行为。例如

impl BaseMap for Map {
    fn get_pathing_distance(&self, idx1: usize, idx2: usize) -> f32 {
        DistanceAlg::Pythagoras.distance2d(
            self.index_to_point2d(idx1),
            self.index_to_point2d(idx2)
        )
    }
}

A* (A*) 路径查找

Bracket-lib包括一个高性能的A*系统。它使用二叉堆来优化打开/关闭存储。默认情况下,它会在65,536次迭代后取消搜索。

实际上执行A*搜索非常简单

let path = a_star_search(
        map.point2d_to_index(START_POINT),
        map.point2d_to_index(END_POINT),
        &map
    );
    if path.success {
        // Do something with it! path.steps has the whole path.
    }

示例astar演示了这一点。

迪杰斯特拉映射

Bracket-lib 还包含迪杰斯特拉图,可以包含你想要的任何数量的搜索目标。参见 迪杰斯特拉图的无穷力量 了解你可以用它做什么。

要生成迪杰斯特拉图,你需要一个目标瓦片索引向量。然后你可以创建这张地图

let mut search_targets : Vec<usize> = Vec::new();
search_targets.push(map.point2d_to_index(START_POINT));
search_targets.push(map.point2d_to_index(END_POINT));
let flow_map = DijkstraMap::new(MAP_WIDTH, MAP_HEIGHT, &search_targets, &map, 1024.0);

一旦你有了地图,你可以在 flow_map.map 访问单个距离 - 或者你可以使用各种辅助函数,如 find_highest_existfind_lowest_exit 来帮助路径查找。

示例 dijkstra 展示了这一点。

视野(目前仅限于2D)

如果你为你的 BaseMap 特性定义了 is_opaque,获取所有可见瓦片的集合就很容易了

let fov = field_of_view_set(START_POINT, 6, &map);

你可以在示例 fov 中看到这一点。

功能标志

如果你启用了 threaded 功能,一些迪杰斯特拉函数将使用多线程算法。

示例

有三个示例(忽略 common.rs - 它是共享代码)

  • astar (运行 cargo run --example astar),演示在随机地图上执行 A-Star 路径搜索。
  • dijkstra (运行 cargo run --example dijkstra),演示对两个目标进行迪杰斯特拉映射。
  • fov (运行 cargo run --example fov),演示视野生成。

这些使用 crossterm 将渲染到你的终端。

依赖项

~2.5MB
~52K SLoC