You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Bevy与Rapier实现鼠标点击选中实体的问题排查

问题

我正在使用Bevy(0.11)搭配Rapier(0.22)物理引擎实现方块下落堆叠效果,方块生成代码如下:

commands.spawn((
    Collider::cuboid(dim, dim),
    MaterialMesh2dBundle {
        mesh: meshes.add(shape::Box::new(dim2, dim2, dim2).into()).into(),
        material: materials.add(ColorMaterial::from(random_colour())),
        transform: Transform::from_xyz(
            left + (dim2 * i as f32) + 1.0,
            half_height - dim,
            0.0,
        ),
        ..default()
    },
    RigidBody::Dynamic,
    Restitution::coefficient(0.5),
    ColliderMassProperties::Mass(1000.0), // Increase the mass to stop the glitching (a bit)
    GravityScale(10.0),
));

我希望通过鼠标点击/触摸与方块交互,尝试使用Rapier的Point Projection功能检测鼠标是否指向实体,实现代码如下:

fn mouse_button_events(
    mut mousebtn_evr: EventReader<MouseButtonInput>,
    q_windows: Query<&Window, With<PrimaryWindow>>,
    rapier_context: Res<RapierContext>,
) {
    use bevy::input::ButtonState;

    for ev in mousebtn_evr.iter() {
        match ev.state {
            ButtonState::Pressed => {
                println!("Mouse button press: {:?}", ev.button);

                let point = q_windows.single().cursor_position().unwrap();
                let filter = QueryFilter::default();

                rapier_context.intersections_with_point(point, filter, |entity| {
                    // Callback called on each collider with a shape containing the point.
                    println!("The entity {:?} contains the point.", entity);
                    // Return `false` instead if we want to stop searching for other colliders containing this point.
                    true
                });
            }
            ButtonState::Released => {
                println!("Mouse button release: {:?}", ev.button);
            }
        }
    }
}

但点击方块时并未打印实体ID,请问屏幕坐标与Rapier空间坐标是否一致?我哪里操作出错了?

解决方案

核心问题:屏幕坐标与Rapier物理空间坐标不匹配

Bevy的鼠标光标位置是屏幕像素坐标(原点在左上角,Y轴向下),而Rapier使用的是世界空间坐标(原点通常在屏幕中心,Y轴向上,单位是米而非像素),两者完全不兼容,这是检测失败的主要原因。

修复步骤

  1. 添加相机查询:获取主相机的Camera和GlobalTransform组件,用于坐标转换。
  2. 屏幕坐标转世界坐标:调用Bevy的viewport_to_world方法,将鼠标的像素位置转换成Rapier可识别的世界空间点。
  3. 修正查询参数:把转换后的世界坐标传入intersections_with_point。

修改后的鼠标事件处理函数:

use bevy::render::camera::Camera;

fn mouse_button_events(
    mut mousebtn_evr: EventReader<MouseButtonInput>,
    q_windows: Query<&Window, With<PrimaryWindow>>,
    q_cameras: Query<(&Camera, &GlobalTransform)>,
    rapier_context: Res<RapierContext>,
) {
    use bevy::input::ButtonState;

    let (camera, camera_transform) = q_cameras.single();
    let window = q_windows.single();

    for ev in mousebtn_evr.iter() {
        match ev.state {
            ButtonState::Pressed => {
                println!("Mouse button press: {:?}", ev.button);

                if let Some(screen_pos) = window.cursor_position() {
                    // 转换屏幕坐标到世界空间
                    if let Some(world_pos) = camera.viewport_to_world(camera_transform, screen_pos) {
                        let point = Vec2::new(world_pos.x, world_pos.y);
                        let filter = QueryFilter::default();

                        rapier_context.intersections_with_point(point, filter, |entity| {
                            println!("The entity {:?} contains the point.", entity);
                            true
                        });
                    }
                }
            }
            ButtonState::Released => {
                println!("Mouse button release: {:?}", ev.button);
            }
        }
    }
}

额外排查点

  • 确认相机类型:2D场景需使用OrthographicCameraBundle,否则坐标转换逻辑会失效。
  • 检查碰撞体与Mesh尺寸匹配:Collider::cuboid的参数是半长,如果你的dim不等于dim2 / 2,会导致碰撞体和视觉Mesh错位,点击不到实际碰撞区域。
  • 验证QueryFilter规则:默认QueryFilter会包含所有实体,若你自定义过过滤条件,需确保没有排除RigidBody::Dynamic类型的实体。

内容的提问来源于stack exchange,提问作者CpILL

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 17:15:55