Mesh Picking——点选 3D 物体
最简单的 3D picking 只需要两步:加插件、挂 Observer。
最小 demo
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>>,
) {
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(materials.add(Color::srgb(0.2, 0.7, 0.9))),
))
.observe(|click: On<Pointer<Click>>| {
info!("clicked on {:?}", click.entity);
});
}Listing 25-1:MeshPickingPlugin + Click Observer——最小可用的 3D 点选
cargo run -p ch25-picking --example listing-25-01点一下立方体,控制台输出:
INFO ch25_picking::listing_25_01: clicked on Entity(3v1#4294967299)三行关键代码拆开看:
加插件——MeshPickingPlugin 不在 DefaultPlugins 里,必须手动加。加了之后,所有 Mesh3d 实体自动参与射线检测;
挂 Observer——.observe(|click: On<Pointer<Click>>| { ... }) 在立方体实体上注册了一个点击回调。闭包参数 click 的类型是 On<Pointer<Click>>,通过 click.entity 拿到被点击的实体 ID;
不需要额外组件——Mesh3d + MeshMaterial3d 就够了,picking 后端会自动对所有带 Mesh3d 的实体做射线检测。
Pickable 组件:控制参与权
默认行为是"所有 Mesh 都可点"。如果某个实体不想被点到(比如地面、辅助线),有两种方式:
Pickable::IGNORE——完全跳过这个实体,射线检测时直接无视。地面通常用这个:rustcommands.spawn(( Mesh3d(meshes.add(Plane3d::default().mesh().size(20.0, 20.0))), MeshMaterial3d(...), Pickable::IGNORE, ));自定义
Pickable——Pickable有两个字段:should_block_lower(是否挡住后面的实体)和is_hoverable(是否产生悬停事件)。默认都是true。
require_markers:严格模式
如果你希望 picking 是"opt-in"的(只对明确标记的实体生效),可以设置:
app.insert_resource(MeshPickingSettings {
require_markers: true,
..default()
});开启后,只有同时挂了 Pickable 组件的实体才会被检测,相机也需要挂 MeshPickingCamera。适合大型场景里需要精确控制哪些物体可交互的情况。
射线检测的幕后
当你移动鼠标时,输入层把鼠标位置写入 PointerInput;事件处理系统根据相机和鼠标位置生成射线;MeshPickingPlugin 的 update_hits 系统对场景里所有 Mesh 做射线求交,把命中的实体和距离写入 PointerHits;事件层再把 PointerHits 翻译成 Over/Out/Click 等事件。
这一切都是自动的——你只需要写 Observer 闭包来响应事件。下一节看悬停效果怎么实现。