Skip to content

Commit

Permalink
Improve movement in examples (#210)
Browse files Browse the repository at this point in the history
# Objective

Fixes #170.

Currently, the movement in examples like `move_marbles` and `cubes` is frame rate dependent because they are running in `Update` but don't use delta time. `basic_dynamic_character` and `basic_kinematic_character` on the other hand run in the `PhysicsSchedule`, but this can cause issues with missed inputs as reported in #170.

The character controllers are also perhaps too basic and don't reflect real usage well, and the movement code isn't great and it doesn't support WASD.

## Solution

- Put all movement systems in `Update` and use delta time (alternatively, you could send input events in `Update` and have another system handle the movement in the `PhysicsSchedule`)
- Refactor input handling to be cleaner and to support WASD as well as arrow keys
- Refactor character controller examples with `CharacterControllerBundle` and configuration options
- Fix some meshes not being rendered due to `clone_weak`
  • Loading branch information
Jondolf authored Oct 29, 2023
1 parent 7fe00b8 commit f087a03
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 157 deletions.
4 changes: 2 additions & 2 deletions crates/bevy_xpbd_2d/examples/chain_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fn setup(
FollowMouse,
MaterialMesh2dBundle {
mesh: particle_mesh.clone(),
material: particle_material.clone_weak(),
material: particle_material.clone(),
..default()
},
))
Expand All @@ -57,7 +57,7 @@ fn setup(
MassPropertiesBundle::new_computed(&Collider::ball(particle_radius), 1.0),
MaterialMesh2dBundle {
mesh: particle_mesh.clone(),
material: particle_material.clone_weak(),
material: particle_material.clone(),
transform: Transform::from_xyz(
0.0,
i as f32 * (particle_radius as f32 * 2.0 + 1.0),
Expand Down
22 changes: 14 additions & 8 deletions crates/bevy_xpbd_2d/examples/many_shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,27 @@ fn setup(
}

fn movement(
time: Res<Time>,
keyboard_input: Res<Input<KeyCode>>,
mut marbles: Query<&mut LinearVelocity, With<Controllable>>,
) {
// Precision is adjusted so that the example works with
// both the `f32` and `f64` features. Otherwise you don't need this.
let delta_time = time.delta_seconds_f64().adjust_precision();

for mut linear_velocity in &mut marbles {
if keyboard_input.pressed(KeyCode::Up) {
linear_velocity.y += 50.0;
if keyboard_input.any_pressed([KeyCode::W, KeyCode::Up]) {
// Use a higher acceleration for upwards movement to overcome gravity
linear_velocity.y += 2500.0 * delta_time;
}
if keyboard_input.pressed(KeyCode::Down) {
linear_velocity.y -= 10.0;
if keyboard_input.any_pressed([KeyCode::S, KeyCode::Down]) {
linear_velocity.y -= 500.0 * delta_time;
}
if keyboard_input.pressed(KeyCode::Left) {
linear_velocity.x -= 10.0;
if keyboard_input.any_pressed([KeyCode::A, KeyCode::Left]) {
linear_velocity.x -= 500.0 * delta_time;
}
if keyboard_input.pressed(KeyCode::Right) {
linear_velocity.x += 10.0;
if keyboard_input.any_pressed([KeyCode::D, KeyCode::Right]) {
linear_velocity.x += 500.0 * delta_time;
}
}
}
24 changes: 15 additions & 9 deletions crates/bevy_xpbd_2d/examples/move_marbles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fn setup(
let marble_material = materials.add(ColorMaterial::from(Color::rgb(0.2, 0.7, 0.9)));

// Spawn stacks of marbles
for x in -21..21 {
for x in -20..20 {
for y in -20..20 {
commands.spawn((
MaterialMesh2dBundle {
Expand All @@ -103,21 +103,27 @@ fn setup(
}

fn movement(
time: Res<Time>,
keyboard_input: Res<Input<KeyCode>>,
mut marbles: Query<&mut LinearVelocity, With<Marble>>,
) {
// Precision is adjusted so that the example works with
// both the `f32` and `f64` features. Otherwise you don't need this.
let delta_time = time.delta_seconds_f64().adjust_precision();

for mut linear_velocity in &mut marbles {
if keyboard_input.pressed(KeyCode::Up) {
linear_velocity.y += 50.0;
if keyboard_input.any_pressed([KeyCode::W, KeyCode::Up]) {
// Use a higher acceleration for upwards movement to overcome gravity
linear_velocity.y += 2500.0 * delta_time;
}
if keyboard_input.pressed(KeyCode::Down) {
linear_velocity.y -= 10.0;
if keyboard_input.any_pressed([KeyCode::S, KeyCode::Down]) {
linear_velocity.y -= 500.0 * delta_time;
}
if keyboard_input.pressed(KeyCode::Left) {
linear_velocity.x -= 10.0;
if keyboard_input.any_pressed([KeyCode::A, KeyCode::Left]) {
linear_velocity.x -= 500.0 * delta_time;
}
if keyboard_input.pressed(KeyCode::Right) {
linear_velocity.x += 10.0;
if keyboard_input.any_pressed([KeyCode::D, KeyCode::Right]) {
linear_velocity.x += 500.0 * delta_time;
}
}
}
26 changes: 17 additions & 9 deletions crates/bevy_xpbd_2d/examples/one_way_platform_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ fn main() {
#[derive(Component)]
struct Actor;

#[derive(Component)]
struct MovementSpeed(Scalar);

#[derive(Component)]
struct JumpImpulse(Scalar);

#[derive(Clone, Eq, PartialEq, Debug, Default, Component)]
pub struct OneWayPlatform(HashSet<Entity>);

Expand Down Expand Up @@ -136,20 +142,22 @@ fn setup(
Collider::cuboid(actor_size.x, actor_size.y),
Actor,
PassThroughOneWayPlatform::ByNormal,
MovementSpeed(250.0),
JumpImpulse(450.0),
));
}

fn movement(
keyboard_input: Res<Input<KeyCode>>,
mut actors: Query<&mut LinearVelocity, With<Actor>>,
mut actors: Query<(&mut LinearVelocity, &MovementSpeed, &JumpImpulse), With<Actor>>,
) {
for mut linear_velocity in &mut actors {
if keyboard_input.pressed(KeyCode::Left) {
linear_velocity.x -= 10.0;
}
if keyboard_input.pressed(KeyCode::Right) {
linear_velocity.x += 10.0;
}
for (mut linear_velocity, movement_speed, jump_impulse) in &mut actors {
let left = keyboard_input.any_pressed([KeyCode::A, KeyCode::Left]);
let right = keyboard_input.any_pressed([KeyCode::D, KeyCode::Right]);
let horizontal = right as i8 - left as i8;

// Move in input direction
linear_velocity.x = horizontal as Scalar * movement_speed.0;

// Assume "mostly stopped" to mean "grounded".
// You should use raycasting, shapecasting or sensor colliders
Expand All @@ -158,7 +166,7 @@ fn movement(
&& !keyboard_input.pressed(KeyCode::Down)
&& keyboard_input.just_pressed(KeyCode::Space)
{
linear_velocity.y = 450.0;
linear_velocity.y = jump_impulse.0;
}
}
}
Expand Down
132 changes: 92 additions & 40 deletions crates/bevy_xpbd_3d/examples/basic_dynamic_character.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,69 @@
//! For a kinematic character controller, see the `basic_kinematic_character` example.

use bevy::prelude::*;
use bevy_xpbd_3d::{math::*, prelude::*, PhysicsSchedule, PhysicsStepSet};
use bevy_xpbd_3d::{math::*, prelude::*};

fn main() {
App::new()
.add_plugins((DefaultPlugins, PhysicsPlugins::default()))
.add_systems(Startup, setup)
.add_systems(PhysicsSchedule, movement.before(PhysicsStepSet::BroadPhase))
.add_systems(Update, movement)
.run();
}

/// The acceleration used for character movement.
#[derive(Component)]
struct Player;
struct MovementAcceleration(Scalar);

/// The damping factor used for slowing down movement.
#[derive(Component)]
struct MovementDampingFactor(Scalar);

/// The strength of a jump.
#[derive(Component)]
struct JumpImpulse(Scalar);

/// A bundle that contains the components needed for a basic
/// dynamic character controller.
#[derive(Bundle)]
struct CharacterControllerBundle {
rigid_body: RigidBody,
collider: Collider,
ground_caster: ShapeCaster,
locked_axes: LockedAxes,
movement_acceleration: MovementAcceleration,
movement_damping: MovementDampingFactor,
jump_impulse: JumpImpulse,
}

impl CharacterControllerBundle {
fn new(
acceleration: Scalar,
damping: Scalar,
jump_impulse: Scalar,
collider: Collider,
) -> Self {
// Create shape caster as a slightly smaller version of the collider
let mut caster_shape = collider.clone();
caster_shape.set_scale(Vector::ONE * 0.99, 10);

Self {
rigid_body: RigidBody::Dynamic,
locked_axes: LockedAxes::ROTATION_LOCKED,
collider,
ground_caster: ShapeCaster::new(
caster_shape,
Vector::ZERO,
Quaternion::default(),
Vector::NEG_Y,
)
.with_max_time_of_impact(0.2),
movement_acceleration: MovementAcceleration(acceleration),
movement_damping: MovementDampingFactor(damping),
jump_impulse: JumpImpulse(jump_impulse),
}
}
}

fn setup(
mut commands: Commands,
Expand All @@ -44,25 +95,13 @@ fn setup(
..default()
})),
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
transform: Transform::from_xyz(0.0, 1.0, 0.0),
transform: Transform::from_xyz(0.0, 1.5, 0.0),
..default()
},
RigidBody::Dynamic,
Collider::capsule(1.0, 0.4),
// Prevent the player from falling over
LockedAxes::new().lock_rotation_x().lock_rotation_z(),
// Cast the player shape downwards to detect when the player is grounded
ShapeCaster::new(
Collider::capsule(0.9, 0.35),
Vector::NEG_Y * 0.05,
Quaternion::default(),
Vector::NEG_Y,
)
.with_max_time_of_impact(0.2)
.with_max_hits(1),
Restitution::new(0.0).with_combine_rule(CoefficientCombine::Min),
CharacterControllerBundle::new(30.0, 0.9, 8.0, Collider::capsule(1.0, 0.4)),
Friction::ZERO.with_combine_rule(CoefficientCombine::Min),
Restitution::ZERO.with_combine_rule(CoefficientCombine::Min),
GravityScale(2.0),
Player,
));

// Light
Expand All @@ -84,31 +123,44 @@ fn setup(
}

fn movement(
time: Res<Time>,
keyboard_input: Res<Input<KeyCode>>,
mut players: Query<(&mut LinearVelocity, &ShapeHits), With<Player>>,
mut controllers: Query<(
&MovementAcceleration,
&MovementDampingFactor,
&JumpImpulse,
&ShapeHits,
&mut LinearVelocity,
)>,
) {
for (mut linear_velocity, ground_hits) in &mut players {
// Directional movement
if keyboard_input.pressed(KeyCode::W) || keyboard_input.pressed(KeyCode::Up) {
linear_velocity.z -= 1.2;
}
if keyboard_input.pressed(KeyCode::S) || keyboard_input.pressed(KeyCode::Down) {
linear_velocity.z += 1.2;
}
if keyboard_input.pressed(KeyCode::A) || keyboard_input.pressed(KeyCode::Left) {
linear_velocity.x -= 1.2;
}
if keyboard_input.pressed(KeyCode::D) || keyboard_input.pressed(KeyCode::Right) {
linear_velocity.x += 1.2;
}
// Precision is adjusted so that the example works with
// both the `f32` and `f64` features. Otherwise you don't need this.
let delta_time = time.delta_seconds_f64().adjust_precision();

// Jump if space pressed and the player is close enough to the ground
for (movement_acceleration, damping_factor, jump_impulse, ground_hits, mut linear_velocity) in
&mut controllers
{
let up = keyboard_input.any_pressed([KeyCode::W, KeyCode::Up]);
let down = keyboard_input.any_pressed([KeyCode::S, KeyCode::Down]);
let left = keyboard_input.any_pressed([KeyCode::A, KeyCode::Left]);
let right = keyboard_input.any_pressed([KeyCode::D, KeyCode::Right]);

let horizontal = right as i8 - left as i8;
let vertical = down as i8 - up as i8;
let direction =
Vector::new(horizontal as Scalar, 0.0, vertical as Scalar).normalize_or_zero();

// Move in input direction
linear_velocity.x += direction.x * movement_acceleration.0 * delta_time;
linear_velocity.z += direction.z * movement_acceleration.0 * delta_time;

// Apply movement damping
linear_velocity.x *= damping_factor.0;
linear_velocity.z *= damping_factor.0;

// Jump if Space is pressed and the player is close enough to the ground
if keyboard_input.just_pressed(KeyCode::Space) && !ground_hits.is_empty() {
linear_velocity.y += 8.0;
linear_velocity.y = jump_impulse.0;
}

// Slow player down on the x and y axes
linear_velocity.x *= 0.8;
linear_velocity.z *= 0.8;
}
}
Loading

0 comments on commit f087a03

Please sign in to comment.