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

如何在Bevy(Rapier2D物理)中为实体施加多份外力?

在Bevy+Rapier2D中从多系统为实体施加多个作用力

在Rapier2D里,你可以通过几种方式实现多系统给同一实体施加不冲突的作用力,核心是避免直接覆盖力的数值,而是做累加或者让Rapier自动处理:

1. 累加ExternalForce组件的力值

ExternalForce是Rapier提供的组件,用于给实体施加持续的外力。如果多个系统都要修改这个组件,不要直接替换整个组件,而是获取可变引用后累加force字段的值。

示例代码:

use bevy::prelude::*;
use rapier2d::prelude::*;

// 移动系统:施加水平方向的力
fn apply_movement_force(
    mut query: Query<&mut ExternalForce, With<Player>>,
    input: Res<Input<KeyCode>>,
) {
    let mut force = query.single_mut();
    let horizontal_force = if input.pressed(KeyCode::A) {
        -500.0
    } else if input.pressed(KeyCode::D) {
        500.0
    } else {
        0.0
    };
    // 累加水平力到现有力上
    force.force.x += horizontal_force;
}

// 跳跃系统:施加垂直方向的力
fn apply_jump_force(
    mut query: Query<(&mut ExternalForce, &Velocity), With<Player>>,
    input: Res<Input<KeyCode>>,
) {
    let (mut force, velocity) = query.single_mut();
    if input.just_pressed(KeyCode::Space) && velocity.linvel.y.abs() < 0.1 {
        // 累加跳跃力到现有力上
        force.force.y += 1500.0;
    }
}

// 记得在App中添加系统时,确保它们在物理模拟前运行
App::new()
    // ...其他插件
    .add_system(apply_movement_force.before(RapierPhysicsSet::StepSimulation))
    .add_system(apply_jump_force.before(RapierPhysicsSet::StepSimulation))
    // ...

2. 用临时组件收集力,统一应用

如果不想直接修改ExternalForce,可以自定义一个临时组件来收集所有系统的作用力,最后由一个专门的系统统一将这些力应用到ExternalForce上,之后清空临时组件。

示例代码:

use bevy::prelude::*;
use rapier2d::prelude::*;

// 自定义临时组件,用于收集待施加的力
#[derive(Component, Default)]
struct PendingForce {
    force: Vec2,
}

// 移动系统:添加水平力到PendingForce
fn add_movement_force(
    mut query: Query<&mut PendingForce, With<Player>>,
    input: Res<Input<KeyCode>>,
) {
    let mut pending = query.single_mut();
    let horizontal = if input.pressed(KeyCode::A) { -500.0 } else if input.pressed(KeyCode::D) { 500.0 } else { 0.0 };
    pending.force.x += horizontal;
}

// 跳跃系统:添加垂直力到PendingForce
fn add_jump_force(
    mut query: Query<(&mut PendingForce, &Velocity), With<Player>>,
    input: Res<Input<KeyCode>>,
) {
    let (mut pending, velocity) = query.single_mut();
    if input.just_pressed(KeyCode::Space) && velocity.linvel.y.abs() < 0.1 {
        pending.force.y += 1500.0;
    }
}

// 统一应用力的系统:将PendingForce的力加到ExternalForce,然后清空
fn apply_pending_forces(
    mut query: Query<(&mut ExternalForce, &mut PendingForce)>,
) {
    for (mut external, mut pending) in query.iter_mut() {
        external.force += pending.force;
        // 清空临时力,避免下一帧重复应用
        pending.force = Vec2::ZERO;
    }
}

// 系统顺序:收集力的系统先运行,然后统一应用,最后物理模拟
App::new()
    // ...
    .add_system(add_movement_force.before(apply_pending_forces))
    .add_system(add_jump_force.before(apply_pending_forces))
    .add_system(apply_pending_forces.before(RapierPhysicsSet::StepSimulation))
    // ...

3. 直接调用RapierContext的即时施加API

如果是一次性的冲量或瞬时力,可以直接使用RapierContext提供的apply_force或apply_impulse方法,这些调用会被Rapier自动累加,不需要担心覆盖问题。

示例代码:

use bevy::prelude::*;
use rapier2d::prelude::*;

// 攻击系统:施加瞬时力
fn apply_attack_impulse(
    rapier_context: Res<RapierContext>,
    query: Query<Entity, With<Player>>,
    input: Res<Input<MouseButton>>,
) {
    let player = query.single();
    if input.just_pressed(MouseButton::Left) {
        // 施加瞬时冲量(位置在实体重心)
        rapier_context.apply_impulse(
            player,
            &Vec2::new(0.0, 2000.0),
            true, // 是否唤醒休眠的实体
        );
    }
}

// 移动系统:施加瞬时力
fn apply_movement_force(
    rapier_context: Res<RapierContext>,
    query: Query<Entity, With<Player>>,
    input: Res<Input<KeyCode>>,
) {
    let player = query.single();
    let force = if input.pressed(KeyCode::A) {
        Vec2::new(-1000.0, 0.0)
    } else if input.pressed(KeyCode::D) {
        Vec2::new(1000.0, 0.0)
    } else {
        Vec2::ZERO
    };
    if force != Vec2::ZERO {
        rapier_context.apply_force(
            player,
            &force,
            &Vec2::ZERO, // 力的作用点(重心为(0,0))
            true,
        );
    }
}

// 确保这些系统在物理模拟前运行
App::new()
    // ...
    .add_system(apply_attack_impulse.before(RapierPhysicsSet::StepSimulation))
    .add_system(apply_movement_force.before(RapierPhysicsSet::StepSimulation))
    // ...

注意事项

  • 系统顺序很重要:所有施加力的系统必须在RapierPhysicsSet::StepSimulation之前运行,否则你的力不会被当前帧的物理模拟处理。
  • 休眠实体:如果实体处于休眠状态,施加力时记得设置wake参数为true,否则实体不会响应。

内容的提问来源于stack exchange,提问作者Ask Sødal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 18:05:32