Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Independent Forces using the ExternalForce component #477

Closed
jsimonrichard opened this issue Feb 8, 2024 · 3 comments
Closed

Adding Independent Forces using the ExternalForce component #477

jsimonrichard opened this issue Feb 8, 2024 · 3 comments

Comments

@jsimonrichard
Copy link

jsimonrichard commented Feb 8, 2024

I'd like to be able to add independent forces/impulses together somehow. For example, I'm working on a space-themed game that requires both the force of gravity (calculated using an n-body simulation) and the force from thrusters to be applied to the same entity (the player's ship). However, this seems to be non-trivial.

It would be nice to be able to do something like this:

fn thruster(mut bodies: Query<&mut ExternalForce, With<Player>>) {
    for mut ext_force in bodies.iter_mut() {
        ext_force.force += Vec2::new(100.0, 0.0); // force from thruster
    }
}

fn gravity(mut bodies: Query<&mut ExternalForce>) {
    for mut ext_force in bodies.iter_mut() {
        ext_force.force += Vec2::new(0.0, -100.0); // force from gravity
    }
}

This doesn't work because the force isn't reset every frame. Instead, the force compounds. To fix this, it's possible to manually reset the force to zero; this might actually be the best option since this allows systems in any part of the code base to add to the total force. Here's a plugin that I wrote that implements this:

use bevy::prelude::*;
use bevy_rapier2d::{dynamics::ExternalForce, plugin::PhysicsSet};

/// Allows forces to be added together each frame without accumulating
/// forces from previous frames.
///
/// **Usage:**
/// ```
/// fn main() {
///     App::new()
///         .add_plugins(AddForcesEachFrame)
///         .add_systems(Update, affect_force.in_set(UpdateForce))
/// }
///
/// // Must run every frame to continue effecting the entity (as simulated by bevy_rapier2d)
/// fn affect_force(mut forces: Query<&mut ExternalForce>) {
///    for mut force in forces.iter_mut() {
///       force.force += Vec2::new(0.0, 1.0);
///   }
/// }
/// ```
pub struct AddForcesEachFrame;

impl Plugin for AddForcesEachFrame {
    fn build(&self, app: &mut App) {
        app.configure_sets(Update, UpdateForce.before(PhysicsSet::SyncBackend))
            .add_systems(
                Update,
                zero_forces
                    .before(UpdateForce)
                    .before(PhysicsSet::SyncBackend),
            );
    }
}

/// SystemSet label for the systems that update the ExternalForce component
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SystemSet)]
pub struct UpdateForce;

fn zero_forces(mut bodies: Query<&mut ExternalForce>) {
    for mut ext_force in bodies.iter_mut() {
        ext_force.force = Vec2::ZERO;
        ext_force.torque = 0.0;
    }
}

Another method: you could create a component for each source of force, and then use a system to combine the forces.

#[derive(Component)]
struct GravityForce {
    force: Vec2
}

#[derive(Component)]
struct ThrusterForce {
    force: Vec2,
    torque: i32
}
...
fn combine_forces(mut bodies: Query<Option<GravityForce>, Option<ThursterForce>, &mut ExternalForce>) {
    for (gravity_force, thruster_force, mut ext_force) in bodies.iter_mut() {
        ext_force.force = 0;
        if let Some(f) = gravity_force {
            ext_force.force += f.force;
        }
        if let Some(f) = thurster_force {
            ext_force.force += f.force;
        }
        ...
    }
}

Is there a better, more ergonomic way to do this with bevy_rapier's APIs?

@ghost
Copy link

ghost commented Feb 21, 2024

Isn't it possible to calculate the sum between the two vectors "gravity" and "thruster" ?
image

@jsimonrichard
Copy link
Author

That's the result both of the examples are trying to achieve.

The assumption here is that the thruster vector and gravity vector aren't initially available in the same system, so they can't be added together directly unless an extra system and extra components are used. In my case, the gravity vector would be coming from a complex gravity simulation managed by one plugin, and the thruster vector would be coming from another plugin/system.

It would be nice to find a solution that allows the gravity code and the thruster code to remain separate so that they're modular. My AddForcesEachFrame plugin does allow this, but I want to make sure there isn't a more ergonomic way to achieve this.

@ghost
Copy link

ghost commented Feb 22, 2024

If you want it to stay "modular" and easy to understand, what you did is just perfect.

  • Add struct to add a new force
  • Applying it to ExternalForce with a system that combine all different forces

The only faster way you can have is to put every force calculation in the same system, a big and confusing system (I didn't even think about that when I suggested my solution).

PS : not even sure if the system is too big
PS2 : Maybe your first solutions would work if you use the chain() to make sure they are executed in order and do extForce = 0 on the first one, but I still don't recommend it for the same reason, the system would be too heavy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant