Bevy 0.14中Rapier3D碰撞检测无响应,求代码问题排查
Bevy 0.14 + Rapier3D 碰撞检测无响应问题排查
我用Bevy 0.14开发游戏,按照官方手册用Rapier3D实现碰撞检测,但始终无法触发碰撞事件,多次对照手册调整代码依然无效。以下是我的代码,麻烦帮忙排查问题:
use bevy::input::mouse::{MouseMotion, MouseWheel}; use bevy::prelude::*; use bevy_rapier3d::prelude::*; fn main() { App::new(). add_plugins(DefaultPlugins). add_plugins(RapierPhysicsPlugin::<NoUserData>::default()). add_plugins(RapierDebugRenderPlugin::default()). add_systems(Startup, init_object). add_systems(Update, (player_movement, camera_movement)). add_systems(PostUpdate, landed_player_check). run(); } // 玩家组件定义 #[derive(Component)] struct Player { is_jumping: bool, } impl Default for Player { fn default() -> Self { Self {is_jumping: false} } } #[derive(Component)] struct Camera; #[derive(Component)] struct CameraFollow { offset: Vec3, } #[derive(Component)] struct Ground; fn init_object( mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<StandardMaterial>>, ) { // 地面 commands.spawn(( PbrBundle { mesh: meshes.add(Plane3d::default().mesh().size(20., 20.)), material: materials.add(Color::srgb_u8(255, 0, 0)), transform: Transform::from_xyz(0.0, 0.0, 0.0), ..default() }, Collider::cuboid(10.0, 0.0, 10.0), Ground, )); // 玩家 commands.spawn(( PbrBundle { mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)), transform: Transform::from_xyz(0.0, 1.0, 0.0), material: materials.add(StandardMaterial { base_color: Color::WHITE, ..default() }), ..default() }, Player::default(), RigidBody::Dynamic, Collider::cuboid(0.5, 0.5, 0.5), GravityScale(10.0), )); // 光源 commands.spawn(DirectionalLightBundle { transform: Transform::from_translation(Vec3::ONE).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); // 相机 commands.spawn(( Camera3dBundle { transform: Transform::from_xyz(0.0, 10.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }, Camera, CameraFollow { offset: Vec3::new(0.0, 10.0, 10.0), }, )); } fn player_movement( mut query: Query<(&mut Transform, &mut Player), With<Player>>, keyboard_input: Res<ButtonInput<KeyCode>>, time: Res<Time>, ) { let speed = 10.0; if let Ok((mut transform, mut player_state)) = query.get_single_mut() { if !player_state.is_jumping { let mut direction = Vec3::ZERO; if keyboard_input.pressed(KeyCode::KeyW) { direction.z -= 1.0; } if keyboard_input.pressed(KeyCode::KeyS) { direction.z += 1.0; } if keyboard_input.pressed(KeyCode::KeyA) { direction.x -= 1.0; } if keyboard_input.pressed(KeyCode::KeyD) { direction.x += 1.0; } if keyboard_input.pressed(KeyCode::Space) { player_state.is_jumping = true; direction.y += 10.0; } transform.translation += direction * speed * time.delta_seconds(); } } } fn camera_movement( mut param_set: ParamSet<( Query<(&mut Transform, &mut Player), With<Player>>, // 玩家查询 Query<(&mut Transform, &mut CameraFollow), With<Camera>>, // 相机查询 )>, mouse_button: Res<ButtonInput<MouseButton>>, mut mouse_motion: EventReader<MouseMotion>, mut wheel_event: EventReader<MouseWheel>, ) { let zoom_speed = 0.5; // 按住鼠标中键时处理相机旋转 if mouse_button.pressed(MouseButton::Middle) { for mouse_move_event in mouse_motion.read() { if let Ok((player_transform, _)) = param_set.p0().get_single() { let player_position = player_transform.translation; if let Ok((mut camera_transform, mut camera_follow)) = param_set.p1().get_single_mut() { // 左右旋转以世界Y轴为基准,上下旋转以相机本地X轴为基准 let rotation = Quat::from_rotation_y(-mouse_move_event.delta.x * 0.005) * Quat::from_axis_angle(*camera_transform.local_x(), -mouse_move_event.delta.y * 0.005); camera_follow.offset = rotation * camera_follow.offset; camera_transform.translation = player_position + camera_follow.offset; // 保持相机朝向玩家 camera_transform.look_at(player_position, Vec3::Y); } } } } // 更新相机位置 if let Ok((player_transform, _)) = param_set.p0().get_single() { let player_position = player_transform.translation; if let Ok((mut camera_transform, camera_follow)) = param_set.p1().get_single_mut() { camera_transform.translation = player_position + camera_follow.offset; camera_transform.look_at(player_position, Vec3::Y); } } // 鼠标滚轮调整相机缩放 for v in wheel_event.read() { if let Ok((_, mut camera_follow)) = param_set.p1().get_single_mut() { let zoom_delta = -v.y * zoom_speed; let offset_length = camera_follow.offset.length(); // 限制缩放范围 let new_length = (offset_length + zoom_delta).clamp(5.0, 20.0); camera_follow.offset = camera_follow.offset.normalize() * new_length; } } } fn landed_player_check( mut collision_events: EventReader<CollisionEvent>, player_query: Query<Entity, With<Player>>, ground_query: Query<Entity, With<Ground>>, ) { let player_entity = player_query.get_single().ok(); let ground_entity = ground_query.get_single().ok(); for event in collision_events.read() { println!("碰撞事件触发: {:?}", event); match event { CollisionEvent::Started(entity1, entity2, _) => { println!("实体1: {:?}, 实体2: {:?}", entity1, entity2); if let (Some(player), Some(ground)) = (player_entity, ground_entity) { println!("玩家实体: {:?}, 地面实体: {:?}", player, ground); if (*entity1 == player && *entity2 == ground) || (*entity1 == ground && *entity2 == player) { println!("玩家落地了!"); } else { println!("触发了碰撞,但不是玩家和地面"); } } } _ => {} } } }
问题排查与修复方案
1. 地面Collider厚度为0
代码中地面的Collider设置为Collider::cuboid(10.0, 0.0, 10.0),Y轴厚度为0的几何体在3D物理引擎中会被视为无限薄的平面,大概率会被碰撞检测忽略。
修复:将厚度改为极小的正值,比如0.1:
Collider::cuboid(10.0, 0.1, 10.0),
2. 地面未添加静态刚体组件
Rapier中,仅添加Collider而没有RigidBody的实体,不会主动参与物理碰撞事件。地面需要显式设置为固定刚体:
修复:给地面实体添加RigidBody::Fixed:
commands.spawn(( PbrBundle { /* ... 原有代码 ... */ }, Collider::cuboid(10.0, 0.1, 10.0), RigidBody::Fixed, // 添加这一行 Ground, ));
3. 直接修改玩家Transform导致物理状态不同步
player_movement系统中直接修改transform.translation,会绕过Rapier的物理模拟,导致刚体的物理状态和视觉位置不一致,碰撞检测自然无法正常触发。
修复:改用Rapier的Velocity组件控制玩家移动:
fn player_movement( mut query: Query<(&mut Velocity, &mut Player), With<Player>>, keyboard_input: Res<ButtonInput<KeyCode>>, ) { let speed = 10.0; let jump_force = 50.0; if let Ok((mut velocity, mut player_state)) = query.get_single_mut() { let mut direction = Vec3::ZERO; if keyboard_input.pressed(KeyCode::KeyW) { direction.z -= 1.0; } if keyboard_input.pressed(KeyCode::KeyS) { direction.z += 1.0; } if keyboard_input.pressed(KeyCode::KeyA) { direction.x -= 1.0; } if keyboard_input.pressed(KeyCode::KeyD) { direction.x += 1.0; } // 处理水平移动 if direction != Vec3::ZERO { direction = direction.normalize(); velocity.linvel.x = direction.x * speed; velocity.linvel.z = direction.z * speed; } else { velocity.linvel.x = 0.0; velocity.linvel.z = 0.0; } // 处理跳跃(仅在未跳跃时触发) if keyboard_input.just_pressed(KeyCode::Space) && !player_state.is_jumping { player_state.is_jumping = true; velocity.linvel.y = jump_force; } } }
4. 修复语法错误
原代码中玩家PbrBundle的transform行末尾有多余的킴字符,会导致编译失败,已在上面的代码示例中移除。
额外优化:落地状态重置
当前代码中玩家is_jumping状态只会在跳跃时设为true,没有重置逻辑,需要在碰撞检测到落地时重置:
fn landed_player_check( mut collision_events: EventReader<CollisionEvent>, mut player_query: Query<&mut Player, With<Player>>, player_entity_query: Query<Entity, With<Player>>, ground_query: Query<Entity, With<Ground>>, ) { let player_entity = player_entity_query.get_single().ok(); let ground_entity = ground_query.get_single().ok(); let mut player = player_query.get_single_mut().ok(); for event in collision_events.read() { match event { CollisionEvent::Started(entity1, entity2, _) => { if let (Some(player), Some(ground), Some(mut player_state)) = (player_entity, ground_entity, player) { if (*entity1 == player && *entity2 == ground) || (*entity1 == ground && *entity2 == player) { player_state.is_jumping = false; println!("玩家落地了!"); } } } _ => {} } } }
内容的提问来源于stack exchange,提问作者yun
相关产品推荐
相关产品推荐

