feat: controlls builder

This commit is contained in:
2024-03-12 07:56:53 +01:00
parent c651f37759
commit e1e762395f
5 changed files with 157 additions and 97 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
- [x] loby - [x] loby
- [x] actions - [x] actions
- [x] game state - [x] game state
-[ ] 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 - [ ] basic example
+31 -36
View File
@@ -66,85 +66,80 @@ fn main() {
}), }),
..default() ..default()
}), }),
ControlsPlugin::<MyAction, MyInputsContainer, MyGameState>::default(), ControlsPlugin::<MyAction, MyInputsContainer, MyGameState>::new(
)) Controls::<MyAction, MyGameState>::new()
.insert_resource(ClearColor(Color::NONE)) .with(
.init_resource::<JumpsCount>()
.add_systems(Startup, (add_bindings, setup))
.add_systems(Update, text_update_system)
.run();
}
// TODO: default bingings...
fn add_bindings(mut controls: ResMut<Controls<MyAction, MyGameState>>) {
controls.force_push(
MyAction::Left, MyAction::Left,
// this way input will work only on latin layout only on `A` // this way input will work only on latin layout only on `A`
Binding::new(ButtonCombination::Single(InputType::Keyboard( Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::A), Keyboard::KeyCode(KeyCode::A),
))), ))),
); )
controls.force_push( .with(
MyAction::Left, MyAction::Left,
// this way input will work only on any layout on same button, 35 is `H` for `us` layout or // this way input will work only on any layout on same button, 35 is `H` for `us` layout or
// `О` for `ru` layout // `О` for `ru` layout
Binding::new(ButtonCombination::Single(InputType::Keyboard( Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::ScanCode(ScanCode(35)), Keyboard::ScanCode(ScanCode(35)),
))), ))),
); )
.with(
controls.force_push(
MyAction::Back, MyAction::Back,
Binding::new(ButtonCombination::Single(InputType::Keyboard( Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::S), Keyboard::KeyCode(KeyCode::S),
))), ))),
); )
controls.force_push( .with(
MyAction::Back, MyAction::Back,
Binding::new(ButtonCombination::Single(InputType::Keyboard( Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::ScanCode(ScanCode(36)), Keyboard::ScanCode(ScanCode(36)),
))), ))),
); )
.with(
controls.force_push(
MyAction::Forward, MyAction::Forward,
Binding::new(ButtonCombination::Single(InputType::Keyboard( Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::W), Keyboard::KeyCode(KeyCode::W),
))), ))),
); )
controls.force_push( .with(
MyAction::Forward, MyAction::Forward,
Binding::new(ButtonCombination::Single(InputType::Keyboard( Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::ScanCode(ScanCode(37)), Keyboard::ScanCode(ScanCode(37)),
))), ))),
); )
.with(
controls.force_push(
MyAction::Right, MyAction::Right,
Binding::new(ButtonCombination::Single(InputType::Keyboard( Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::D), Keyboard::KeyCode(KeyCode::D),
))), ))),
); )
controls.force_push( .with(
MyAction::Right, MyAction::Right,
Binding::new(ButtonCombination::Single(InputType::Keyboard( Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::ScanCode(ScanCode(38)), Keyboard::ScanCode(ScanCode(38)),
))), ))),
); )
.with(
controls.force_push(
MyAction::Up, MyAction::Up,
Binding::new(ButtonCombination::Single(InputType::Keyboard( Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::Space), Keyboard::KeyCode(KeyCode::Space),
))), ))),
); )
controls.force_push( .with(
MyAction::Down, MyAction::Down,
Binding::new(ButtonCombination::Chord(vec![ Binding::new(ButtonCombination::Chord(vec![
InputType::Keyboard(Keyboard::KeyCode(KeyCode::ShiftLeft)), InputType::Keyboard(Keyboard::KeyCode(KeyCode::ShiftLeft)),
InputType::Keyboard(Keyboard::KeyCode(KeyCode::Space)), InputType::Keyboard(Keyboard::KeyCode(KeyCode::Space)),
])), ])),
); )
.build(),
),
))
.insert_resource(ClearColor(Color::NONE))
.init_resource::<JumpsCount>()
.add_systems(Startup, setup)
.add_systems(Update, text_update_system)
.run();
} }
#[derive(Component)] #[derive(Component)]
@@ -158,7 +153,7 @@ fn setup(mut commands: Commands) {
commands.spawn(( commands.spawn((
TextBundle::from_sections([ TextBundle::from_sections([
TextSection::new( TextSection::new(
"Action: ", "Info: this example is intended to show basic crate capabilities and limitations\nso WASD work only on US layout, but HJKL may work on any layout\nAction: ",
TextStyle { TextStyle {
font_size: TEXT_SIZE, font_size: TEXT_SIZE,
..default() ..default()
+22 -7
View File
@@ -14,26 +14,40 @@ use crate::{
resource::*, resource::*,
}; };
pub struct ControlsPlugin<A, Ic, Gs> { pub struct ControlsPlugin<A: Action, Ic: InputsContainer<A>, Gs: GameState> {
_action: std::marker::PhantomData<A>, _action: std::marker::PhantomData<A>,
_inputs_container: std::marker::PhantomData<Ic>, _inputs_container: std::marker::PhantomData<Ic>,
_game_state: std::marker::PhantomData<Gs>, _game_state: std::marker::PhantomData<Gs>,
controls: Controls<A, Gs>,
} }
impl<A, Ic, Gs> Default for ControlsPlugin<A, Ic, Gs> { impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Default for ControlsPlugin<A, Ic, Gs> {
fn default() -> Self { fn default() -> Self {
Self { Self {
_action: std::marker::PhantomData, _action: std::marker::PhantomData,
_inputs_container: std::marker::PhantomData, _inputs_container: std::marker::PhantomData,
_game_state: std::marker::PhantomData, _game_state: std::marker::PhantomData,
controls: Controls::<A, Gs>::default(),
} }
} }
} }
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs> {
pub fn new(controls: Controls<A, Gs>) -> Self {
Self {
_action: std::marker::PhantomData,
_inputs_container: std::marker::PhantomData,
_game_state: std::marker::PhantomData,
controls,
}
}
}
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
.init_resource::<Controls<A, Gs>>() .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(Update, Self::save_input);
@@ -53,9 +67,11 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
controls: Res<Controls<A, Gs>>, controls: Res<Controls<A, Gs>>,
game_state: Res<State<Gs>>, game_state: Res<State<Gs>>,
) { ) {
// get current client inputs
if let Some(inputs) = inputs_container.me_mut() { if let Some(inputs) = inputs_container.me_mut() {
for (action, config) in controls.iter() { for (action, config) in controls.iter() {
'bindings_loop: for binding in config.bindings.iter() { 'bindings_loop: for binding in config.bindings.iter() {
// check binding conditions
for condition in &binding.conditions { for condition in &binding.conditions {
match condition { match condition {
BindingCondition::InGameState(state) => { BindingCondition::InGameState(state) => {
@@ -68,16 +84,15 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
log::trace!("action binding {:?} {:?} in condition", action, binding); log::trace!("action binding {:?} {:?} in condition", action, binding);
// check inputs & mark actions
match &binding.input { match &binding.input {
ButtonCombination::Single(button) => match button { ButtonCombination::Single(button) => match button {
InputType::Keyboard(Keyboard::KeyCode(key)) => { InputType::Keyboard(Keyboard::KeyCode(key)) => {
log::trace!("presed?: {:?}", keyboard_input_keycode.pressed(*key));
log::trace!("active");
if keyboard_input_keycode.pressed(*key) { if keyboard_input_keycode.pressed(*key) {
inputs.forced_set(*action, true); inputs.forced_set(*action, true);
break; // when on binding trigered no sence check another break; // when on binding trigered no sence check another
} }
inputs.forced_set(*action, false); // FIXME: this is happening on every pass so too frequently inputs.forced_set(*action, false); // FIXME: this is happening on every pass so maybe too frequently
} }
InputType::Keyboard(Keyboard::ScanCode(code)) => { InputType::Keyboard(Keyboard::ScanCode(code)) => {
log::trace!("presed?: {:?}", keyboard_input_scancode.pressed(*code)); log::trace!("presed?: {:?}", keyboard_input_scancode.pressed(*code));
@@ -86,7 +101,7 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
inputs.forced_set(*action, true); inputs.forced_set(*action, true);
break; // when on binding trigered no sence check another break; // when on binding trigered no sence check another
} }
inputs.forced_set(*action, false); // FIXME: this is happening on every pass so too frequently inputs.forced_set(*action, false); // FIXME: this is happening on every pass so maybe too frequently
} }
InputType::Mouse(input) => match input { InputType::Mouse(input) => match input {
MouseInput::Axis(_axis) => { MouseInput::Axis(_axis) => {
+26 -1
View File
@@ -503,9 +503,29 @@ impl<Gs: GameState> BindingConfig<Gs> {
} }
} }
#[derive(Deref, DerefMut)]
pub struct ControlsBuilder<A: Action, Gs: GameState>(Controls<A, Gs>);
impl<A: Action, Gs: GameState> ControlsBuilder<A, Gs>{
///
pub fn with(mut self, action: A, binding: Binding<Gs>) -> Self {
self.force_push(action, binding);
self
}
///
pub fn build(mut self) -> Controls<A, Gs> {
// If you see this error, you may add new action in menu_actions
// or make sure that you have only one Menufor<'a> Action<'a> with the same name in the Menufor<'a> Action<'a>s
assert!(validate_hash_map(&**self));
std::mem::take(&mut self)
}
}
common_traits_conditions! { common_traits_conditions! {
/// Contains all bindings for actions /// Contains all bindings for actions
#[derive(Resource, Clone)] #[derive(Resource, Clone, Deref, DerefMut)]
#[cfg_attr(feature = "reflect", reflect(Resource))] #[cfg_attr(feature = "reflect", reflect(Resource))]
// TODO: Add delay for input // TODO: Add delay for input
pub struct Controls<A: Action, Gs: GameState>( pub struct Controls<A: Action, Gs: GameState>(
@@ -533,6 +553,11 @@ impl<A: Action, Gs: GameState> Default for Controls<A, Gs> {
} }
impl<A: Action, Gs: GameState> Controls<A, Gs> { impl<A: Action, Gs: GameState> Controls<A, Gs> {
/// New [`Controls`] instance
pub fn new() -> ControlsBuilder<A, Gs> {
ControlsBuilder(Controls(HashMap::new()))
}
/// Returns bindings for action /// Returns bindings for action
pub fn get(&self, action: A) -> Option<&BindingConfig<Gs>> { pub fn get(&self, action: A) -> Option<&BindingConfig<Gs>> {
self.0.get(&action) self.0.get(&action)
+27 -2
View File
@@ -1,3 +1,28 @@
<ul> <h1 id="v0.1.0">v0.1.0</h1>
<li>[ ]</li> <ul class="task-list">
<li><label><input type="checkbox" checked="" />contracts</label>
<ul class="task-list">
<li><label><input type="checkbox" checked="" />loby<br />
</label></li>
<li><label><input type="checkbox" checked="" />actions<br />
</label></li>
<li><label><input type="checkbox" checked="" />game state<br />
</label></li>
</ul></li>
<li><label><input type="checkbox" checked="" />way to define default
controlls</label></li>
<li><label><input type="checkbox" />layers for controlls: if on (shift +
space) action enable, ignore on (space) action</label></li>
<li><label><input type="checkbox" />warnings on actions
collision</label></li>
<li><label><input type="checkbox" />basic example</label></li>
</ul>
<h1 id="v0.2.0">v0.2.0</h1>
<ul class="task-list">
<li><label><input type="checkbox" />layers for controlls: user
posibility give priority to controls</label></li>
<li><label><input type="checkbox" />controller support</label></li>
<li><label><input type="checkbox" />touch pad support</label></li>
<li><label><input type="checkbox" />posibility support more that one
game state</label></li>
</ul> </ul>