Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bevy_fps_controller"
version = "17.1.0"
version = "19.0.0"
edition = "2021"
authors = ["bevy_fps_controller"]
repository = "https://github.com/qhdwight/bevy_fps_controller"
Expand All @@ -10,9 +10,9 @@ readme = "README.md"
description = "Bevy plugin that adds a Source engine inspired FPS movement controller"

[dependencies]
bevy = "0.17"
bevy_rapier3d = { version = "0.32", optional = true }
avian3d = { version = "0.4", optional = true }
bevy = "0.19"
bevy_rapier3d = { version = "0.36", optional = true }
avian3d = { version = "0.7", optional = true }

[features]
default = []
Expand Down
18 changes: 10 additions & 8 deletions examples/minimal_avian.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ const SPAWN_POINT: Vec3 = Vec3::new(0.0, 1.625, 0.0);

fn main() {
App::new()
.insert_resource(AmbientLight {
color: Color::WHITE,
.insert_resource(GlobalAmbientLight {
brightness: 10000.0,
affects_lightmapped_meshes: true,
..default()
})
.insert_resource(ClearColor(Color::linear_rgb(0.83, 0.96, 0.96)))
.add_plugins(DefaultPlugins)
Expand All @@ -39,7 +38,7 @@ fn setup(mut commands: Commands, mut window: Query<&mut Window>, assets: Res<Ass
commands.spawn((
DirectionalLight {
illuminance: light_consts::lux::FULL_DAYLIGHT,
shadows_enabled: true,
shadow_maps_enabled: true,
..default()
},
Transform::from_xyz(4.0, 7.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
Expand Down Expand Up @@ -107,8 +106,8 @@ fn setup(mut commands: Commands, mut window: Query<&mut Window>, assets: Res<Ass
commands.spawn((
Text(String::from("")),
TextFont {
font: assets.load("fira_mono.ttf"),
font_size: 24.0,
font: assets.load("fira_mono.ttf").into(),
font_size: FontSize::Px(24.0),
..default()
},
TextColor(Color::BLACK),
Expand Down Expand Up @@ -154,15 +153,18 @@ fn scene_colliders(

if let Some(gltf) = gltf {
let scene = gltf.scenes.first().unwrap().clone();
commands.spawn(SceneRoot(scene));
commands.spawn(WorldAssetRoot(scene));
for node in &gltf.nodes {
let node = gltf_node_assets.get(node).unwrap();
if let Some(gltf_mesh) = node.mesh.clone() {
let gltf_mesh = gltf_mesh_assets.get(&gltf_mesh).unwrap();
for mesh_primitive in &gltf_mesh.primitives {
let mesh = mesh_assets.get(&mesh_primitive.mesh).unwrap();
commands.spawn((
Collider::trimesh_from_mesh(mesh).unwrap(),
// `TrimeshFlags::all()` includes `FIX_INTERNAL_EDGES`, which prevents
// ghost collisions against the internal edges of the level mesh
Collider::trimesh_from_mesh_with_config(mesh, TrimeshFlags::all())
.unwrap(),
RigidBody::Static,
node.transform,
));
Expand Down
23 changes: 11 additions & 12 deletions examples/minimal_rapier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ const SPAWN_POINT: Vec3 = Vec3::new(0.0, 1.625, 0.0);

fn main() {
App::new()
.insert_resource(AmbientLight {
color: Color::WHITE,
.insert_resource(GlobalAmbientLight {
brightness: 10000.0,
affects_lightmapped_meshes: true,
..default()
})
.insert_resource(ClearColor(Color::linear_rgb(0.83, 0.96, 0.96)))
.add_plugins(DefaultPlugins)
Expand All @@ -39,7 +38,7 @@ fn setup(mut commands: Commands, mut window: Query<&mut Window>, assets: Res<Ass
commands.spawn((
DirectionalLight {
illuminance: light_consts::lux::FULL_DAYLIGHT,
shadows_enabled: true,
shadow_maps_enabled: true,
..default()
},
Transform::from_xyz(4.0, 7.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
Expand Down Expand Up @@ -109,8 +108,8 @@ fn setup(mut commands: Commands, mut window: Query<&mut Window>, assets: Res<Ass
commands.spawn((
Text(String::from("")),
TextFont {
font: assets.load("fira_mono.ttf"),
font_size: 24.0,
font: assets.load("fira_mono.ttf").into(),
font_size: FontSize::Px(24.0),
..default()
},
TextColor(Color::BLACK),
Expand All @@ -129,7 +128,7 @@ fn respawn(mut query: Query<(&mut Transform, &mut Velocity)>) {
continue;
}

velocity.linvel = Vec3::ZERO;
velocity.linear = Vec3::ZERO;
transform.translation = SPAWN_POINT;
}
}
Expand All @@ -156,7 +155,7 @@ fn scene_colliders(

if let Some(gltf) = gltf {
let scene = gltf.scenes.first().unwrap().clone();
commands.spawn(SceneRoot(scene));
commands.spawn(WorldAssetRoot(scene));
for node in &gltf.nodes {
let node = gltf_node_assets.get(node).unwrap();
if let Some(gltf_mesh) = node.mesh.clone() {
Expand Down Expand Up @@ -209,13 +208,13 @@ fn display_text(
for mut text in &mut text_query {
text.0 = format!(
"vel: {:.2}, {:.2}, {:.2}\npos: {:.2}, {:.2}, {:.2}\nspd: {:.2}",
velocity.linvel.x,
velocity.linvel.y,
velocity.linvel.z,
velocity.linear.x,
velocity.linear.y,
velocity.linear.z,
transform.translation.x,
transform.translation.y,
transform.translation.z,
velocity.linvel.xz().length()
velocity.linear.xz().length()
);
}
}
Expand Down
50 changes: 28 additions & 22 deletions src/controller_avian.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use std::f32::consts::*;

use avian3d::{
parry::{math::Point, shape::SharedShape},
prelude::*,
};
use avian3d::{parry::shape::SharedShape, prelude::*};
use bevy::{input::mouse::MouseMotion, math::Vec3Swizzles, prelude::*};

pub struct FpsControllerPlugin;
Expand All @@ -14,7 +11,12 @@ impl Plugin for FpsControllerPlugin {
.add_systems(PreUpdate, clear_fixed_timestep_flag)
.add_systems(
FixedPreUpdate,
(set_fixed_time_step_flag, fps_controller_move),
(
set_fixed_time_step_flag,
fps_controller_move,
fps_controller_update_collider,
)
.chain(),
)
.add_systems(
RunFixedMainLoop,
Expand Down Expand Up @@ -251,13 +253,13 @@ pub fn fps_controller_look(mut query: Query<(&mut FpsController, &FpsControllerI

pub fn fps_controller_move(
time: Res<Time<Fixed>>,
spatial_query_pipeline: Res<SpatialQueryPipeline>,
spatial_query_pipeline: SpatialQuery,
mut query: Query<
(
Entity,
&FpsControllerInput,
&mut FpsController,
&mut Collider,
&Collider,
&mut Transform,
&mut LinearVelocity,
),
Expand All @@ -266,9 +268,7 @@ pub fn fps_controller_move(
) {
let dt = time.delta_secs();

for (entity, input, mut controller, mut collider, mut transform, mut velocity) in
query.iter_mut()
{
for (entity, input, mut controller, collider, mut transform, mut velocity) in query.iter_mut() {
controller.previous_translation = Some(transform.translation);

if input.fly {
Expand Down Expand Up @@ -410,17 +410,6 @@ pub fn fps_controller_move(
controller.height += dt * crouch_speed;
controller.height = controller.height.clamp(crouch_height, upright_height);

if let Some(capsule) = collider.shape().as_capsule() {
let radius = capsule.radius;
let half = Point::from(Vec3::Y * (controller.height * 0.5 - radius));
collider.set_shape(SharedShape::capsule(-half, half, radius));
} else if let Some(cylinder) = collider.shape().as_cylinder() {
let radius = cylinder.radius;
collider.set_shape(SharedShape::cylinder(controller.height * 0.5, radius));
} else {
panic!("Controller must use a cylinder or capsule collider")
}

// Step offset really only works best for cylinders
// For capsules the player has to practically teleported to fully step up
if collider.shape().as_cylinder().is_some()
Expand Down Expand Up @@ -520,7 +509,7 @@ fn overhang_component(
entity: Entity,
collider: &Collider,
transform: &Transform,
spatial_query: &SpatialQueryPipeline,
spatial_query: &SpatialQuery,
velocity: Vec3,
dt: f32,
) -> Option<Vec3> {
Expand Down Expand Up @@ -590,6 +579,23 @@ fn get_axis(key_input: &Res<ButtonInput<KeyCode>>, key_pos: KeyCode, key_neg: Ke
get_pressed(key_input, key_pos) - get_pressed(key_input, key_neg)
}

pub fn fps_controller_update_collider(
mut query: Query<(&FpsController, &mut Collider), With<LogicalPlayer>>,
) {
for (controller, mut collider) in query.iter_mut() {
if let Some(capsule) = collider.shape().as_capsule() {
let radius = capsule.radius;
let half = Vec3::Y * (controller.height * 0.5 - radius);
collider.set_shape(SharedShape::capsule(-half, half, radius));
} else if let Some(cylinder) = collider.shape().as_cylinder() {
let radius = cylinder.radius;
collider.set_shape(SharedShape::cylinder(controller.height * 0.5, radius));
} else {
panic!("Controller must use a cylinder or capsule collider")
}
}
}

// ____ __
// / __ \___ ____ ____/ /__ _____
// / /_/ / _ \/ __ \/ __ / _ \/ ___/
Expand Down
50 changes: 25 additions & 25 deletions src/controller_rapier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,9 @@ pub fn fps_controller_move(
MoveMode::Noclip => {
if input.movement == Vec3::ZERO {
let friction = controller.fly_friction.clamp(0.0, 1.0);
velocity.linvel *= 1.0 - friction;
if velocity.linvel.length_squared() < f32::EPSILON {
velocity.linvel = Vec3::ZERO;
velocity.linear *= 1.0 - friction;
if velocity.linear.length_squared() < f32::EPSILON {
velocity.linear = Vec3::ZERO;
}
} else {
let fly_speed = if input.sprint {
Expand All @@ -291,7 +291,7 @@ pub fn fps_controller_move(
Mat3::from_euler(EulerRot::YXZ, input.yaw, input.pitch, 0.0);
move_to_world.z_axis *= -1.0; // Forward is -Z
move_to_world.y_axis = Vec3::Y; // Vertical movement aligned with world up
velocity.linvel = move_to_world * input.movement * fly_speed;
velocity.linear = move_to_world * input.movement * fly_speed;
}
}
MoveMode::Ground => {
Expand Down Expand Up @@ -336,40 +336,40 @@ pub fn fps_controller_move(

// Only apply friction after at least one tick, allows b-hopping without losing speed
if controller.ground_tick >= 1 && has_traction {
let lateral_speed = velocity.linvel.xz().length();
let lateral_speed = velocity.linear.xz().length();
if lateral_speed > controller.friction_speed_cutoff {
let control = f32::max(lateral_speed, controller.stop_speed);
let drop = control * controller.friction * dt;
let new_speed = f32::max((lateral_speed - drop) / lateral_speed, 0.0);
velocity.linvel.x *= new_speed;
velocity.linvel.z *= new_speed;
velocity.linear.x *= new_speed;
velocity.linear.z *= new_speed;
} else {
velocity.linvel = Vec3::ZERO;
velocity.linear = Vec3::ZERO;
}
if controller.ground_tick == 1 {
velocity.linvel.y = -hit.time_of_impact;
velocity.linear.y = -hit.time_of_impact;
}
}

let mut add = acceleration(
wish_direction,
wish_speed,
controller.acceleration,
velocity.linvel,
velocity.linear,
dt,
);
if !has_traction {
add.y -= controller.gravity * dt;
}
velocity.linvel += add;
velocity.linear += add;

if has_traction {
let linear_velocity = velocity.linvel;
velocity.linvel -=
let linear_velocity = velocity.linear;
velocity.linear -=
Vec3::dot(linear_velocity, hit_details.normal1) * hit_details.normal1;

if input.jump {
velocity.linvel.y = controller.jump_speed;
velocity.linear.y = controller.jump_speed;
}
}

Expand All @@ -383,17 +383,17 @@ pub fn fps_controller_move(
wish_direction,
wish_speed,
controller.air_acceleration,
velocity.linvel,
velocity.linear,
dt,
);
add.y = -controller.gravity * dt;
velocity.linvel += add;
velocity.linear += add;

let air_speed = velocity.linvel.xz().length();
let air_speed = velocity.linear.xz().length();
if air_speed > controller.max_air_speed {
let ratio = controller.max_air_speed / air_speed;
velocity.linvel.x *= ratio;
velocity.linvel.z *= ratio;
velocity.linear.x *= ratio;
velocity.linear.z *= ratio;
}
}

Expand Down Expand Up @@ -428,7 +428,7 @@ pub fn fps_controller_move(
{
// Try putting the player forward, but instead lifted upward by the step offset
// If we can find a surface below us, we can adjust our position to be on top of it
let future_position = transform.translation + velocity.linvel * dt;
let future_position = transform.translation + velocity.linear * dt;
let future_position_lifted = future_position + Vec3::Y * controller.step_offset;
let cast = physics_context.single().unwrap().cast_shape(
future_position_lifted,
Expand Down Expand Up @@ -458,11 +458,11 @@ pub fn fps_controller_move(
&collider,
transform.as_ref(),
&physics_context,
velocity.linvel,
velocity.linear,
dt,
);
if let Some(overhang) = overhang {
velocity.linvel -= overhang;
velocity.linear -= overhang;
}
}
// If we are still overhanging consider unsolvable and freeze
Expand All @@ -471,12 +471,12 @@ pub fn fps_controller_move(
&collider,
transform.as_ref(),
&physics_context,
velocity.linvel,
velocity.linear,
dt,
)
.is_some()
{
velocity.linvel = Vec3::ZERO;
velocity.linear = Vec3::ZERO;
}
}
}
Expand Down Expand Up @@ -552,7 +552,7 @@ fn overhang_component(
let cast = physics_context.single().unwrap().cast_ray(
future_position + Vec3::Y * 0.125,
-Vec3::Y,
0.375.into(),
0.375_f32.into(),
false,
filter,
);
Expand Down
Loading