This commit is contained in:
2024-04-05 01:42:17 +02:00
parent 234e086a77
commit 1812be6ae3
4 changed files with 114 additions and 242 deletions
+10 -1
View File
@@ -1,12 +1,21 @@
# v0.1.0 # v0.1.0
- [ ] core
- [x] single keys
- [ ] chords
- [x] contracts - [x] contracts
- [x] loby - [x] loby
- [x] actions - [x] actions
- [x] game state - [x] game state
- [ ] serialization & deserialization with ignoring imutable values
- [x] way to define default controlls - [x] way to define default controlls
- [ ] layers for controlls: if on (shift + space) action enable, ignore on (space) action - [ ] layers for controlls: if on (shift + space) action enable, ignore on (space) action
- [ ] warnings on actions collision - [ ] warnings on actions collision
- [ ] basic example - [ ] examples
- [ ] basic example
- [ ] serialization example
- [ ] action options work
- [ ] deley
- [ ] tap / hold
# v0.2.0 # v0.2.0
- [ ] layers for controlls: user posibility give priority to controls - [ ] layers for controlls: user posibility give priority to controls
@@ -10,8 +10,8 @@ use bevy_controls::{
contract::InputsContainer, contract::InputsContainer,
plugin::ControlsPlugin, plugin::ControlsPlugin,
resource::{ resource::{
ActivationOptions, Binding, BindingConfig, Bindings, ButtonCombination, Controls, InputType, ActivationMode, ActivationOptions, Binding, BindingCondition, BindingConfig, Bindings,
InputValue, Keyboard, OptionsMode, PlayerInputs, ButtonCombination, Controls, InputType, InputValue, Keyboard, OptionsMode, PlayerInputs,
}, },
}; };
use bevy_controls_derive::{Action, GameState}; use bevy_controls_derive::{Action, GameState};
@@ -31,6 +31,7 @@ enum MyAction {
enum MyGameState { enum MyGameState {
#[default] #[default]
InGame, InGame,
Menu,
} }
#[derive(Resource, Default, Clone, Debug)] #[derive(Resource, Default, Clone, Debug)]
@@ -83,8 +84,12 @@ fn main() {
// `О` for `ru` layout, because it uses `Keyboard::ScanCode` // `О` for `ru` layout, because it uses `Keyboard::ScanCode`
Binding::new(ButtonCombination::Single(InputType::Keyboard( Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::ScanCode(ScanCode(35)), 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 // the rest will be simplified
.with( .with(
+74 -216
View File
@@ -1,4 +1,4 @@
use bevy_app::{App, Plugin, Update}; use bevy_app::{App, Plugin, PreUpdate, Update};
use bevy_ecs::{ use bevy_ecs::{
schedule::State, schedule::State,
system::{Res, ResMut}, system::{Res, ResMut},
@@ -43,14 +43,13 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
} }
} }
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin<A, Ic, Gs> { impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin<A, Ic, Gs> {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app app
.insert_resource(self.controls.clone()) .insert_resource(self.controls.clone())
.init_resource::<Ic>() .init_resource::<Ic>()
.add_state::<Gs>() .add_state::<Gs>()
.add_systems(Update, Self::save_input); .add_systems(PreUpdate, Self::collect_inputs);
#[cfg(feature = "reflect")] #[cfg(feature = "reflect")]
app.register_type::<Controls<A, Gs>>(); app.register_type::<Controls<A, Gs>>();
@@ -58,224 +57,83 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin
} }
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs> { impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs> {
/// Process all hard inputs and bindings to update [`PlayerInputs`] fn collect_inputs(
fn save_input(
keyboard_input_keycode: Res<Input<KeyCode>>, keyboard_input_keycode: Res<Input<KeyCode>>,
keyboard_input_scancode: Res<Input<ScanCode>>, keyboard_input_scancode: Res<Input<ScanCode>>,
mouse_input: Res<Input<MouseButton>>,
mut inputs_container: ResMut<Ic>,
controls: Res<Controls<A, Gs>>,
game_state: Res<State<Gs>>,
) { ) {
// 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); let a: Vec<(InputType, InputValue)> = vec![];
for key in keyboard_input_keycode.get_pressed() {
// 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")
} }
} }
fn fill_players_inputs() {}
// /// Process all hard inputs and bindings to update [`PlayerInputs`]
// fn save_input(
// keyboard_input_keycode: Res<Input<KeyCode>>,
// keyboard_input_scancode: Res<Input<ScanCode>>,
// mouse_input: Res<Input<MouseButton>>,
// mut inputs_container: ResMut<Ic>,
// controls: Res<Controls<A, Gs>>,
// game_state: Res<State<Gs>>,
// ) {
// // 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);
// }
// }
+19 -19
View File
@@ -47,27 +47,27 @@ where
common_traits_conditions! { common_traits_conditions! {
/// Struct that contains user's inputs corresponding to actions /// Struct that contains user's inputs corresponding to actions
#[derive(Debug, PartialEq, Clone, Deref, DerefMut)] #[derive(Debug, PartialEq, Clone, Deref, DerefMut)]
pub struct Inputs<A: Action>(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap<A, InputValue>); pub struct Actions<A: Action>(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap<A, InputValue>);
/// Resource that contains current user inputs /// Resource that contains current user inputs
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct PlayerInputs<A: Action> { pub struct PlayerActions<A: Action> {
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] #[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
current: Inputs<A>, current: Actions<A>,
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] #[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
previous: Inputs<A>, previous: Actions<A>,
} }
} }
impl<A: Action> Default for PlayerInputs<A> { impl<A: Action> Default for PlayerActions<A> {
fn default() -> Self { fn default() -> Self {
PlayerInputs { PlayerActions {
current: Inputs( current: Actions(
A::iter() A::iter()
.map(|action| (action, InputValue::Empty)) .map(|action| (action, InputValue::Empty))
.collect(), .collect(),
), ),
previous: Inputs( previous: Actions(
A::iter() A::iter()
.map(|action| (action, InputValue::Empty)) .map(|action| (action, InputValue::Empty))
.collect(), .collect(),
@@ -76,7 +76,7 @@ impl<A: Action> Default for PlayerInputs<A> {
} }
} }
impl<A: Action> PlayerInputs<A> { impl<A: Action> PlayerActions<A> {
/// Returns current input value for action /// Returns current input value for action
/// ///
/// in any case faster that `get_many` method /// in any case faster that `get_many` method
@@ -171,7 +171,7 @@ impl<A: Action> PlayerInputs<A> {
/// Update current inputs /// Update current inputs
/// ///
/// notice: not recomended to use if you do not know what do you do /// notice: not recomended to use if you do not know what do you do
pub fn forced_update(&mut self, inputs: Inputs<A>) { pub fn forced_update(&mut self, inputs: Actions<A>) {
*self.previous = self.current.clone().0; *self.previous = self.current.clone().0;
*self.current = inputs.0; *self.current = inputs.0;
} }
@@ -181,7 +181,7 @@ impl<A: Action> PlayerInputs<A> {
/// ! remember that it work safely but slow /// ! remember that it work safely but slow
/// ///
/// notice: not recomended to use if you do not know what do you do /// notice: not recomended to use if you do not know what do you do
pub fn update(&mut self, inputs: Inputs<A>) -> Result<(), Box<dyn error::Error>> { pub fn update(&mut self, inputs: Actions<A>) -> Result<(), Box<dyn error::Error>> {
// SAFETY: action is always valid // SAFETY: action is always valid
// because we iterate over all actions in `default` method // because we iterate over all actions in `default` method
for (action, value) in inputs.0.iter() { for (action, value) in inputs.0.iter() {
@@ -308,8 +308,8 @@ common_traits_conditions! {
pub enum ButtonCombination { pub enum ButtonCombination {
/// Single button press /// Single button press
Single(InputType), Single(InputType),
/// Chord is a combination of buttons that must be pressed at the same time // /// Chord is a combination of buttons that must be pressed at the same time
Chord(Vec<InputType>), // Chord(Vec<InputType>),
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@@ -353,12 +353,12 @@ impl<Gs: GameState> Binding<Gs> {
} }
} }
pub fn from_chord(input: Vec<InputType>) -> Self { // pub fn from_chord(input: Vec<InputType>) -> Self {
Self { // Self {
input: ButtonCombination::Chord(input), // input: ButtonCombination::Chord(input),
conditions: Vec::new(), // conditions: Vec::new(),
} // }
} // }
pub fn with_condition(mut self, condition: BindingCondition<Gs>) -> Self { pub fn with_condition(mut self, condition: BindingCondition<Gs>) -> Self {
self.conditions.push(condition); self.conditions.push(condition);