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

修改重力后无法唤醒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似乎没有被唤醒,请问我遗漏了什么?


解决方案

问题出在两个核心点:

  1. 错误的休眠唤醒方式:直接修改Sleeping组件的sleeping字段不会生效,Rapier物理引擎会在内部管理刚体的休眠状态,手动修改的字段会被引擎覆盖。正确的做法是通过RapierContext访问刚体实例,调用wake_up()方法强制唤醒。

  2. 多余的导入导致冲突:代码中的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 19:25:01