17个版本 (7个破坏性版本)
0.8.0 | 2024年7月20日 |
---|---|
0.6.0 | 2024年4月13日 |
0.5.1 | 2024年3月16日 |
0.3.4 | 2023年12月18日 |
0.3.3 | 2023年11月18日 |
#56 在 游戏开发
每月下载量129次
110KB
2.5K SLoC
bevy_voxel_world
什么是 bevy_voxel_world
此插件使得在Bevy中生成和修改体素地形变得简单。 bevy_voxel_world
处理多线程网格生成、块创建/销毁、纹理映射,并提供一个易于使用的API,可以从任何系统访问。
$ cargo run -r --example noise_terrain
世界可以通过两种主要方式控制:通过地形查找函数,以及通过 set_voxel
和 get_voxel
函数直接控制。世界有两层体素信息,一层是通过地形查找函数生成的程序化信息,另一层是通过 set_voxel
控制的,并持久化存储在 HashMap
中。持久化层始终覆盖程序化层。这样,世界可以无限大,但我们只需要存储有意改变的体素信息。在当前实现中,程序化层被缓存在已创建的块中,因此如果创建距离很大,仍然可能会使用大量内存。
有关如何使用地形查找函数的示例,请参阅 此示例。
基本设置
为您的世界创建一个配置结构体
#[derive(Resource, Clone, Default)]
struct MyWorld;
impl VoxelWorldConfig for MyWorld {
// All options have defaults, so you only need to add the ones you want to modify.
// For a full list, see src/configuration.rs
fn spawning_distance(&self) -> u32 {
25
}
}
然后添加带有您的配置的插件
.add_plugins(VoxelWorldPlugin::with_config(MyWorld))
配置结构体做两件事
- 它提供配置值
- 其类型还作为世界实例标识符。这意味着只要每个实例都有唯一的配置结构体,您就可以通过添加多个插件的多个实例来创建多个世界。 这里是一个使用不同材料创建两个世界的示例
访问世界
要在系统中访问体素世界实例,您可以使用 VoxelWorld
系统参数。 VoxelWorld
接收一个类型参数,这是您要访问的世界配置结构体。
可以使用 set_voxel
和 get_voxel
访问函数来操纵世界中的体素数据。
fn my_system(mut voxel_world: VoxelWorld<MyWorld>) {
voxel_world.set_voxel(IVec3 { ... }, WorldVoxel::Solid(0));
}
这将更新持久 HashMap
中给定位置的体素值,并导致 bevy_voxel_world
将受影响的块排队进行重新网格化。
体素通过其在世界中的XYZ坐标进行索引,由一个IVec3
指定。体素类型由WorldVoxel
类型指定。一个体素可以是Unset
、Air
或Solid
。
体素材料
Solid
体素包含一个u8
材料类型值。因此,最多支持256种材料类型。通过映射回调,材料类型可以轻松地映射到2D纹理数组中的索引。
在配置中可以提供自定义的数组纹理。它应该是一个大小为W x (W * n)
的图像,其中n
是索引数。因此,4个16x16像素纹理的数组大小将是16x64像素。索引数在第二个参数中指定。
然后,为了映射哪些索引属于哪种材料类型,你可以提供一个texture_index_mapper
回调
impl VoxelWorldConfig for MyWorld {
fn texture_index_mapper(&self) -> Arc<dyn Fn(u8) -> [u32; 3] + Send + Sync> {
Arc::new(|vox_mat: u8| match vox_mat {
SNOWY_BRICK => [0, 1, 2],
FULL_BRICK => [2, 2, 2],
GRASS | _ => [3, 3, 3],
})
}
fn voxel_texture(&self) -> Option<(String, u32)> {
Some(("example_voxel_texture.png".into(), 4)) // Array texture with 4 indexes
}
}
texture_index_mapper
回调会提供一个材料类型,并应该返回一个包含三个值的数组。这些值表示哪个纹理索引映射到体素的[top, sides, bottom]
。
请参见纹理示例以了解此示例的运行实例。
自定义着色器支持
如果您需要进一步自定义材料,您可以在添加插件时使用.with_material(MyCustomVoxelMaterial)
,以注册您自己的Bevy材料。这允许您使用自己的自定义着色器与bevy_voxel_world
一起使用。有关更多详细信息,请参阅此示例。
光线投射
要从屏幕上的像素位置(例如鼠标位置)在世界中找到一个体素位置,您可以对体素世界进行光线投射。
fn do_something_with_mouse_voxel_pos(
voxel_world: VoxelWorld<MyWorld>,
camera_info: Query<(&Camera, &GlobalTransform), With<VoxelWorldCamera<MyWorld>>>,
mut cursor_evr: EventReader<CursorMoved>,
) {
for ev in cursor_evr.read() {
// Get a ray from the cursor position into the world
let (camera, cam_gtf) = camera_info.single();
let Some(ray) = camera.viewport_to_world(cam_gtf, ev.position) else {
return;
};
if let Some(result) = voxel_world.raycast(ray, &|(_pos, _vox)| true) {
// result.position will be the world location of the voxel as a Vec3
// To get the empty location next to the voxel in the direction of the surface where the ray intersected you can use result.normal:
// let empty_pos = result.position + result.normal;
}
}
}
请参见此光线投射完整示例以获取更多详细信息。
注意事项
bevy_voxel_world
最初是我正在开发的一款游戏的内部部分,但我认为它可以用作一个独立的插件,对我自己和可能对其他人都有用,所以我决定将其拆分出来,并作为crate公开。
在其当前状态下,还有一些硬编码的假设适用于我的用例,但可能不适合每个人。随着时间的推移,目标是使其泛化,并使bevy_voxel_world
更具可配置性。还有许多潜在的性能优化,我尚未优先考虑。
目前仅支持“方块”,类似于Minecraft的体素,且不支持“半块”。网格处理由block-mesh-rs处理,仅使用“简单”算法(即没有贪婪网格)。
欢迎反馈、问题和拉取请求!
Bevy兼容性
bevy | bevy_voxel_world |
---|---|
0.14 | ^0.8.0 |
0.13 | 0.4.0 - 0.8.0 |
0.12 | 0.3.6 |
0.11 | 0.2.2 |
依赖项
~23MB
~428K SLoC