diff --git a/Cargo.toml b/Cargo.toml index 3adfa5ee5..7502676cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ bevy = { version = "^0.14", default-features = false, features = [ "x11", # "filesystem_watcher", ] } -avian3d = { version = "^0.1", features = ["3d", "debug-plugin", "parallel", "parry-f32"] } +avian3d = { version = "^0.1", git = "https://github.com/Jondolf/avian", features = ["3d", "debug-plugin", "parallel", "parry-f32"] } bevy-tnua-avian3d = { path = "avian3d" } [package.metadata.docs.rs] diff --git a/avian2d/Cargo.toml b/avian2d/Cargo.toml index c890e4aec..7284bac96 100644 --- a/avian2d/Cargo.toml +++ b/avian2d/Cargo.toml @@ -13,7 +13,7 @@ readme = "../README.md" [dependencies] bevy = { version = "^0.14", default-features = false } -avian2d = {version = "^0.1", default-features = false, features = ["2d", "debug-plugin", "parallel"]} +avian2d = {version = "^0.1", git = "https://github.com/Jondolf/avian", default-features = false, features = ["2d", "debug-plugin", "parallel"]} bevy-tnua-physics-integration-layer = { version = "^0.4", path = "../physics-integration-layer" } [package.metadata.docs.rs] diff --git a/avian2d/src/lib.rs b/avian2d/src/lib.rs index ec8aeb9d1..eb04aa207 100644 --- a/avian2d/src/lib.rs +++ b/avian2d/src/lib.rs @@ -22,6 +22,20 @@ use bevy_tnua_physics_integration_layer::*; /// Add this plugin to use avian2d as a physics backend. /// /// This plugin should be used in addition to `TnuaControllerPlugin`. +/// Note that you should make sure both of these plugins use the same schedule. +/// This should usually be `PhysicsSchedule`, which by default is `FixedUpdate`. +/// +/// # Example +/// +/// ```ignore +/// App::new() +/// .add_plugins(( +/// DefaultPlugins, +/// PhysicsPlugins::default(), +/// TnuaControllerPlugin::new(PhysicsSchedule), +/// TnuaAvian2dPlugin::new(PhysicsSchedule), +/// )); +/// ``` pub struct TnuaAvian2dPlugin { schedule: InternedScheduleLabel, } @@ -34,19 +48,12 @@ impl TnuaAvian2dPlugin { } } -impl Default for TnuaAvian2dPlugin { - fn default() -> Self { - Self::new(Update) - } -} - impl Plugin for TnuaAvian2dPlugin { fn build(&self, app: &mut App) { app.configure_sets( self.schedule, TnuaSystemSet - .before(PhysicsSet::Prepare) - .before(PhysicsStepSet::First) + .in_set(PhysicsStepSet::First) .run_if(|physics_time: Res>| !physics_time.is_paused()), ); app.add_systems( @@ -71,24 +78,25 @@ pub struct TnuaAvian2dSensorShape(pub Collider); fn update_rigid_body_trackers_system( gravity: Res, mut query: Query<( - &GlobalTransform, + &Position, + &Rotation, &LinearVelocity, &AngularVelocity, &mut TnuaRigidBodyTracker, Option<&TnuaToggle>, )>, ) { - for (transform, linaer_velocity, angular_velocity, mut tracker, tnua_toggle) in query.iter_mut() + for (position, rotation, linaer_velocity, angular_velocity, mut tracker, tnua_toggle) in + query.iter_mut() { match tnua_toggle.copied().unwrap_or_default() { TnuaToggle::Disabled => continue, TnuaToggle::SenseOnly => {} TnuaToggle::Enabled => {} } - let (_, rotation, translation) = transform.to_scale_rotation_translation(); *tracker = TnuaRigidBodyTracker { - translation: translation.adjust_precision(), - rotation: rotation.adjust_precision(), + translation: position.adjust_precision().extend(0.0), + rotation: Quaternion::from(*rotation).adjust_precision(), velocity: linaer_velocity.0.extend(0.0), angvel: Vector3::new(0.0, 0.0, angular_velocity.0), gravity: gravity.0.extend(0.0), @@ -102,7 +110,9 @@ fn update_proximity_sensors_system( collisions: Res, mut query: Query<( Entity, - &GlobalTransform, + &Position, + &Rotation, + &Collider, &mut TnuaProximitySensor, Option<&TnuaAvian2dSensorShape>, Option<&mut TnuaGhostSensor>, @@ -111,7 +121,7 @@ fn update_proximity_sensors_system( )>, collision_layers_entity: Query<&CollisionLayers>, other_object_query: Query<( - Option<(&GlobalTransform, &LinearVelocity, &AngularVelocity)>, + Option<(&Position, &LinearVelocity, &AngularVelocity)>, Option<&CollisionLayers>, Has, Has, @@ -120,7 +130,9 @@ fn update_proximity_sensors_system( query.par_iter_mut().for_each( |( owner_entity, - transform, + position, + rotation, + collider, mut sensor, shape, mut ghost_sensor, @@ -132,6 +144,11 @@ fn update_proximity_sensors_system( TnuaToggle::SenseOnly => {} TnuaToggle::Enabled => {} } + let transform = Transform { + translation: position.adjust_precision().extend(0.0), + rotation: Quaternion::from(*rotation).adjust_precision(), + scale: collider.scale().adjust_precision().extend(1.0), + }; let cast_origin = transform.transform_point(sensor.cast_origin.f32()); let cast_direction = sensor.cast_direction; let cast_direction_2d = Dir2::new(cast_direction.truncate()) @@ -201,14 +218,14 @@ fn update_proximity_sensors_system( let entity_linvel; let entity_angvel; - if let Some((entity_transform, entity_linear_velocity, entity_angular_velocity)) = + if let Some((entity_position, entity_linear_velocity, entity_angular_velocity)) = entity_kinematic_data { entity_angvel = Vector3::new(0.0, 0.0, entity_angular_velocity.0); entity_linvel = entity_linear_velocity.0.extend(0.0) + if 0.0 < entity_angvel.length_squared() { - let relative_point = intersection_point - - entity_transform.translation().truncate().adjust_precision(); + let relative_point = + intersection_point - entity_position.adjust_precision(); // NOTE: no need to project relative_point on the // rotation plane, it will not affect the cross // product. @@ -257,7 +274,7 @@ fn update_proximity_sensors_system( cast_direction_2d, sensor.cast_range, true, - query_filter, + &query_filter, #[allow(clippy::useless_conversion)] |shape_hit_data| { apply_cast(CastResult { @@ -275,7 +292,7 @@ fn update_proximity_sensors_system( cast_direction_2d, sensor.cast_range, true, - query_filter, + &query_filter, |ray_hit_data| { apply_cast(CastResult { entity: ray_hit_data.entity, diff --git a/avian3d/Cargo.toml b/avian3d/Cargo.toml index 825913529..3f49c0a5f 100644 --- a/avian3d/Cargo.toml +++ b/avian3d/Cargo.toml @@ -13,7 +13,7 @@ readme = "../README.md" [dependencies] bevy = { version = "^0.14", default-features = false } -avian3d = {version = "^0.1", default-features = false, features = ["3d", "debug-plugin", "parallel"] } +avian3d = {version = "^0.1", git = "https://github.com/Jondolf/avian", default-features = false, features = ["3d", "debug-plugin", "parallel"] } bevy-tnua-physics-integration-layer = { version = "^0.4", path = "../physics-integration-layer" } [package.metadata.docs.rs] diff --git a/avian3d/src/lib.rs b/avian3d/src/lib.rs index d636a1f71..c0d31a853 100644 --- a/avian3d/src/lib.rs +++ b/avian3d/src/lib.rs @@ -27,6 +27,20 @@ use bevy_tnua_physics_integration_layer::TnuaSystemSet; /// Add this plugin to use avian3d as a physics backend. /// /// This plugin should be used in addition to `TnuaControllerPlugin`. +/// Note that you should make sure both of these plugins use the same schedule. +/// This should usually be `PhysicsSchedule`, which by default is `FixedUpdate`. +/// +/// # Example +/// +/// ```ignore +/// App::new() +/// .add_plugins(( +/// DefaultPlugins, +/// PhysicsPlugins::default(), +/// TnuaControllerPlugin::new(PhysicsSchedule), +/// TnuaAvian3dPlugin::new(PhysicsSchedule), +/// )); +/// ``` pub struct TnuaAvian3dPlugin { schedule: InternedScheduleLabel, } @@ -50,8 +64,7 @@ impl Plugin for TnuaAvian3dPlugin { app.configure_sets( self.schedule, TnuaSystemSet - .before(PhysicsSet::Prepare) - .before(PhysicsStepSet::First) + .in_set(PhysicsStepSet::First) .run_if(|physics_time: Res>| !physics_time.is_paused()), ); app.add_systems( @@ -76,23 +89,24 @@ pub struct TnuaAvian3dSensorShape(pub Collider); fn update_rigid_body_trackers_system( gravity: Res, mut query: Query<( - &GlobalTransform, + &Position, + &Rotation, &LinearVelocity, &AngularVelocity, &mut TnuaRigidBodyTracker, Option<&TnuaToggle>, )>, ) { - for (transform, linaer_velocity, angular_velocity, mut tracker, tnua_toggle) in query.iter_mut() + for (position, rotation, linaer_velocity, angular_velocity, mut tracker, tnua_toggle) in + query.iter_mut() { match tnua_toggle.copied().unwrap_or_default() { TnuaToggle::Disabled => continue, TnuaToggle::SenseOnly => {} TnuaToggle::Enabled => {} } - let (_, rotation, translation) = transform.to_scale_rotation_translation(); *tracker = TnuaRigidBodyTracker { - translation: translation.adjust_precision(), + translation: position.adjust_precision(), rotation: rotation.adjust_precision(), velocity: linaer_velocity.0.adjust_precision(), angvel: angular_velocity.0.adjust_precision(), @@ -107,7 +121,9 @@ fn update_proximity_sensors_system( collisions: Res, mut query: Query<( Entity, - &GlobalTransform, + &Position, + &Rotation, + &Collider, &mut TnuaProximitySensor, Option<&TnuaAvian3dSensorShape>, Option<&mut TnuaGhostSensor>, @@ -116,7 +132,7 @@ fn update_proximity_sensors_system( )>, collision_layers_entity: Query<&CollisionLayers>, other_object_query: Query<( - Option<(&GlobalTransform, &LinearVelocity, &AngularVelocity)>, + Option<(&Position, &LinearVelocity, &AngularVelocity)>, Option<&CollisionLayers>, Has, Has, @@ -125,7 +141,9 @@ fn update_proximity_sensors_system( query.par_iter_mut().for_each( |( owner_entity, - transform, + position, + rotation, + collider, mut sensor, shape, mut ghost_sensor, @@ -137,6 +155,11 @@ fn update_proximity_sensors_system( TnuaToggle::SenseOnly => {} TnuaToggle::Enabled => {} } + let transform = Transform { + translation: position.0, + rotation: rotation.0, + scale: collider.scale(), + }; // TODO: is there any point in doing these transformations as f64 when that feature // flag is active? @@ -206,14 +229,14 @@ fn update_proximity_sensors_system( let entity_linvel; let entity_angvel; - if let Some((entity_transform, entity_linear_velocity, entity_angular_velocity)) = + if let Some((entity_position, entity_linear_velocity, entity_angular_velocity)) = entity_kinematic_data { entity_angvel = entity_angular_velocity.0.adjust_precision(); entity_linvel = entity_linear_velocity.0.adjust_precision() + if 0.0 < entity_angvel.length_squared() { - let relative_point = intersection_point - - entity_transform.translation().adjust_precision(); + let relative_point = + intersection_point - entity_position.adjust_precision(); // NOTE: no need to project relative_point on the // rotation plane, it will not affect the cross // product. @@ -255,10 +278,9 @@ fn update_proximity_sensors_system( let query_filter = SpatialQueryFilter::from_excluded_entities([owner_entity]); if let Some(TnuaAvian3dSensorShape(shape)) = shape { - let (_, owner_rotation, _) = transform.to_scale_rotation_translation(); let owner_rotation = Quat::from_axis_angle( *cast_direction, - owner_rotation.to_scaled_axis().dot(*cast_direction), + rotation.to_scaled_axis().dot(*cast_direction), ); spatial_query_pipeline.shape_hits_callback( shape, @@ -267,7 +289,7 @@ fn update_proximity_sensors_system( cast_direction, sensor.cast_range, true, - query_filter, + &query_filter, |shape_hit_data| { apply_cast(CastResult { entity: shape_hit_data.entity, @@ -284,7 +306,7 @@ fn update_proximity_sensors_system( cast_direction, sensor.cast_range, true, - query_filter, + &query_filter, |ray_hit_data| { apply_cast(CastResult { entity: ray_hit_data.entity, diff --git a/demos/Cargo.toml b/demos/Cargo.toml index 1f5aa5811..c6c8d4f70 100644 --- a/demos/Cargo.toml +++ b/demos/Cargo.toml @@ -59,10 +59,10 @@ bevy-tnua-rapier2d = { path = "../rapier2d", optional = true } bevy_rapier3d = { version = "^0.27", features = ["debug-render-3d"], optional = true } bevy-tnua-rapier3d = { path = "../rapier3d", optional = true } -avian2d = {version = "^0.1", default-features = false, features = ["2d","debug-plugin", "parallel"], optional = true} +avian2d = {version = "^0.1", git = "https://github.com/Jondolf/avian", default-features = false, features = ["2d","debug-plugin", "parallel"], optional = true} bevy-tnua-avian2d = { path = "../avian2d", default-features = false, optional = true } -avian3d = {version = "^0.1", default-features = false, features = ["3d","debug-plugin", "parallel"], optional = true } +avian3d = {version = "^0.1", git = "https://github.com/Jondolf/avian", default-features = false, features = ["3d","debug-plugin", "parallel"], optional = true } bevy-tnua-avian3d = { path = "../avian3d", default-features = false, optional = true } bevy_egui = { version = "0.28", optional = true, default-features = false, features = ["default_fonts", "render"] } diff --git a/demos/src/app_setup_options.rs b/demos/src/app_setup_options.rs index ae04d1ee8..316403376 100644 --- a/demos/src/app_setup_options.rs +++ b/demos/src/app_setup_options.rs @@ -5,7 +5,11 @@ use clap::{Parser, ValueEnum}; #[derive(Resource, Debug, Parser, Clone)] pub struct AppSetupConfiguration { - #[arg(long = "schedule", default_value = "update")] + #[cfg_attr(feature = "rapier", arg(long = "schedule", default_value = "update"))] + #[cfg_attr( + feature = "avian", + arg(long = "schedule", default_value = "physics-schedule") + )] pub schedule_to_use: ScheduleToUse, #[arg(long = "level")] pub level_to_load: Option, @@ -26,7 +30,18 @@ impl AppSetupConfiguration { schedule_to_use: if let Some(value) = url_params.get("schedule") { ScheduleToUse::from_str(&value, true).unwrap() } else { - ScheduleToUse::Update + #[cfg(feature = "avian")] + { + ScheduleToUse::PhysicsSchedule + } + #[cfg(feature = "rapier")] + { + ScheduleToUse::Update + } + #[cfg(all(not(feature = "avian"), not(feature = "rapier")))] + { + panic!("No schedule was specified, but also no physics engine is avaible. Therefore, there is no fallback.") + } }, level_to_load: url_params.get("level"), } diff --git a/demos/src/bin/platformer_2d.rs b/demos/src/bin/platformer_2d.rs index aca2e225c..90c18d9dc 100644 --- a/demos/src/bin/platformer_2d.rs +++ b/demos/src/bin/platformer_2d.rs @@ -1,5 +1,5 @@ #[cfg(feature = "avian2d")] -use avian2d::{prelude as avian, prelude::*, schedule::PhysicsSchedule}; +use avian2d::{prelude as avian, prelude::*}; use bevy::ecs::schedule::ScheduleLabel; use bevy::prelude::*; #[cfg(feature = "rapier2d")] @@ -22,6 +22,7 @@ use tnua_demos_crate::app_setup_options::{AppSetupConfiguration, ScheduleToUse}; use tnua_demos_crate::character_control_systems::info_dumpeing_systems::character_control_info_dumping_system; use tnua_demos_crate::character_control_systems::platformer_control_systems::{ apply_platformer_controls, CharacterMotionConfigForPlatformerDemo, FallingThroughControlScheme, + JustPressedCachePlugin, }; use tnua_demos_crate::character_control_systems::Dimensionality; use tnua_demos_crate::level_mechanics::LevelMechanicsPlugin; @@ -71,10 +72,10 @@ fn main() { app.add_plugins(PhysicsDebugPlugin::default()); match app_setup_configuration.schedule_to_use { ScheduleToUse::Update => { - app.add_plugins(PhysicsPlugins::default()); + app.add_plugins(PhysicsPlugins::new(Update)); // To use Tnua with avian2d, you need the `TnuaAvian2dPlugin` plugin from // bevy-tnua-avian2d. - app.add_plugins(TnuaAvian2dPlugin::default()); + app.add_plugins(TnuaAvian2dPlugin::new(Update)); } ScheduleToUse::FixedUpdate => { app.add_plugins(PhysicsPlugins::new(FixedUpdate)); @@ -82,7 +83,6 @@ fn main() { } ScheduleToUse::PhysicsSchedule => { app.add_plugins(PhysicsPlugins::default()); - app.insert_resource(Time::new_with(Physics::fixed_hz(144.0))); app.add_plugins(TnuaAvian2dPlugin::new(PhysicsSchedule)); } } @@ -101,7 +101,7 @@ fn main() { app.add_plugins(TnuaControllerPlugin::new(FixedUpdate)); app.add_plugins(TnuaCrouchEnforcerPlugin::new(FixedUpdate)); } - #[cfg(any(feature = "avian", feature = "avian"))] + #[cfg(feature = "avian")] ScheduleToUse::PhysicsSchedule => { app.add_plugins(TnuaControllerPlugin::new(PhysicsSchedule)); app.add_plugins(TnuaCrouchEnforcerPlugin::new(PhysicsSchedule)); @@ -129,11 +129,14 @@ fn main() { ScheduleToUse::Update => Update.intern(), ScheduleToUse::FixedUpdate => FixedUpdate.intern(), #[cfg(feature = "avian")] - ScheduleToUse::PhysicsSchedule => PhysicsSchedule.intern(), + // `PhysicsSchedule` is `FixedPostUpdate` by default, which allows us + // to run user code like the platformer controls in `FixedUpdate`, + // which is a bit more idiomatic. + ScheduleToUse::PhysicsSchedule => FixedUpdate.intern(), }, apply_platformer_controls.in_set(TnuaUserControlsSystemSet), ); - app.add_plugins(LevelMechanicsPlugin); + app.add_plugins((LevelMechanicsPlugin, JustPressedCachePlugin)); #[cfg(feature = "rapier2d")] { app.add_systems(Startup, |mut cfg: ResMut| { @@ -301,9 +304,14 @@ fn setup_player(mut commands: Commands) { #[cfg(feature = "avian2d")] { let player_layers: LayerMask = if use_collision_groups { - [LayerNames::Player].into() + [LayerNames::Player, LayerNames::Default].into() } else { - [LayerNames::Player, LayerNames::PhaseThrough].into() + [ + LayerNames::Player, + LayerNames::PhaseThrough, + LayerNames::Default, + ] + .into() }; cmd.insert(CollisionLayers::new(player_layers, player_layers)); } diff --git a/demos/src/bin/platformer_3d.rs b/demos/src/bin/platformer_3d.rs index e436f509f..e4641de2b 100644 --- a/demos/src/bin/platformer_3d.rs +++ b/demos/src/bin/platformer_3d.rs @@ -1,5 +1,5 @@ #[cfg(feature = "avian3d")] -use avian3d::{prelude as avian, prelude::*, schedule::PhysicsSchedule}; +use avian3d::{prelude as avian, prelude::*}; use bevy::ecs::schedule::ScheduleLabel; use bevy::prelude::*; #[cfg(feature = "rapier3d")] @@ -19,9 +19,6 @@ use bevy_tnua_avian3d::*; use bevy_tnua_rapier3d::*; use tnua_demos_crate::app_setup_options::{AppSetupConfiguration, ScheduleToUse}; -use tnua_demos_crate::character_animating_systems::platformer_animating_systems::{ - animate_platformer_character, AnimationState, -}; #[cfg(feature = "egui")] use tnua_demos_crate::character_control_systems::info_dumpeing_systems::character_control_info_dumping_system; use tnua_demos_crate::character_control_systems::platformer_control_systems::{ @@ -41,6 +38,12 @@ use tnua_demos_crate::ui::plotting::PlotSource; #[cfg(feature = "egui")] use tnua_demos_crate::ui::DemoInfoUpdateSystemSet; use tnua_demos_crate::util::animating::{animation_patcher_system, GltfSceneHandler}; +use tnua_demos_crate::{ + character_animating_systems::platformer_animating_systems::{ + animate_platformer_character, AnimationState, + }, + character_control_systems::platformer_control_systems::JustPressedCachePlugin, +}; fn main() { tnua_demos_crate::verify_physics_backends_features!("rapier3d", "avian3d"); @@ -74,10 +77,10 @@ fn main() { { match app_setup_configuration.schedule_to_use { ScheduleToUse::Update => { - app.add_plugins(PhysicsPlugins::default()); + app.add_plugins(PhysicsPlugins::new(Update)); // To use Tnua with avian3d, you need the `TnuaAvian3dPlugin` plugin from // bevy-tnua-avian3d. - app.add_plugins(TnuaAvian3dPlugin::default()); + app.add_plugins(TnuaAvian3dPlugin::new(Update)); } ScheduleToUse::FixedUpdate => { app.add_plugins(PhysicsPlugins::new(FixedUpdate)); @@ -85,7 +88,6 @@ fn main() { } ScheduleToUse::PhysicsSchedule => { app.add_plugins(PhysicsPlugins::default()); - app.insert_resource(Time::new_with(Physics::fixed_hz(144.0))); app.add_plugins(TnuaAvian3dPlugin::new(PhysicsSchedule)); } } @@ -137,13 +139,16 @@ fn main() { ScheduleToUse::Update => Update.intern(), ScheduleToUse::FixedUpdate => FixedUpdate.intern(), #[cfg(feature = "avian")] - ScheduleToUse::PhysicsSchedule => PhysicsSchedule.intern(), + // `PhysicsSchedule` is `FixedPostUpdate` by default, which allows us + // to run user code like the platformer controls in `FixedUpdate`, + // which is a bit more idiomatic. + ScheduleToUse::PhysicsSchedule => FixedUpdate.intern(), }, apply_platformer_controls.in_set(TnuaUserControlsSystemSet), ); app.add_systems(Update, animation_patcher_system); app.add_systems(Update, animate_platformer_character); - app.add_plugins(LevelMechanicsPlugin); + app.add_plugins((LevelMechanicsPlugin, JustPressedCachePlugin)); app.run(); } @@ -332,9 +337,14 @@ fn setup_player(mut commands: Commands, asset_server: Res) { #[cfg(feature = "avian3d")] { let player_layers: LayerMask = if use_collision_groups { - [LayerNames::Player].into() + [LayerNames::Player, LayerNames::Default].into() } else { - [LayerNames::Player, LayerNames::PhaseThrough].into() + [ + LayerNames::Player, + LayerNames::PhaseThrough, + LayerNames::Default, + ] + .into() }; cmd.insert(CollisionLayers::new(player_layers, player_layers)); } diff --git a/demos/src/bin/shooter_like.rs b/demos/src/bin/shooter_like.rs index 8522ed74b..146dd2da6 100644 --- a/demos/src/bin/shooter_like.rs +++ b/demos/src/bin/shooter_like.rs @@ -1,5 +1,5 @@ #[cfg(feature = "avian3d")] -use avian3d::{prelude as avian, prelude::*, schedule::PhysicsSchedule}; +use avian3d::{prelude as avian, prelude::*}; use bevy::ecs::schedule::ScheduleLabel; use bevy::input::mouse::MouseMotion; use bevy::prelude::*; @@ -23,12 +23,15 @@ use tnua_demos_crate::app_setup_options::{AppSetupConfiguration, ScheduleToUse}; use tnua_demos_crate::character_animating_systems::platformer_animating_systems::{ animate_platformer_character, AnimationState, }; -use tnua_demos_crate::character_control_systems::info_dumpeing_systems::character_control_info_dumping_system; use tnua_demos_crate::character_control_systems::platformer_control_systems::{ apply_platformer_controls, CharacterMotionConfigForPlatformerDemo, FallingThroughControlScheme, ForwardFromCamera, }; use tnua_demos_crate::character_control_systems::Dimensionality; +use tnua_demos_crate::character_control_systems::{ + info_dumpeing_systems::character_control_info_dumping_system, + platformer_control_systems::JustPressedCachePlugin, +}; use tnua_demos_crate::level_mechanics::LevelMechanicsPlugin; #[cfg(feature = "avian3d")] use tnua_demos_crate::levels_setup::for_3d_platformer::LayerNames; @@ -84,7 +87,6 @@ fn main() { } ScheduleToUse::PhysicsSchedule => { app.add_plugins(PhysicsPlugins::default()); - app.insert_resource(Time::new_with(Physics::fixed_hz(144.0))); app.add_plugins(TnuaAvian3dPlugin::new(PhysicsSchedule)); } } @@ -131,8 +133,6 @@ fn main() { let system = apply_camera_controls; #[cfg(feature = "rapier")] let system = system.after(bevy_rapier3d::prelude::PhysicsSet::SyncBackend); - #[cfg(feature = "avian")] - let system = system.after(avian3d::prelude::PhysicsSet::Sync); system.before(bevy::transform::TransformSystem::TransformPropagate) }); app.add_systems( @@ -140,13 +140,16 @@ fn main() { ScheduleToUse::Update => Update.intern(), ScheduleToUse::FixedUpdate => FixedUpdate.intern(), #[cfg(feature = "avian")] - ScheduleToUse::PhysicsSchedule => PhysicsSchedule.intern(), + // `PhysicsSchedule` is `FixedPostUpdate` by default, which allows us + // to run user code like the platformer controls in `FixedUpdate`, + // which is a bit more idiomatic. + ScheduleToUse::PhysicsSchedule => FixedUpdate.intern(), }, apply_platformer_controls.in_set(TnuaUserControlsSystemSet), ); app.add_systems(Update, animation_patcher_system); app.add_systems(Update, animate_platformer_character); - app.add_plugins(LevelMechanicsPlugin); + app.add_plugins((LevelMechanicsPlugin, JustPressedCachePlugin)); app.run(); } @@ -338,9 +341,14 @@ fn setup_player(mut commands: Commands, asset_server: Res) { #[cfg(feature = "avian3d")] { let player_layers: LayerMask = if use_collision_groups { - [LayerNames::Player].into() + [LayerNames::Player, LayerNames::Default].into() } else { - [LayerNames::Player, LayerNames::PhaseThrough].into() + [ + LayerNames::Player, + LayerNames::PhaseThrough, + LayerNames::Default, + ] + .into() }; cmd.insert(CollisionLayers::new(player_layers, player_layers)); } diff --git a/demos/src/character_control_systems/platformer_control_systems.rs b/demos/src/character_control_systems/platformer_control_systems.rs index 8cdca7bdc..d5f8f0196 100644 --- a/demos/src/character_control_systems/platformer_control_systems.rs +++ b/demos/src/character_control_systems/platformer_control_systems.rs @@ -1,4 +1,4 @@ -use bevy::prelude::*; +use bevy::{app::RunFixedMainLoop, prelude::*, time::run_fixed_main_schedule}; #[cfg(feature = "egui")] use bevy_egui::{egui, EguiContexts}; use bevy_tnua::builtins::{TnuaBuiltinCrouch, TnuaBuiltinCrouchState, TnuaBuiltinDash}; @@ -18,6 +18,7 @@ use super::Dimensionality; pub fn apply_platformer_controls( #[cfg(feature = "egui")] mut egui_context: EguiContexts, keyboard: Res>, + mut just_pressed: ResMut, mut query: Query<( &CharacterMotionConfigForPlatformerDemo, // This is the main component used for interacting with Tnua. It is used for both issuing @@ -111,25 +112,13 @@ pub fn apply_platformer_controls( let turn_in_place = forward_from_camera.is_none() && keyboard.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]); - let crouch_pressed: bool; - let crouch_just_pressed: bool; - match config.dimensionality { - Dimensionality::Dim2 => { - let crouch_buttons = [ - KeyCode::ControlLeft, - KeyCode::ControlRight, - KeyCode::ArrowDown, - KeyCode::KeyS, - ]; - crouch_pressed = keyboard.any_pressed(crouch_buttons); - crouch_just_pressed = keyboard.any_just_pressed(crouch_buttons); - } - Dimensionality::Dim3 => { - let crouch_buttons = [KeyCode::ControlLeft, KeyCode::ControlRight]; - crouch_pressed = keyboard.any_pressed(crouch_buttons); - crouch_just_pressed = keyboard.any_just_pressed(crouch_buttons); - } - } + let crouch_buttons = match config.dimensionality { + Dimensionality::Dim2 => CROUCH_BUTTONS_2D.iter().copied(), + Dimensionality::Dim3 => CROUCH_BUTTONS_3D.iter().copied(), + }; + let crouch_pressed = keyboard.any_pressed(crouch_buttons); + let crouch_just_pressed = just_pressed.crouch; + just_pressed.was_read = true; // This needs to be called once per frame. It lets the air actions counter know about the // air status of the character. Specifically: @@ -457,3 +446,58 @@ impl Default for ForwardFromCamera { } } } + +/// Since the fixed timestep schedule does not cache just pressed states that happened +/// in a frame with no fixed updates, we need to cache them ourselves in order to not miss them. +/// Note that if you use a smarter input manager like LWIM, this is handled for you. +/// If the demo is running with a variable timestep, this will just report the current frame's +/// state as expected. +pub struct JustPressedCachePlugin; + +impl Plugin for JustPressedCachePlugin { + fn build(&self, app: &mut App) { + app.init_resource::(); + app.add_systems( + RunFixedMainLoop, + ( + collect_just_pressed_cache.before(run_fixed_main_schedule), + clear_just_pressed_cache.after(run_fixed_main_schedule), + ), + ); + } +} + +fn collect_just_pressed_cache( + query: Query<&CharacterMotionConfigForPlatformerDemo>, + keyboard: Res>, + mut just_pressed: ResMut, +) { + for config in &query { + let crouch_buttons = match config.dimensionality { + Dimensionality::Dim2 => CROUCH_BUTTONS_2D.iter().copied(), + Dimensionality::Dim3 => CROUCH_BUTTONS_3D.iter().copied(), + }; + just_pressed.crouch = keyboard.any_just_pressed(crouch_buttons); + } +} + +fn clear_just_pressed_cache(mut just_pressed: ResMut) { + if just_pressed.was_read { + *just_pressed = default() + } +} + +#[derive(Resource, Default)] +pub struct JustPressedCache { + crouch: bool, + was_read: bool, +} + +const CROUCH_BUTTONS_2D: &[KeyCode] = &[ + KeyCode::ControlLeft, + KeyCode::ControlRight, + KeyCode::ArrowDown, + KeyCode::KeyS, +]; + +const CROUCH_BUTTONS_3D: &[KeyCode] = &[KeyCode::ControlLeft, KeyCode::ControlRight]; diff --git a/demos/src/levels_setup/for_2d_platformer.rs b/demos/src/levels_setup/for_2d_platformer.rs index 3d5f1224d..f7c9658de 100644 --- a/demos/src/levels_setup/for_2d_platformer.rs +++ b/demos/src/levels_setup/for_2d_platformer.rs @@ -16,8 +16,10 @@ use super::{ }; #[cfg(feature = "avian2d")] -#[derive(PhysicsLayer)] +#[derive(PhysicsLayer, Default)] pub enum LayerNames { + #[default] + Default, Player, FallThrough, PhaseThrough, diff --git a/demos/src/levels_setup/for_3d_platformer.rs b/demos/src/levels_setup/for_3d_platformer.rs index 1dc68380e..0e39c7952 100644 --- a/demos/src/levels_setup/for_3d_platformer.rs +++ b/demos/src/levels_setup/for_3d_platformer.rs @@ -16,8 +16,10 @@ use super::{ }; #[cfg(feature = "avian3d")] -#[derive(PhysicsLayer)] +#[derive(PhysicsLayer, Default)] pub enum LayerNames { + #[default] + Default, Player, FallThrough, PhaseThrough, diff --git a/examples/example.rs b/examples/example.rs index 94171c917..7b188671d 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -11,15 +11,15 @@ fn main() { DefaultPlugins, PhysicsPlugins::default(), // We need both Tnua's main controller plugin, and the plugin to connect to the physics - // backend (in this case XBPD-3D) - TnuaControllerPlugin::default(), - TnuaAvian3dPlugin::default(), + // backend (in this case Avian 3D) + TnuaControllerPlugin::new(PhysicsSchedule), + TnuaAvian3dPlugin::new(PhysicsSchedule), )) .add_systems( Startup, (setup_camera_and_lights, setup_level, setup_player), ) - .add_systems(Update, apply_controls.in_set(TnuaUserControlsSystemSet)) + .add_systems(FixedUpdate, apply_controls) .run(); } diff --git a/examples/example_animating.rs b/examples/example_animating.rs index 87544851d..9250e4994 100644 --- a/examples/example_animating.rs +++ b/examples/example_animating.rs @@ -16,21 +16,15 @@ fn main() { .add_plugins(( DefaultPlugins, PhysicsPlugins::default(), - TnuaControllerPlugin::default(), - TnuaAvian3dPlugin::default(), + TnuaControllerPlugin::new(PhysicsSchedule), + TnuaAvian3dPlugin::new(PhysicsSchedule), )) .add_systems( Startup, (setup_camera_and_lights, setup_level, setup_player), ) - .add_systems( - Update, - ( - apply_controls.in_set(TnuaUserControlsSystemSet), - prepare_animations, - handle_animating, - ), - ) + .add_systems(FixedUpdate, apply_controls) + .add_systems(Update, (prepare_animations, handle_animating)) .run(); }