Skip to content

小结与练习

综合示例

Listing 25-7 把本章的所有要素组合在一起:可飞行的相机、可点选的物体、悬停变色、拖拽移动:

rust
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),
        FreeCamera {
            walk_speed: 5.0,
            run_speed: 15.0,
            ..default()
        },
    ));

    commands.spawn((
        PointLight {
            contact_shadows_enabled: true,
            ..default()
        },
        Transform::from_xyz(4.0, 8.0, 4.0),
    ));

    commands.spawn((
        Mesh3d(meshes.add(Plane3d::default().mesh().size(20.0, 20.0))),
        MeshMaterial3d(materials.add(Color::srgb(0.3, 0.3, 0.3))),
        Pickable::IGNORE,
    ));

    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));
    let cube_mesh = meshes.add(Cuboid::new(1.0, 1.0, 1.0));
    let sphere_mesh = meshes.add(Sphere::new(0.5));
rust
commands
        .spawn((
            Mesh3d(cube_mesh),
            MeshMaterial3d(default_matl.clone()),
            Transform::from_xyz(-1.5, 0.5, 0.0),
        ))
        .observe(on_drag_move)
        .observe(
            move |over: On<Pointer<Over>>, mut q: Query<&mut MeshMaterial3d<StandardMaterial>>| {
                if let Ok(mut m) = q.get_mut(over.entity) {
                    m.0 = hover_matl.clone();
                }
            },
        )
        .observe(
            move |out: On<Pointer<Out>>, mut q: Query<&mut MeshMaterial3d<StandardMaterial>>| {
                if let Ok(mut m) = q.get_mut(out.entity) {
                    m.0 = default_matl.clone();
                }
            },
        );

    commands
        .spawn((
            Mesh3d(sphere_mesh),
            MeshMaterial3d(materials.add(Color::srgb(0.9, 0.3, 0.3))),
            Transform::from_xyz(1.5, 0.5, 0.0),
        ))
        .observe(on_drag_move);
}
rust
fn on_drag_move(drag: On<Pointer<Drag>>, mut transforms: Query<&mut Transform>) {
    if let Ok(mut transform) = transforms.get_mut(drag.entity) {
        transform.translation.x += drag.delta.x * 0.01;
        transform.translation.z += drag.delta.y * 0.01;
    }
}

Listing 25-7:Picking + FreeCamera 的完整组合(src/main.rs)

console
cargo run -p ch25-picking

操作方式:左键点击/拖拽物体,WASD 移动相机,鼠标转向(左键按住捕获)。

本章要点回顾

  • MeshPickingPlugin 不在 DefaultPlugins 里,3D picking 必须手动加;
  • Pointer<E> 是统一的事件容器,E 可以是 ClickOverOutDragDragDrop 等十多种事件;
  • .observe() 在实体上注册事件回调,闭包参数类型决定响应哪种事件;
  • 事件冒泡自动从子实体传播到父实体,event.propagate(false) 阻止传播;
  • Pickable::IGNORE 让实体不参与 picking(地面、辅助线等);
  • Drag 事件的 delta 是屏幕像素增量,不是世界坐标,需要乘以缩放系数;
  • DragDrop 需要在接收区实体上挂 DragEnter/DragOver/DragDrop/DragLeave 四个 Observer;
  • FreeCamera 通过 feature gate "free_camera" 启用,挂组件 + 加插件即可 WASD 飞行。

练习

  1. 多物体拖拽——场景里放三个不同形状的物体,每个都能独立拖拽。提示:每个实体单独挂 Observer,drag.entity 保证只移动被拖的那个。

  2. 拖放到目标——一个球、三个不同颜色的"洞"(平面),把球拖到某个洞上松手,球的颜色变成洞的颜色。提示:在 DragDrop Observer 里查询目标实体的材质颜色。

  3. 相机切换——两个相机,一个固定俯视,一个 FreeCamera。按 Tab 键切换哪个相机是活跃的。提示:Camera 组件有 order 字段,或者用 is_active 控制;FreeCameraStateenabled 字段。

  4. Click 放置——点击地面时,在点击位置(HitData::position)生成一个小物体。提示:用 Pointer<Click> Observer,在闭包里读 click.hit.position,用 commands.spawn(...) 放置物体。

  5. 禁止穿透——场景里有一个 UI 面板浮在 3D 物体上方。要求点击 UI 时不触发后面 3D 物体的 Click 事件。提示:UI 默认的 Pickableshould_block_lower,不需要额外代码——验证一下就好。