使用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轴向上,单位是米而非像素),两者完全不兼容,这是检测失败的主要原因。
修复步骤
- 添加相机查询:获取主相机的
Camera和GlobalTransform组件,用于坐标转换。 - 屏幕坐标转世界坐标:调用Bevy的
viewport_to_world方法,将鼠标的像素位置转换成Rapier可识别的世界空间点。 - 修正查询参数:把转换后的世界坐标传入
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
相关产品推荐
相关产品推荐

