Skip to content

Commit

Permalink
Merge pull request #164 from janhohenheim/rigid-body
Browse files Browse the repository at this point in the history
  • Loading branch information
janhohenheim authored Feb 18, 2023
2 parents 5f0cb1a + 035b2b1 commit 42ab915
Show file tree
Hide file tree
Showing 12 changed files with 152 additions and 327 deletions.
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 1 addition & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ iyes_progress = "0.7.1"
unicode-segmentation = "1.10.1"
bevy_hanabi = { version = "0.5", optional = true }
anyhow = "1.0.69"
bevy_rapier3d = { version = "0.20.0", features = ["serde-serialize"] }
bevy_rapier3d = { version = "0.20", features = ["serde-serialize"] }

# keep the following in sync with Bevy's dependencies
winit = { version = "0.27", default-features = false }
Expand All @@ -83,10 +83,5 @@ git = "https://github.com/jakobhellermann/bevy_editor_pls"
rev = "6062b860aea87034081399369fd7ef5715f13256"
optional = true

[patch.crates-io.bevy_rapier3d]
git = "https://github.com/janhohenheim/bevy_rapier"
branch = "own-fork"


[build-dependencies]
embed-resource = "1.4"
6 changes: 3 additions & 3 deletions src/level_instantiation/spawning/objects/npc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::level_instantiation::spawning::objects::GameCollisionGroup;
use crate::level_instantiation::spawning::{
GameObject, PrimedGameObjectSpawner, PrimedGameObjectSpawnerImplementor,
};
use crate::movement::general_movement::{CharacterAnimations, KinematicCharacterBundle, Model};
use crate::movement::general_movement::{CharacterAnimations, CharacterControllerBundle, Model};
use crate::movement::navigation::Follower;
use crate::world_interaction::dialog::{DialogId, DialogTarget};
use anyhow::{Context, Result};
Expand Down Expand Up @@ -35,7 +35,7 @@ impl PrimedGameObjectSpawnerImplementor for NpcSpawner {
..default()
},
Name::new("NPC"),
KinematicCharacterBundle::capsule(HEIGHT, RADIUS),
CharacterControllerBundle::capsule(HEIGHT, RADIUS),
Follower,
CharacterAnimations {
idle: spawner.animations.character_idle.clone(),
Expand All @@ -52,7 +52,7 @@ impl PrimedGameObjectSpawnerImplementor for NpcSpawner {
Collider::cylinder(HEIGHT / 2., RADIUS * 5.),
Sensor,
ActiveEvents::COLLISION_EVENTS,
ActiveCollisionTypes::KINEMATIC_KINEMATIC,
ActiveCollisionTypes::DYNAMIC_DYNAMIC,
CollisionGroups::new(
GameCollisionGroup::OTHER.into(),
GameCollisionGroup::PLAYER.into(),
Expand Down
4 changes: 2 additions & 2 deletions src/level_instantiation/spawning/objects/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::level_instantiation::spawning::objects::GameCollisionGroup;
use crate::level_instantiation::spawning::{
GameObject, PrimedGameObjectSpawner, PrimedGameObjectSpawnerImplementor,
};
use crate::movement::general_movement::{CharacterAnimations, KinematicCharacterBundle, Model};
use crate::movement::general_movement::{CharacterAnimations, CharacterControllerBundle, Model};
use crate::player_control::player_embodiment::Player;
use anyhow::{Context, Result};
use bevy::prelude::*;
Expand Down Expand Up @@ -35,7 +35,7 @@ impl PrimedGameObjectSpawnerImplementor for PlayerSpawner {
},
Player,
Name::new("Player"),
KinematicCharacterBundle::capsule(HEIGHT, RADIUS),
CharacterControllerBundle::capsule(HEIGHT, RADIUS),
CharacterAnimations {
idle: spawner.animations.character_idle.clone(),
walk: spawner.animations.character_walking.clone(),
Expand Down
187 changes: 69 additions & 118 deletions src/movement/general_movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@ use crate::level_instantiation::spawning::AnimationEntityLink;
use crate::util::log_error::log_errors;
use crate::util::trait_extension::Vec3Ext;
use crate::GameState;
pub use components::{Velocity, *};
pub use components::*;

/// Handles movement of kinematic character controllers, i.e. entities with the [`KinematicCharacterBundle`]. A movement is done by applying forces to the objects.
/// Handles movement of character controllers, i.e. entities with the [`CharacterControllerBundle`].
/// The default forces on a character going right are:
/// ```text
/// ┌──────────────────────────────┐
/// │ Gravity │
/// │ ↓ │
/// │ ╔═╗ │
/// │ Walking ─► ║ ║ ◄─ Drag
/// │ Walking ─► ║ ║ ◄─ Damping
/// │ ╚═╝ │
/// │ │
/// └──────────────────────────────┘
/// ```
/// All physics values are assumed to be in SI units, e.g. forces are measured in N and acceleration in m/s².
///
/// The [`Walking`] and [`Jumping`] components are user friendly ways of influencing the corresponding forces.
/// There is no explicit maximum speed since the [`Drag`] counteracts all other forces until reaching an equilibrium.
/// There is no explicit maximum speed since the damping counteracts all other forces until reaching an equilibrium.
/// The [`Grounded`] component is used to determine whether the character is on the ground or not.
/// To influence movement, apply your force by adding it to the character's total [`Force`]. Common ways to do this are:
/// - A continuous force like walking: `force.0 += acceleration * mass.0`, with `force`: [`Force`], `mass`: [`Mass`], and a user-defined `acceleration`: [`f32`]
/// - An instantaneous force (i.e. an impulse) like jumping: `force.0 += velocity * mass.0 / time.delta_seconds()`, with `force`: [`Force`], `mass`: [`Mass`], `time`: [`Res<Time>`](Time) and a user-defined `velocity`: [`f32`]
/// To influence movement, apply your force by adding it to the character's total [`ExternalForce`] or [`ExternalImpulse`]. This is usually done like this:
/// - A continuous force like walking: `external_force.force += acceleration * read_mass_properties.0.mass`, with `external_force`: [`ExternalForce`], `read_mass_properties`: [`ReadMassProperties`], and a user-defined `acceleration`: [`Vec3`]
/// - An instantaneous force (i.e. an impulse) like jumping: `external_impulse.impulse += velocity * read_mass_properties.0.mass`, with `external_impulse`: [`ExternalImpulse`], `read_mass_properties`: [`ReadMassProperties`], and a user-defined `velocity`: [`Vec3`]
///
/// Note: you might notice that the normal force is not included in the above diagram. This is because the underlying [`KinematicCharacterController`] takes care of the character not penetrating colliders, thus emulating this force.
/// Note: you might notice that the normal force is not included in the above diagram. This is because rapier emulates it by moving penetrating colliders out of each other.
pub struct GeneralMovementPlugin;

impl Plugin for GeneralMovementPlugin {
Expand All @@ -39,85 +39,51 @@ impl Plugin for GeneralMovementPlugin {
.register_type::<Grounded>()
.register_type::<Jumping>()
.register_type::<Velocity>()
.register_type::<Drag>()
.register_type::<Walking>()
.register_type::<Force>()
.register_type::<Mass>()
.register_type::<Gravity>()
.register_type::<CharacterAnimations>()
.add_system_set(
SystemSet::on_update(GameState::Playing)
.with_system(update_grounded)
.with_system(apply_gravity.after(update_grounded).before(apply_force))
.with_system(apply_walking.after(update_grounded).before(apply_force))
.with_system(apply_jumping.after(apply_gravity).before(apply_force))
.with_system(
apply_drag
.after(apply_walking)
.after(apply_jumping)
.before(apply_force),
)
.with_system(apply_force)
.with_system(reset_movement_components.after(apply_force))
.with_system(rotate_characters)
.with_system(play_animations.pipe(log_errors)),
.with_system(reset_movement_components)
.with_system(update_grounded.after(reset_movement_components))
.with_system(apply_walking.after(update_grounded))
.with_system(apply_jumping.after(update_grounded))
.with_system(rotate_characters.after(update_grounded))
.with_system(play_animations.pipe(log_errors).after(update_grounded)),
);
}
}

fn update_grounded(
mut query: Query<(
&mut Grounded,
&Velocity,
&KinematicCharacterController,
Option<&KinematicCharacterControllerOutput>,
)>,
) {
for (mut grounded, velocity, controller, output) in &mut query {
let falling = velocity.0.dot(controller.up) < -1e-5;
if !falling {
grounded.force_set(false)
} else if let Some(output) = output {
grounded.try_set(output.grounded);
}
}
}

fn apply_gravity(
mut character: Query<(&mut Force, &KinematicCharacterController, &Mass, &Gravity)>,
mut query: Query<(Entity, &Transform, &Collider, &mut Grounded)>,
rapier_context: Res<RapierContext>,
) {
for (mut force, controller, mass, gravity) in &mut character {
let gravitational_force = -controller.up * gravity.0 * mass.0;
force.0 += gravitational_force;
}
}

/// Treat `Force` as readonly after this system.
pub fn apply_force(
time: Res<Time>,
mut player_query: Query<(
&Force,
&mut Velocity,
&mut KinematicCharacterController,
&Mass,
)>,
) {
let dt = time.delta_seconds();
for (force, mut velocity, mut controller, mass) in &mut player_query {
let acceleration = force.0 / mass.0;
let desired_translation = velocity.0 * dt + 0.5 * acceleration * dt * dt;
velocity.0 += acceleration * dt;
controller.translation = Some(desired_translation);
for (entity, transform, collider, mut grounded) in &mut query {
let height = collider.raw.compute_local_aabb().maxs.y;
grounded.0 = rapier_context
.cast_ray(
transform.translation,
transform.down(),
height + 0.1,
true,
QueryFilter::new()
.exclude_collider(entity)
.exclude_sensors(),
)
.is_some();
}
}

pub fn reset_movement_components(
mut forces: Query<&mut Force>,
mut forces: Query<&mut ExternalForce>,
mut impulses: Query<&mut ExternalImpulse>,
mut walking: Query<&mut Walking>,
mut jumpers: Query<&mut Jumping>,
) {
for mut force in &mut forces {
force.0 = Vec3::ZERO;
*force = default();
}
for mut impulse in &mut impulses {
*impulse = default();
}
for mut walk in &mut walking {
walk.direction = None;
Expand All @@ -128,45 +94,38 @@ pub fn reset_movement_components(
}

pub fn apply_jumping(
time: Res<Time>,
mut character_query: Query<(
&Grounded,
&mut Force,
&mut ExternalImpulse,
&mut Velocity,
&KinematicCharacterController,
&Mass,
&ReadMassProperties,
&Jumping,
&Transform,
)>,
) {
let dt = time.delta_seconds();
for (grounded, mut force, mut velocity, controller, mass, jump) in &mut character_query {
if jump.requested && grounded.is_grounded() {
force.0 += controller.up * mass.0 * jump.speed / dt;
for (grounded, mut impulse, mut velocity, mass, jump, transform) in &mut character_query {
if jump.requested && grounded.0 {
let up = transform.up();
impulse.impulse += up * mass.0.mass * jump.speed;

// Kill any downward velocity. This ensures that repeated jumps are always the same height.
// Otherwise the falling velocity from the last tick would dampen the jump velocity.
let velocity_components = velocity.0.split(controller.up);
velocity.0 = velocity_components.horizontal;
let velocity_components = velocity.linvel.split(up);
velocity.linvel = velocity_components.horizontal;
}
}
}

fn rotate_characters(
time: Res<Time>,
mut player_query: Query<(
&KinematicCharacterControllerOutput,
&KinematicCharacterController,
&mut Transform,
)>,
) {
fn rotate_characters(time: Res<Time>, mut player_query: Query<(&Velocity, &mut Transform)>) {
let dt = time.delta_seconds();
for (output, controller, mut transform) in player_query.iter_mut() {
let horizontal_movement = output.effective_translation.split(controller.up).horizontal;
for (velocity, mut transform) in player_query.iter_mut() {
let up = transform.up();
let horizontal_movement = velocity.linvel.split(up).horizontal;
if horizontal_movement.is_approx_zero() {
continue;
}
let target_transform =
transform.looking_at(transform.translation + horizontal_movement, controller.up);
transform.looking_at(transform.translation + horizontal_movement, up);
// Asymptotic averaging
const SMOOTHNESS: f32 = 4.;
let scale = (SMOOTHNESS * dt).min(1.);
Expand All @@ -178,25 +137,25 @@ fn rotate_characters(
fn play_animations(
mut animation_player: Query<&mut AnimationPlayer>,
characters: Query<(
&KinematicCharacterControllerOutput,
&KinematicCharacterController,
&Velocity,
&Transform,
&Grounded,
&AnimationEntityLink,
&CharacterAnimations,
)>,
) -> Result<()> {
for (output, controller, grounded, animation_entity_link, animations) in characters.iter() {
for (velocity, transform, grounded, animation_entity_link, animations) in characters.iter() {
let mut animation_player = animation_player
.get_mut(animation_entity_link.0)
.context("animation_entity_link held entity without animation player")?;

let has_horizontal_movement = !output
.effective_translation
.split(controller.up)
let has_horizontal_movement = !velocity
.linvel
.split(transform.up())
.horizontal
.is_approx_zero();

if !grounded.is_grounded() {
if !grounded.0 {
animation_player
.play(animations.aerial.clone_weak())
.repeat();
Expand All @@ -209,40 +168,32 @@ fn play_animations(
Ok(())
}

fn apply_drag(
mut character_query: Query<(&mut Force, &Velocity, &KinematicCharacterController, &Drag)>,
) {
for (mut force, velocity, controller, drag) in &mut character_query {
let drag_force = drag.calculate_force(velocity.0, controller.up);
force.0 += drag_force;
}
}

pub fn apply_walking(
mut character_query: Query<(
&mut Force,
&mut ExternalForce,
&Walking,
&mut Velocity,
&KinematicCharacterController,
&Grounded,
&Mass,
&ReadMassProperties,
&Transform,
)>,
) {
for (mut force, walking, mut velocity, controller, grounded, mass) in &mut character_query {
if let Some(acceleration) = walking.get_acceleration(grounded.is_grounded()) {
let walking_force = acceleration * mass.0;
force.0 += walking_force;
} else if grounded.is_grounded() {
let velocity_components = velocity.0.split(controller.up);
for (mut force, walking, mut velocity, grounded, mass, transform) in &mut character_query {
let mass = mass.0.mass;
if let Some(acceleration) = walking.get_acceleration(grounded.0) {
let walking_force = acceleration * mass;
force.force += walking_force;
} else if grounded.0 {
let velocity_components = velocity.linvel.split(transform.up());
if velocity_components.horizontal.length_squared()
< walking.stopping_speed * walking.stopping_speed
{
velocity.0 = velocity_components.vertical;
velocity.linvel = velocity_components.vertical;
} else if let Some(braking_direction) =
velocity_components.horizontal.try_normalize().map(|v| -v)
{
let braking_force = walking.braking_acceleration * braking_direction * mass.0;
force.0 += braking_force;
let braking_force = walking.braking_acceleration * braking_direction * mass;
force.force += braking_force;
}
}
}
Expand Down
Loading

0 comments on commit 42ab915

Please sign in to comment.