指针事件与 Observer 集成
Picking 框架把鼠标/触摸的原始输入翻译成一组语义清晰的高层事件。每种事件对应一个 Rust 结构体,包裹在 Pointer<E> 里。
事件族谱
| 事件 | 触发时机 | 关键字段 |
|---|---|---|
Over | 指针进入实体边界 | hit: HitData |
Out | 指针离开实体边界 | hit: HitData |
Press | 指针按下(在实体上) | button, hit |
Release | 指针抬起 | button, hit |
Click | 按下 + 抬起(在同一个实体上) | button, hit, duration |
Move | 指针在实体上移动 | hit, delta |
DragStart | 按下后开始移动 | button, hit |
Drag | 拖拽持续中 | button, distance, delta |
DragEnd | 拖拽结束(松手) | button, distance |
DragEnter | 拖拽的物体进入另一个实体 | button, dragged, hit |
DragOver | 拖拽的物体在另一个实体上方移动 | button, dragged, hit |
DragLeave | 拖拽的物体离开另一个实体 | button, dragged, hit |
DragDrop | 拖拽的物体放到另一个实体上 | button, dropped, hit |
事件按顺序触发:一个完整的拖放流程是 DragStart → Drag(多次)→ DragEnd;如果拖到了另一个实体上,还会触发 DragEnter → DragOver(多次)→ DragLeave 或 DragDrop。
悬停效果:Over/Out 切换材质
Listing 25-2 用 Over/Out 事件切换立方体的颜色——鼠标悬停时变黄,移开时恢复:
rust
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins((DefaultPlugins, MeshPickingPlugin))
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let default_matl = materials.add(Color::srgb(0.2, 0.7, 0.9));
let hover_matl = materials.add(Color::srgb(0.9, 0.7, 0.2));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 5.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
));
commands.spawn((
PointLight {
contact_shadows_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0),
));
commands
.spawn((
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
MeshMaterial3d(default_matl.clone()),
))
.observe(
move |over: On<Pointer<Over>>, mut mats: Query<&mut MeshMaterial3d<StandardMaterial>>| {
if let Ok(mut mat) = mats.get_mut(over.entity) {
mat.0 = hover_matl.clone();
}
},
)
.observe(
move |out: On<Pointer<Out>>, mut mats: Query<&mut MeshMaterial3d<StandardMaterial>>| {
if let Ok(mut mat) = mats.get_mut(out.entity) {
mat.0 = default_matl.clone();
}
},
);
}Listing 25-2:Over/Out 事件驱动的悬停材质切换
console
cargo run -p ch25-picking --example listing-25-02两个 Observer 闭包结构完全对称:Over 换成悬停材质,Out 换回默认材质。闭包的第一个参数类型决定了它响应哪种事件——Rust 的类型系统帮你做了事件路由。
注意闭包用了 move 来捕获材质句柄。Handle<StandardMaterial> 实现了 Clone,克隆只是引用计数加一,不复制数据。
通用 Observer 工厂
如果有很多实体需要同样的悬停效果,可以写一个泛型函数来生成 Observer:
rust
fn update_material_on<E: EntityEvent>(
new_material: Handle<StandardMaterial>,
) -> impl Fn(On<E>, Query<&mut MeshMaterial3d<StandardMaterial>>) {
move |event, mut query| {
if let Ok(mut material) = query.get_mut(event.event_target()) {
material.0 = new_material.clone();
}
}
}用的时候:
rust
commands
.spawn((...))
.observe(update_material_on::<Pointer<Over>>(hover_matl.clone()))
.observe(update_material_on::<Pointer<Out>>(default_matl.clone()));EntityEvent trait 提供了 event_target() 方法,返回事件的目标实体 ID——跟直接访问 .entity 等价,但在泛型上下文里更方便。
HitData:命中信息
每种事件携带的 HitData 包含:
position: Option<Vec3>——命中点的世界坐标(Mesh picking 才有)normal: Option<Vec3>——命中面的法线方向distance: f32——命中点到相机的距离
这些信息在做精确交互时很有用——比如在命中点放置一个标记,或者根据法线方向决定物体的朝向。Listing 25-2 只用了实体 ID 来切换材质,没用到 HitData;后面的拖拽和拖放会用到 position。