Rust 3D交互插件bevy_mod_picking的使用,Bevy引擎的高效鼠标拾取与对象选择功能库

Rust 3D交互插件bevy_mod_picking的使用,Bevy引擎的高效鼠标拾取与对象选择功能库

demo

这是一个为Bevy引擎添加拾取功能的灵活插件集合。它允许你在任何实体上添加事件监听器,支持鼠标、触摸甚至游戏手柄输入。

主要特点

  • 轻量级:只编译你需要的部分
  • 表达性强:使用事件监听器组件 On::<Pointer<Click>>::run(my_system)
  • 输入无关:可以用鼠标、笔、触摸或自定义系统控制指针
  • 模块化后端:混合使用如rapiereguibevy_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(),
    ));
}

使用方法

  1. 添加插件到你的应用:
.add_plugins(DefaultPickingPlugins)
  1. 在需要拾取的实体上添加 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
        );
    }
}

示例功能说明

  1. 场景设置

    • 创建了一个包含3种不同3D形状(立方体、球体、胶囊体)的场景
    • 添加了地面和光照
    • 每个可拾取对象都有名称标识
  2. 交互功能

    • 鼠标悬停时会在控制台输出悬停对象的名称
    • 点击对象时会选中并在控制台输出点击对象的名称
    • UI顶部会实时显示当前悬停和选中的对象
  3. 拾取配置

    • 使用PickableBundle标记可拾取对象
    • 使用RaycastPickCamera使相机支持拾取
    • 地面使用PickableBundle::IGNORE标记为不可拾取
  4. UI反馈

    • 屏幕左上角显示当前交互状态
    • 悬停和选中状态分开显示

扩展建议

  1. 要添加拖拽功能,可以监听Drag事件
  2. 要实现多选,可以修改SelectionState使用HashSet<Entity>代替单个Entity
  3. 要添加自定义高亮效果,可以参考前面的PickHighlight配置示例
回到顶部