Skip to content

Commit

Permalink
FPS Character example (#476)
Browse files Browse the repository at this point in the history
Co-authored-by: Thierry Berger <contact@thierryberger.com>
  • Loading branch information
ManevilleF and Vrixyz committed Jun 17, 2024
1 parent b77d846 commit f850272
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 2 deletions.
9 changes: 7 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ name: Rust

on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]

env:
CARGO_TERM_COLOR: always
RUST_CACHE_KEY: rust-cache-20240617

jobs:
check-fmt:
Expand All @@ -29,6 +30,8 @@ jobs:
with:
components: clippy
- uses: Swatinem/rust-cache@v2
with:
prefix-key: ${{ env.RUST_CACHE_KEY }}
- run: sudo apt update && sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev
- name: Clippy for bevy_rapier2d
run: cargo clippy --verbose -p bevy_rapier2d
Expand All @@ -53,6 +56,8 @@ jobs:
components: clippy
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
with:
prefix-key: ${{ env.RUST_CACHE_KEY }}
- name: Clippy bevy_rapier2d
run: cd bevy_rapier2d && cargo clippy --verbose --features wasm-bindgen,bevy/webgl2 --target wasm32-unknown-unknown
- name: Clippy bevy_rapier3d
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and new features. Please have a look at the

- Derive `Debug` for `LockedAxes`.
- Expose `is_sliding_down_slope` to both `MoveShapeOutput` and `KinematicCharacterControllerOutput`.
- Added a First Person Shooter `character_controller` example for `bevy_rapier3d`.

### Fix

Expand Down
217 changes: 217 additions & 0 deletions bevy_rapier3d/examples/character_controller3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
use bevy::{
input::{mouse::MouseMotion, InputSystem},
prelude::*,
};
use bevy_rapier3d::{control::KinematicCharacterController, prelude::*};

const MOUSE_SENSITIVITY: f32 = 0.3;
const GROUND_TIMER: f32 = 0.5;
const MOVEMENT_SPEED: f32 = 8.0;
const JUMP_SPEED: f32 = 20.0;
const GRAVITY: f32 = -9.81;

fn main() {
App::new()
.insert_resource(ClearColor(Color::srgb(
0xF9 as f32 / 255.0,
0xF9 as f32 / 255.0,
0xFF as f32 / 255.0,
)))
.init_resource::<MovementInput>()
.init_resource::<LookInput>()
.add_plugins((
DefaultPlugins,
RapierPhysicsPlugin::<NoUserData>::default(),
RapierDebugRenderPlugin::default(),
))
.add_systems(Startup, (setup_player, setup_map))
.add_systems(PreUpdate, handle_input.after(InputSystem))
.add_systems(Update, player_look)
.add_systems(FixedUpdate, player_movement)
.run();
}

pub fn setup_player(mut commands: Commands) {
commands
.spawn((
SpatialBundle {
transform: Transform::from_xyz(0.0, 5.0, 0.0),
..default()
},
Collider::round_cylinder(0.9, 0.3, 0.2),
KinematicCharacterController {
custom_mass: Some(5.0),
up: Vec3::Y,
offset: CharacterLength::Absolute(0.01),
slide: true,
autostep: Some(CharacterAutostep {
max_height: CharacterLength::Relative(0.3),
min_width: CharacterLength::Relative(0.5),
include_dynamic_bodies: false,
}),
// Don’t allow climbing slopes larger than 45 degrees.
max_slope_climb_angle: 45.0_f32.to_radians(),
// Automatically slide down on slopes smaller than 30 degrees.
min_slope_slide_angle: 30.0_f32.to_radians(),
apply_impulse_to_dynamic_bodies: true,
snap_to_ground: None,
..default()
},
))
.with_children(|b| {
// FPS Camera
b.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 0.2, -0.1),
..Default::default()
});
});
}

