Rust 3D交互插件bevy_mod_picking的使用,Bevy引擎的高效鼠标拾取与对象选择功能库
Rust 3D交互插件bevy_mod_picking的使用,Bevy引擎的高效鼠标拾取与对象选择功能库
这是一个为Bevy引擎添加拾取功能的灵活插件集合。它允许你在任何实体上添加事件监听器,支持鼠标、触摸甚至游戏手柄输入。
主要特点
- 轻量级:只编译你需要的部分
- 表达性强:使用事件监听器组件
On::<Pointer<Click>>::run(my_system)
- 输入无关:可以用鼠标、笔、触摸或自定义系统控制指针
- 模块化后端:混合使用如
rapier
、egui
、bevy_ui
等后端,或编写自己的后端
示例代码
以下是内容中提供的示例:
commands.spawn((
PbrBundle { /* ... */ },
// 当此实体或其子实体被交互时运行这些回调
On::<Pointer<Move>>::run(change_hue_with_vertical_move),
// 拖动时旋转实体:
On::<Pointer<Drag>>::target_component_mut::<Transform>(|drag, transform| {
transform.rotate_local_y(drag.delta.x / 50.0)
}),
// 点击时销毁实体:
On::<Pointer<Click>>::target_commands_mut(|_click, target_commands| {
target_commands.despawn();
}),
// 当指针在此实体上按下时发送事件:
On::<Pointer<Down>>::send_event::<DoSomethingComplex>(),
));
完整示例Demo
use bevy::prelude::*;
use bevy_mod_picking::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(DefaultPickingPlugins) // 添加拾取插件
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 添加相机
commands.spawn((
Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
RaycastPickCamera::default(), // 使相机能够进行射线拾取
));
// 添加光源
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// 添加可拾取的立方体
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
},
PickableBundle::default(), // 使实体可拾取
On::<Pointer<Click>>::target_commands_mut(|_click, target_commands| {
// 点击时改变颜色
target_commands.insert(StandardMaterial {
base_color: Color::rgb(rand::random(), rand::random(), rand::random()),
..default()
});
}),
On::<Pointer<Drag>>::target_component_mut::<Transform>(|drag, transform| {
// 拖动时移动立方体
transform.translation.x += drag.delta.x / 100.0;
transform.translation.z -= drag.delta.y / 100.0;
}),
));
// 添加地面
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane {
size: 5.0,
subdivisions: 0,
})),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default()
},
PickableBundle::default(),
));
}
使用方法
- 添加插件到你的应用:
.add_plugins(DefaultPickingPlugins)
- 在需要拾取的实体上添加
PickableBundle
:
commands.spawn((
PbrBundle::default(), // `bevy_picking_raycast` 后端适用于网格
PickableBundle::default(), // 使实体可拾取
));
Bevy版本支持
bevy | bevy_mod_picking |
---|---|
0.14 | 0.20 |
0.13 | 0.18, 0.19 |
0.12 | 0.17 |
0.11 | 0.15, 0.16 |
0.10 | 0.12, 0.13, 0.14 |
0.9 | 0.10, 0.11 |
0.8 | 0.8, 0.9 |
0.7 | 0.6, 0.7 |
0.6 | 0.5 |
0.5 | 0.4 |
0.4 | 0.3 |
0.3 | 0.2 |
0.2 | 0.1 |
许可证
本仓库所有代码采用双重许可:
- MIT许可证
- Apache许可证2.0版本
你可以选择你偏好的许可证。
1 回复
bevy_mod_picking - Bevy引擎的3D交互与鼠标拾取插件完整示例
基于前面提供的内容,下面是一个完整的3D对象拾取和交互示例,包含鼠标悬停、点击事件处理和选择高亮效果:
use bevy::prelude::*;
use bevy_mod_picking::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Bevy 3D拾取示例".into(),
..default()
}),
..default()
}))
.add_plugins(DefaultPickingPlugins) // 添加默认拾取插件
.insert_resource(SelectionState::default()) // 选择状态资源
.add_systems(Startup, setup_scene)
.add_systems(Update, (handle_picking_events, update_selection_display))
.run();
}
// 选择状态资源
#[derive(Resource, Default)]
struct SelectionState {
hovered: Option<Entity>,
selected: Option<Entity>,
}
// 设置场景
fn setup_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 相机
commands.spawn((
Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
RaycastPickCamera::default(), // 启用相机拾取
));
// 创建三种不同形状的可拾取对象
let shapes = [
(meshes.add(Cuboid::new(1.0, 1.0, 1.0)), "立方体"),
(meshes.add(Sphere::new(0.5)), "球体"),
(meshes.add(Capsule3d::new(0.5, 1.0)), "胶囊体"),
];
for (i, (shape, name)) in shapes.into_iter().enumerate() {
commands.spawn((
PbrBundle {
mesh: shape,
material: materials.add(Color::hsl(i as f32 * 120.0, 0.9, 0.6)),
transform: Transform::from_xyz(i as f32 * 2.0 - 2.0, 0.5, 0.0),
..default()
},
Name::new(name.to_string()), // 给实体命名
PickableBundle::default(), // 使对象可拾取
RaycastPickTarget::default(), // 使对象成为射线拾取目标
));
}
// 平面作为地面
commands.spawn((
PbrBundle {
mesh: meshes.add(Plane3d::default().mesh().size(10.0, 10.0)),
material: materials.add(Color::rgb(0.3, 0.5, 0.3)),
..default()
},
PickableBundle::IGNORE, // 地面不可拾取
));
// 光源
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// UI文字显示
commands.spawn(
TextBundle::from_sections([
TextSection::new(
"当前状态: ",
TextStyle {
font_size: 24.0,
color: Color::WHITE,
..default()
},
),
TextSection::from_style(TextStyle {
font_size: 24.0,
color: Color::GOLD,
..default()
}),
])
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(10.0),
left: Val::Px(10.0),
..default()
}),
);
}
// 处理拾取事件
fn handle_picking_events(
mut selection_state: ResMut<SelectionState>,
mut click_events: EventReader<Click>,
mut hover_events: EventReader<Pointer<Over>>,
mut exit_events: EventReader<Pointer<Out>>,
names: Query<&Name>,
) {
// 处理悬停事件
for event in hover_events.read() {
if let Some(entity) = event.target {
if let Ok(name) = names.get(entity) {
println!("鼠标悬停在: {}", name);
selection_state.hovered = Some(entity);
}
}
}
// 处理离开事件
for _ in exit_events.read() {
selection_state.hovered = None;
}
// 处理点击事件
for event in click_events.read() {
if let Some(entity) = event.target {
if let Ok(name) = names.get(entity) {
println!("点击了: {}", name);
selection_state.selected = Some(entity);
}
}
}
}
// 更新UI显示
fn update_selection_display(
selection_state: Res<SelectionState>,
names: Query<&Name>,
mut text_query: Query<&mut Text>,
) {
if selection_state.is_changed() {
let mut text = text_query.single_mut();
let hover_text = match selection_state.hovered {
Some(entity) => names.get(entity).map_or("未知对象".to_string(), |n| format!("悬停: {}", n)),
None => "".to_string(),
};
let select_text = match selection_state.selected {
Some(entity) => names.get(entity).map_or("未知对象".to_string(), |n| format!("选中: {}", n)),
None => "".to_string(),
};
text.sections[1].value = format!("{}{}{}",
hover_text,
if !hover_text.is_empty() && !select_text.is_empty() { " | " } else { "" },
select_text
);
}
}
示例功能说明
-
场景设置:
- 创建了一个包含3种不同3D形状(立方体、球体、胶囊体)的场景
- 添加了地面和光照
- 每个可拾取对象都有名称标识
-
交互功能:
- 鼠标悬停时会在控制台输出悬停对象的名称
- 点击对象时会选中并在控制台输出点击对象的名称
- UI顶部会实时显示当前悬停和选中的对象
-
拾取配置:
- 使用
PickableBundle
标记可拾取对象 - 使用
RaycastPickCamera
使相机支持拾取 - 地面使用
PickableBundle::IGNORE
标记为不可拾取
- 使用
-
UI反馈:
- 屏幕左上角显示当前交互状态
- 悬停和选中状态分开显示
扩展建议
- 要添加拖拽功能,可以监听
Drag
事件 - 要实现多选,可以修改
SelectionState
使用HashSet<Entity>
代替单个Entity
- 要添加自定义高亮效果,可以参考前面的
PickHighlight
配置示例