Skip to content

Mesh Picking——点选 3D 物体

最简单的 3D picking 只需要两步:加插件、挂 Observer。

最小 demo

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>>,
) {
    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 点选

console
cargo run -p ch25-picking --example listing-25-01

点一下立方体,控制台输出:

text
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——完全跳过这个实体,射线检测时直接无视。地面通常用这个:

    rust
    commands.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"的(只对明确标记的实体生效),可以设置:

rust
app.insert_resource(MeshPickingSettings {
    require_markers: true,
    ..default()
});

开启后,只有同时挂了 Pickable 组件的实体才会被检测,相机也需要挂 MeshPickingCamera。适合大型场景里需要精确控制哪些物体可交互的情况。

射线检测的幕后

当你移动鼠标时,输入层把鼠标位置写入 PointerInput;事件处理系统根据相机和鼠标位置生成射线;MeshPickingPluginupdate_hits 系统对场景里所有 Mesh 做射线求交,把命中的实体和距离写入 PointerHits;事件层再把 PointerHits 翻译成 Over/Out/Click 等事件。

这一切都是自动的——你只需要写 Observer 闭包来响应事件。下一节看悬停效果怎么实现。