fn setup_map(mut commands: Commands) {
/*
* Ground
*/
let ground_size = 50.0;
let ground_height = 0.1;

commands.spawn((
TransformBundle::from(Transform::from_xyz(0.0, -ground_height, 0.0)),
Collider::cuboid(ground_size, ground_height, ground_size),
));
/*
* Stairs
*/
let stair_len = 30;
let stair_step = 0.2;
for i in 1..=stair_len {
let step = i as f32;
let collider = Collider::cuboid(1.0, step * stair_step, 1.0);
commands.spawn((
TransformBundle::from(Transform::from_xyz(
40.0,
step * stair_step,
step * 2.0 - 20.0,
)),
collider.clone(),
));
commands.spawn((
TransformBundle::from(Transform::from_xyz(
-40.0,
step * stair_step,
step * -2.0 + 20.0,
)),
collider.clone(),
));
commands.spawn((
TransformBundle::from(Transform::from_xyz(
step * 2.0 - 20.0,
step * stair_step,
40.0,
)),
collider.clone(),
));
commands.spawn((
TransformBundle::from(Transform::from_xyz(
step * -2.0 + 20.0,
step * stair_step,
-40.0,
)),
collider.clone(),
));
}
}

/// Keyboard input vector
#[derive(Default, Resource, Deref, DerefMut)]
struct MovementInput(Vec3);

/// Mouse input vector
#[derive(Default, Resource, Deref, DerefMut)]
struct LookInput(Vec2);

fn handle_input(
keyboard: Res<ButtonInput<KeyCode>>,
mut movement: ResMut<MovementInput>,
mut look: ResMut<LookInput>,
mut mouse_events: EventReader<MouseMotion>,
) {
if keyboard.pressed(KeyCode::KeyW) {
movement.z -= 1.0;
}
if keyboard.pressed(KeyCode::KeyS) {
movement.z += 1.0
}
if keyboard.pressed(KeyCode::KeyA) {
movement.x -= 1.0;
}
if keyboard.pressed(KeyCode::KeyD) {
movement.x += 1.0
}
**movement = movement.normalize_or_zero();
if keyboard.pressed(KeyCode::ShiftLeft) {
**movement *= 2.0;
}
if keyboard.pressed(KeyCode::Space) {
movement.y = 1.0;
}

for event in mouse_events.read() {
look.x -= event.delta.x * MOUSE_SENSITIVITY;
look.y -= event.delta.y * MOUSE_SENSITIVITY;
look.y = look.y.clamp(-89.9, 89.9); // Limit pitch
}
}

fn player_movement(
time: Res<Time>,
mut input: ResMut<MovementInput>,
mut player: Query<(
&mut Transform,
&mut KinematicCharacterController,
Option<&KinematicCharacterControllerOutput>,
)>,
mut vertical_movement: Local<f32>,
mut grounded_timer: Local<f32>,
) {
let Ok((transform, mut controller, output)) = player.get_single_mut() else {
return;
};
let delta_time = time.delta_seconds();
// Retrieve input
let mut movement = Vec3::new(input.x, 0.0, input.z) * MOVEMENT_SPEED;
let jump_speed = input.y * JUMP_SPEED;
// Clear input
**input = Vec3::ZERO;
// Check physics ground check
if output.map(|o| o.grounded).unwrap_or(false) {
*grounded_timer = GROUND_TIMER;
*vertical_movement = 0.0;
}
// If we are grounded we can jump
if *grounded_timer > 0.0 {
*grounded_timer -= delta_time;
// If we jump we clear the grounded tolerance
if jump_speed > 0.0 {
*vertical_movement = jump_speed;
*grounded_timer = 0.0;
}
}
movement.y = *vertical_movement;
*vertical_movement += GRAVITY * delta_time * controller.custom_mass.unwrap_or(1.0);
controller.translation = Some(transform.rotation * (movement * delta_time));
}

fn player_look(
mut player: Query<&mut Transform, (With<KinematicCharacterController>, Without<Camera>)>,
mut camera: Query<&mut Transform, With<Camera>>,
input: Res<LookInput>,
) {
let Ok(mut transform) = player.get_single_mut() else {
return;
};
transform.rotation = Quat::from_axis_angle(Vec3::Y, input.x.to_radians());
let Ok(mut transform) = camera.get_single_mut() else {
return;
};
transform.rotation = Quat::from_axis_angle(Vec3::X, input.y.to_radians());
}

0 comments on commit f850272

Please sign in to comment.