修改重力后无法唤醒RigidBody的问题排查
问题
我有一个立方体,初始设置GravityScale为0以实现悬浮效果。触发事件(按下空格键)后,需要将其GravityScale设为1使其下落。我知道当重力为0时RigidBody会进入休眠状态,因此尝试通过施加ExternalForce来唤醒它。
代码实现
use bevy::prelude::*; use std::time::Duration; use bevy_inspector_egui::quick::WorldInspectorPlugin; use bevy_rapier3d::prelude::*; use leafwing_input_manager::prelude::*; // 注意:此处多余的导入会导致命名冲突,需移除 use prelude::*; #[derive(Component)] struct MyGameCamera; #[derive(Reflect, Component, Default)] #[reflect(Component)] struct ActiveElement { pendulum_timer: Timer, pendulum_inverted: bool, } #[derive(Component)] struct Player; #[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug)] enum Action { Drop, } fn main() { App::new() .register_type::<ActiveElement>() .add_plugins(DefaultPlugins) .add_plugin(RapierPhysicsPlugin::<NoUserData>::default()) .add_plugin(RapierDebugRenderPlugin::default()) .add_plugin(WorldInspectorPlugin::default()) .add_plugin(InputManagerPlugin::<Action>::default()) .add_startup_system(setup) .add_system(drop) .run() } fn setup( mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<StandardMaterial>>, ) { // Camera commands.spawn(( Camera3dBundle { transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..Default::default() }, MyGameCamera, )); // Light commands.spawn(PointLightBundle { transform: Transform::from_xyz(4.0, 10.0, 4.0), point_light: PointLight { intensity: 3000.0, shadows_enabled: true, range: 30.0, ..default() }, ..default() }); // Floor commands .spawn(PbrBundle { mesh: meshes.add(shape::Plane::from_size(5.0).into()), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }) .insert(Collider::cuboid(100.0, 0.1, 100.0)); // cube commands .spawn(PbrBundle { mesh: meshes.add(Mesh::from(shape::Box::new(1.0, 0.2, 1.0))), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 1.6, 0.0), ..default() }) .insert(RigidBody::Dynamic) .insert(Collider::cuboid(0.5, 0.1, 0.5)) .insert(ColliderMassProperties::Density(1.0)) .insert(Restitution::coefficient(0.1)) .insert(Name::new("Cube")) .insert(GravityScale(0.0)) .insert(ActiveElement { pendulum_timer: Timer::new(Duration::from_secs(3), TimerMode::Repeating), pendulum_inverted: false, }); // Input commands .spawn(InputManagerBundle::<Action> { action_state: ActionState::default(), input_map: InputMap::new([(KeyCode::Space, Action::Drop)]), }) .insert(Player); } fn drop( action: Query<&ActionState<Action>, With<Player>>, mut query: Query<(&mut GravityScale, &mut ExternalForce, &mut Sleeping), With<ActiveElement>>, ) { let action_state = action.single(); if action_state.just_pressed(Action::Drop) { query.iter_mut().for_each(|(mut gravity_scale, mut external_force, mut sleeping)| { gravity_scale.0 = 1.0; sleeping.sleeping = false; external_force.force = Vec3::new(200.0, 30000.0, 0.0); external_force.torque = Vec3::new(10.0, 1.0, 0.0); }); } }
Cargo.toml
[...] [dependencies] bevy = "0.10.0" bevy_rapier3d = { version = "0.21.0", features = [ "simd-stable", "debug-render", "parallel" ] } leafwing-input-manager = "0.9.0" bevy-inspector-egui = "0.18.0" [...]
我通过egui inspector可以看到立方体的重力已被应用,但RigidBody似乎没有被唤醒,请问我遗漏了什么?
解决方案
问题出在两个核心点:
错误的休眠唤醒方式:直接修改
Sleeping组件的sleeping字段不会生效,Rapier物理引擎会在内部管理刚体的休眠状态,手动修改的字段会被引擎覆盖。正确的做法是通过RapierContext访问刚体实例,调用wake_up()方法强制唤醒。多余的导入导致冲突:代码中的
use prelude::*;会和bevy::prelude::*的导入冲突,必须移除这行。
另外,使用ExternalImpulse(瞬时冲量)比ExternalForce(持续力)更适合唤醒休眠刚体的场景,因为冲量是一次性施加的,能立刻让刚体产生运动。
修改后的代码
首先移除多余的use prelude::*;,然后修改drop系统:
fn drop( action: Query<&ActionState<Action>, With<Player>>, mut query: Query<( &mut GravityScale, &RigidBodyComponent, ), With<ActiveElement>>, rapier_ctx: Res<RapierContext>, ) { let action_state = action.single(); if action_state.just_pressed(Action::Drop) { query.iter_mut().for_each(|(mut gravity_scale, rb)| { gravity_scale.0 = 1.0; // 强制唤醒刚体 if let Some(rigid_body) = rapier_ctx.rigid_body_set.get_mut(rb.handle) { rigid_body.wake_up(true); } }); } }
如果需要额外施加力/冲量,可在setup中给立方体添加ExternalImpulse组件,并在drop系统中设置:
// 在立方体spawn链中添加 .insert(ExternalImpulse::default())
修改后的drop系统:
fn drop( action: Query<&ActionState<Action>, With<Player>>, mut query: Query<( &mut GravityScale, &mut ExternalImpulse, &RigidBodyComponent, ), With<ActiveElement>>, rapier_ctx: Res<RapierContext>, ) { let action_state = action.single(); if action_state.just_pressed(Action::Drop) { query.iter_mut().for_each(|(mut gravity_scale, mut impulse, rb)| { gravity_scale.0 = 1.0; // 强制唤醒刚体 if let Some(rigid_body) = rapier_ctx.rigid_body_set.get_mut(rb.handle) { rigid_body.wake_up(true); } // 施加瞬时冲量(可选,确保刚体立刻产生运动) impulse.impulse = Vec3::new(0.0, 0.0, 0.0); }); } }
关键说明
RapierContext提供了访问物理引擎内部刚体集合的接口,通过rigid_body_set.get_mut(rb.handle)可以获取到刚体的可变引用,调用wake_up(true)可强制唤醒(参数true表示即使刚体已经在运动也强制刷新状态)。- 若不需要额外的力/冲量,仅仅唤醒刚体并设置
GravityScale为1就足够让它下落,因为重力会持续作用在唤醒后的动态刚体上。
内容的提问来源于stack exchange,提问作者user2393256
相关产品推荐
相关产品推荐

