如何在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
相关产品推荐
相关产品推荐

