diff --git a/TODO.md b/TODO.md index 38636a4..cd77041 100644 --- a/TODO.md +++ b/TODO.md @@ -1,12 +1,21 @@ # v0.1.0 +- [ ] core + - [x] single keys + - [ ] chords - [x] contracts - [x] loby - [x] actions - [x] game state +- [ ] serialization & deserialization with ignoring imutable values - [x] way to define default controlls - [ ] layers for controlls: if on (shift + space) action enable, ignore on (space) action - [ ] warnings on actions collision -- [ ] basic example +- [ ] examples + - [ ] basic example + - [ ] serialization example +- [ ] action options work + - [ ] deley + - [ ] tap / hold # v0.2.0 - [ ] layers for controlls: user posibility give priority to controls diff --git a/crate/bevy_controls/example/basic/src/main.rs b/crate/bevy_controls/example/basic/src/main.rs index e7df44a..95c3494 100644 --- a/crate/bevy_controls/example/basic/src/main.rs +++ b/crate/bevy_controls/example/basic/src/main.rs @@ -10,8 +10,8 @@ use bevy_controls::{ contract::InputsContainer, plugin::ControlsPlugin, resource::{ - ActivationOptions, Binding, BindingConfig, Bindings, ButtonCombination, Controls, InputType, - InputValue, Keyboard, OptionsMode, PlayerInputs, + ActivationMode, ActivationOptions, Binding, BindingCondition, BindingConfig, Bindings, + ButtonCombination, Controls, InputType, InputValue, Keyboard, OptionsMode, PlayerInputs, }, }; use bevy_controls_derive::{Action, GameState}; @@ -31,6 +31,7 @@ enum MyAction { enum MyGameState { #[default] InGame, + Menu, } #[derive(Resource, Default, Clone, Debug)] @@ -45,7 +46,7 @@ impl InputsContainer for MyInputsContainer { } fn me<'a>(&'a self) -> Option<&'a PlayerInputs> { - Some(&self.player_inputs) + Some(&self.player_inputs) } fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerInputs> { @@ -83,8 +84,12 @@ fn main() { // `О` for `ru` layout, because it uses `Keyboard::ScanCode` Binding::new(ButtonCombination::Single(InputType::Keyboard( Keyboard::ScanCode(ScanCode(35)), - ))), - ])), + ))) + .with_condition(BindingCondition::InGameState(MyGameState::InGame)), + ])) + .with_activation_options( + ActivationOptions::new(ActivationMode::Tap, OptionsMode::Immutable).with_delay(0.05), + ), ) // the rest will be simplified .with( diff --git a/crate/bevy_controls/src/plugin.rs b/crate/bevy_controls/src/plugin.rs index fbe87c7..b5f5e57 100644 --- a/crate/bevy_controls/src/plugin.rs +++ b/crate/bevy_controls/src/plugin.rs @@ -1,4 +1,4 @@ -use bevy_app::{App, Plugin, Update}; +use bevy_app::{App, Plugin, PreUpdate, Update}; use bevy_ecs::{ schedule::State, system::{Res, ResMut}, @@ -43,14 +43,13 @@ impl, Gs: GameState> ControlsPlugin } } - impl, Gs: GameState> Plugin for ControlsPlugin { fn build(&self, app: &mut App) { app .insert_resource(self.controls.clone()) .init_resource::() .add_state::() - .add_systems(Update, Self::save_input); + .add_systems(PreUpdate, Self::collect_inputs); #[cfg(feature = "reflect")] app.register_type::>(); @@ -58,224 +57,83 @@ impl, Gs: GameState> Plugin for ControlsPlugin } impl, Gs: GameState> ControlsPlugin { - /// Process all hard inputs and bindings to update [`PlayerInputs`] - fn save_input( + fn collect_inputs( keyboard_input_keycode: Res>, keyboard_input_scancode: Res>, - mouse_input: Res>, - mut inputs_container: ResMut, - controls: Res>, - game_state: Res>, ) { - // get current client inputs - if let Some(inputs) = inputs_container.me_mut() { - for (action, config) in controls.iter() { - 'bindings_loop: for binding in config.bindings.iter() { - // check binding conditions - for condition in &binding.conditions { - match condition { - BindingCondition::InGameState(state) => { - if *state != *game_state.get() { - continue 'bindings_loop; - } - } - } - } - log::trace!("action binding {:?} {:?} in condition", action, binding); - - // check inputs & mark actions - match &binding.input { - ButtonCombination::Single(button) => match button { - InputType::Keyboard(Keyboard::KeyCode(key)) => { - if keyboard_input_keycode.pressed(*key) { - inputs.forced_set(*action, true); - break; // when on binding trigered no sence check another - } - inputs.forced_set(*action, false); // FIXME: this is happening on every pass so maybe too frequently - } - InputType::Keyboard(Keyboard::ScanCode(code)) => { - log::trace!("presed?: {:?}", keyboard_input_scancode.pressed(*code)); - log::trace!("active"); - if keyboard_input_scancode.pressed(*code) { - inputs.forced_set(*action, true); - break; // when on binding trigered no sence check another - } - inputs.forced_set(*action, false); // FIXME: this is happening on every pass so maybe too frequently - } - InputType::Mouse(input) => match input { - MouseInput::Axis(_axis) => { - todo!(); - } - MouseInput::Button(button) => { - if !mouse_input.get_pressed().any(|b| b == button) { - continue 'bindings_loop; - } - } - MouseInput::Wheel(_axis) => { - todo!(); - } - }, - }, - ButtonCombination::Chord(buttons) => { - // If any button in chord is not pressed we skip this `binding` - for button in buttons { - match button { - InputType::Keyboard(Keyboard::KeyCode(key)) => { - if !keyboard_input_keycode.pressed(*key) { - continue 'bindings_loop; - } - } - InputType::Keyboard(Keyboard::ScanCode(code)) => { - if !keyboard_input_scancode.pressed(*code) { - continue 'bindings_loop; - } - } - InputType::Mouse(input) => match input { - MouseInput::Axis(_axis) => { - todo!(); - } - MouseInput::Button(button) => { - if !mouse_input.get_pressed().any(|b| b == button) { - continue 'bindings_loop; - } - } - MouseInput::Wheel(_axis) => { - todo!(); - } - }, - } - } - - log::trace!("active"); - // TODO: should be [`Chord`](ButtonCombination::Chord) only [`Boolean`](InputValue::Boolean) type - inputs.forced_set(*action, true); - break; // when on binding trigered no sence check another - } - } - } - } - } else { - log::error!("cannot find me in inputs container") + let a: Vec<(InputType, InputValue)> = vec![]; + for key in keyboard_input_keycode.get_pressed() { + } } + + fn fill_players_inputs() {} + + // /// Process all hard inputs and bindings to update [`PlayerInputs`] + // fn save_input( + // keyboard_input_keycode: Res>, + // keyboard_input_scancode: Res>, + // mouse_input: Res>, + // mut inputs_container: ResMut, + // controls: Res>, + // game_state: Res>, + // ) { + // // get current client inputs + // if let Some(inputs) = inputs_container.me_mut() { + // for (action, config) in controls.iter() { + // 'bindings_loop: for binding in config.bindings.iter() { + // // check binding conditions + // for condition in &binding.conditions { + // match condition { + // BindingCondition::InGameState(state) => { + // if *state != *game_state.get() { + // continue 'bindings_loop; + // } + // } + // } + // } + // + // log::trace!("action binding {:?} {:?} in condition", action, binding); + // + // // check inputs & mark actions + // match &binding.input { + // ButtonCombination::Single(button) => match button { + // InputType::Keyboard(Keyboard::KeyCode(key)) => { + // if keyboard_input_keycode.pressed(*key) { + // inputs.forced_set(*action, true); + // break; // when on binding trigered no sence check another + // } + // inputs.forced_set(*action, false); // FIXME: this is happening on every pass so maybe too frequently + // } + // InputType::Keyboard(Keyboard::ScanCode(code)) => { + // log::trace!("presed?: {:?}", keyboard_input_scancode.pressed(*code)); + // log::trace!("active"); + // if keyboard_input_scancode.pressed(*code) { + // inputs.forced_set(*action, true); + // break; // when on binding trigered no sence check another + // } + // inputs.forced_set(*action, false); // FIXME: this is happening on every pass so maybe too frequently + // } + // InputType::Mouse(input) => match input { + // MouseInput::Axis(_axis) => { + // todo!(); + // } + // MouseInput::Button(button) => { + // if !mouse_input.get_pressed().any(|b| b == button) { + // continue 'bindings_loop; + // } + // } + // MouseInput::Wheel(_axis) => { + // todo!(); + // } + // }, + // }, + // } + // } + // } + // } else { + // log::error!("cannot find me in inputs container") + // } + // } } - -// #[cfg(test)] -// mod performance_test { -// use crate::{ util::test::{enable_loggings, measure_time, Times}, resource::PlayerInputs}; - -// use super::Controls; -// use std::time::Duration; - -// /// Test for execution time for [`Controls`] get -// /// -// /// Example: -// /// ``` -// /// cargo test --package pih-pah-app --lib --features "dev, ui_egui" -- controls::test::controls_get --exact --nocapture -// /// ``` -// #[test] -// fn controls_get() { -// enable_loggings(); - -// let controls = Controls::default(); - -// let duration = measure_time( -// || { -// controls.get(Action::LeverEditorForward); -// controls.get(Action::LevelEditorBackward); -// controls.get(Action::LevelEditorLeft); -// controls.get(Action::LevelEditorRight); -// }, -// Times::default(), -// ); - -// log::info!("time: {:?}", duration); -// } - -// /// Test for execution speed for [`PlayerInputs`] get -// fn player_inputs_get() -> Duration { -// enable_loggings(); - -// let inputs = PlayerInputs::default(); - -// let duration = measure_time( -// || { -// inputs.get(Action::LeverEditorForward); -// inputs.get(Action::LevelEditorBackward); -// inputs.get(Action::LevelEditorLeft); -// inputs.get(Action::LevelEditorRight); -// }, -// Times::default(), -// ); - -// duration -// } - -// /// Test for execution speed for [`PlayerInputs`] get_many -// fn player_inputs_get_many() -> Duration { -// enable_loggings(); - -// let inputs = PlayerInputs::default(); - -// let duration = measure_time( -// || { -// inputs.get_many(vec![ -// Action::LeverEditorForward, -// Action::LevelEditorBackward, -// Action::LevelEditorLeft, -// Action::LevelEditorRight, -// ]); -// }, -// Times::default(), -// ); - -// duration -// } - -// /// Test for execution speed for [`PlayerInputs`] get and get_many -// /// -// /// Example: -// /// ``` -// /// cargo test --package pih-pah-app --lib --features "dev, ui_egui" -- controls::test::compare_player_inputs_get_and_get_many --exact --nocapture -// /// ``` -// #[test] -// fn compare_player_inputs_get_and_get_many() { -// let get = player_inputs_get(); -// let get_many = player_inputs_get_many(); - -// log::info!("get: {:?}", get); -// log::info!("get_many: {:?}", get_many); -// } - -// /// Test for execution speed for [`InputValue`] casting -// #[test] -// fn action_casting() { -// enable_loggings(); - -// let inputs = PlayerInputs::default(); - -// let duration = measure_time( -// || { -// let _ = (inputs.get(Action::LeverEditorForward).as_boolean() as i8 -// - inputs.get(Action::LevelEditorBackward).as_boolean() as i8) -// as f32; -// }, -// 10000000.into(), -// ); - -// log::info!("with casting: {:?}", duration); - -// let inputs = PlayerInputs::default(); - -// let duration = measure_time( -// || { -// let _ = inputs.get(Action::LeverEditorForward); -// let _ = inputs.get(Action::LevelEditorBackward); -// }, -// 10000000.into(), -// ); - -// log::info!("without casting: {:?}", duration); -// } -// } diff --git a/crate/bevy_controls/src/resource.rs b/crate/bevy_controls/src/resource.rs index e948f00..3098af4 100644 --- a/crate/bevy_controls/src/resource.rs +++ b/crate/bevy_controls/src/resource.rs @@ -47,27 +47,27 @@ where common_traits_conditions! { /// Struct that contains user's inputs corresponding to actions #[derive(Debug, PartialEq, Clone, Deref, DerefMut)] - pub struct Inputs(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap); + pub struct Actions(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap); /// Resource that contains current user inputs #[derive(Debug, PartialEq, Clone)] - pub struct PlayerInputs { + pub struct PlayerActions { #[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] - current: Inputs, + current: Actions, #[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] - previous: Inputs, + previous: Actions, } } -impl Default for PlayerInputs { +impl Default for PlayerActions { fn default() -> Self { - PlayerInputs { - current: Inputs( + PlayerActions { + current: Actions( A::iter() .map(|action| (action, InputValue::Empty)) .collect(), ), - previous: Inputs( + previous: Actions( A::iter() .map(|action| (action, InputValue::Empty)) .collect(), @@ -76,7 +76,7 @@ impl Default for PlayerInputs { } } -impl PlayerInputs { +impl PlayerActions { /// Returns current input value for action /// /// in any case faster that `get_many` method @@ -171,7 +171,7 @@ impl PlayerInputs { /// Update current inputs /// /// notice: not recomended to use if you do not know what do you do - pub fn forced_update(&mut self, inputs: Inputs) { + pub fn forced_update(&mut self, inputs: Actions) { *self.previous = self.current.clone().0; *self.current = inputs.0; } @@ -181,7 +181,7 @@ impl PlayerInputs { /// ! remember that it work safely but slow /// /// notice: not recomended to use if you do not know what do you do - pub fn update(&mut self, inputs: Inputs) -> Result<(), Box> { + pub fn update(&mut self, inputs: Actions) -> Result<(), Box> { // SAFETY: action is always valid // because we iterate over all actions in `default` method for (action, value) in inputs.0.iter() { @@ -308,8 +308,8 @@ common_traits_conditions! { pub enum ButtonCombination { /// Single button press Single(InputType), - /// Chord is a combination of buttons that must be pressed at the same time - Chord(Vec), + // /// Chord is a combination of buttons that must be pressed at the same time + // Chord(Vec), } #[derive(Debug, PartialEq, Clone)] @@ -353,12 +353,12 @@ impl Binding { } } - pub fn from_chord(input: Vec) -> Self { - Self { - input: ButtonCombination::Chord(input), - conditions: Vec::new(), - } - } + // pub fn from_chord(input: Vec) -> Self { + // Self { + // input: ButtonCombination::Chord(input), + // conditions: Vec::new(), + // } + // } pub fn with_condition(mut self, condition: BindingCondition) -> Self { self.conditions.push(condition);