From a38a98ef7926c51bfda47d97d9ffd703a0c64d07 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Fri, 15 Jul 2022 16:29:05 -0500 Subject: [PATCH] Support Single Axis Inputs in Virtual DPads (#180) * Temporarily Comment Out Bevy Egui * Make Axis Input Example Slightly More Readable * Fix Bug in Symmetric Axis Constructor Axis would always trigger because negative_low was positive. * Return Axis Pair Results When Action Isn't Pressed This makes sure that axis_pair() returns `Some` even if the value is zero because the axis isn't moved. * Set Dual Axis Value Zero if Not in Trigger Range * Make Single-Axis Inputs Work in Virtual DPad This makes sure that SingleAxis inputs will report a value of 0.0 if the axis value is not withing the triggering zone. This makes it possible to create a virtual gamepad made up of multiple single-axis inputs on the same stick. * Cargo Format --- Cargo.toml | 3 +- examples/axis_inputs.rs | 24 +- examples/binding_menu.rs | 528 ++++++++++++++++++++------------------- src/action_state.rs | 6 +- src/axislike.rs | 2 +- src/input_map.rs | 25 +- src/input_streams.rs | 51 ++-- 7 files changed, 331 insertions(+), 308 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4f8ae66a..aff0df6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,8 @@ serde = {version = "1.0", features = ["derive"]} [dev-dependencies] bevy = {git = "https://github.com/bevyengine/bevy", default-features = false, features = ["bevy_gilrs", "bevy_sprite", "bevy_text", "bevy_ui", "bevy_render", "bevy_core_pipeline", "x11"]} -bevy_egui = { version="0.14", default-features = false } +# Uncomment once Bevy 0.8 is relese and bevy_egui supports it +#bevy_egui = "0.14.0" [lib] name = "leafwing_input_manager" diff --git a/examples/axis_inputs.rs b/examples/axis_inputs.rs index 4ab3c803..4558a324 100644 --- a/examples/axis_inputs.rs +++ b/examples/axis_inputs.rs @@ -32,25 +32,25 @@ fn spawn_player(mut commands: Commands) { // Stores "which actions are currently activated" action_state: ActionState::default(), // Describes how to convert from player inputs into those actions - input_map: InputMap::new([ + input_map: InputMap::default() // Configure the left stick as a dual-axis - (DualAxis::left_stick(), Action::Move), - ]) - // Let's bind the right gamepad trigger to the throttle action - .insert(GamepadButtonType::RightTrigger2, Action::Throttle) - // And we'll use the right stick's x axis as a rudder control - .insert( - // This will trigger if the axis is moved 10% or more in either direction. - SingleAxis::symmetric(GamepadAxisType::RightStickX, 0.1), - Action::Rudder, - ) - .build(), + .insert(DualAxis::left_stick(), Action::Move) + // Let's bind the right gamepad trigger to the throttle action + .insert(GamepadButtonType::RightTrigger2, Action::Throttle) + // And we'll use the right stick's x axis as a rudder control + .insert( + // This will trigger if the axis is moved 10% or more in either direction. + SingleAxis::symmetric(GamepadAxisType::RightStickX, 0.1), + Action::Rudder, + ) + .build(), }); } // Query for the `ActionState` component in your game logic systems! fn move_player(query: Query<&ActionState, With>) { let action_state = query.single(); + // Each action has a button-like state of its own that you can check if action_state.pressed(Action::Move) { // We're working with gamepads, so we want to defensively ensure that we're using the clamped values diff --git a/examples/binding_menu.rs b/examples/binding_menu.rs index 7a81de79..0ca5322a 100644 --- a/examples/binding_menu.rs +++ b/examples/binding_menu.rs @@ -1,285 +1,289 @@ -use bevy::{ - ecs::system::SystemParam, - input::{keyboard::KeyboardInput, mouse::MouseButtonInput, ButtonState}, - prelude::*, -}; -use bevy_egui::{ - egui::{Align2, Area, Grid, Window}, - EguiContext, -}; -use derive_more::Display; -use leafwing_input_manager::{prelude::*, user_input::InputKind}; - -const UI_MARGIN: f32 = 10.0; - fn main() { - App::new() - .insert_resource(ControlSettings::default()) - .add_plugins(DefaultPlugins) - // FIXME: this should be re-enabled once Bevy 0.8 is out and bevy_egui is updated - //.add_plugin(EguiPlugin) - .add_plugin(InputManagerPlugin::::default()) - .add_plugin(InputManagerPlugin::::default()) - .add_startup_system(spawn_player_system) - .add_system(controls_window_system) - .add_system(buttons_system) - .add_system(binding_window_system) - .run(); + todo!("Example will be re-enabled once Bevy 0.8 is release and bevy_egui supports it"); } -fn spawn_player_system(mut commands: Commands, control_settings: Res) { - commands.spawn().insert(control_settings.input.clone()); - commands.insert_resource(InputMap::::new([( - KeyCode::Escape, - UiAction::Back, - )])); - commands.insert_resource(ActionState::::default()); -} +// use bevy::{ +// ecs::system::SystemParam, +// input::{keyboard::KeyboardInput, mouse::MouseButtonInput, ButtonState}, +// prelude::*, +// }; +// use bevy_egui::{ +// egui::{Align2, Area, Grid, Window}, +// EguiContext, +// }; +// use derive_more::Display; +// use leafwing_input_manager::{prelude::*, user_input::InputKind}; -fn controls_window_system( - mut commands: Commands, - mut egui: ResMut, - windows: Res, - control_settings: ResMut, -) { - let main_window = windows.get_primary().unwrap(); - let window_width_margin = egui.ctx_mut().style().spacing.window_margin.left * 2.0; +// const UI_MARGIN: f32 = 10.0; - Window::new("Settings") - .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) - .collapsible(false) - .resizable(false) - .default_width(main_window.width() - UI_MARGIN * 2.0 - window_width_margin) - .show(egui.ctx_mut(), |ui| { - const INPUT_VARIANTS: usize = 3; - const COLUMNS_COUNT: usize = INPUT_VARIANTS + 1; +// fn main() { +// App::new() +// .insert_resource(ControlSettings::default()) +// .add_plugins(DefaultPlugins) +// // FIXME: this should be re-enabled once Bevy 0.8 is out and bevy_egui is updated +// //.add_plugin(EguiPlugin) +// .add_plugin(InputManagerPlugin::::default()) +// .add_plugin(InputManagerPlugin::::default()) +// .add_startup_system(spawn_player_system) +// .add_system(controls_window_system) +// .add_system(buttons_system) +// .add_system(binding_window_system) +// .run(); +// } - Grid::new("Control grid") - .num_columns(COLUMNS_COUNT) - .striped(true) - .min_col_width(ui.available_width() / COLUMNS_COUNT as f32 - window_width_margin) - .show(ui, |ui| { - for action in ControlAction::variants() { - ui.label(action.to_string()); - let inputs = control_settings.input.get(action); - for index in 0..INPUT_VARIANTS { - let button_text = match inputs.get_at(index) { - Some(UserInput::Single(InputKind::GamepadButton( - gamepad_button, - ))) => { - format!("🎮 {:?}", gamepad_button) - } - Some(UserInput::Single(InputKind::Keyboard(keycode))) => { - format!("🖮 {:?}", keycode) - } - Some(UserInput::Single(InputKind::Mouse(mouse_button))) => { - format!("🖱 {:?}", mouse_button) - } - _ => "Empty".to_string(), - }; - if ui.button(button_text).clicked() { - commands.insert_resource(ActiveBinding::new(action, index)); - } - } - ui.end_row(); - } - }); - ui.expand_to_include_rect(ui.available_rect_before_wrap()); - }); -} +// fn spawn_player_system(mut commands: Commands, control_settings: Res) { +// commands.spawn().insert(control_settings.input.clone()); +// commands.insert_resource(InputMap::::new([( +// KeyCode::Escape, +// UiAction::Back, +// )])); +// commands.insert_resource(ActionState::::default()); +// } -fn buttons_system( - mut egui: ResMut, - mut control_settings: ResMut, - mut player_mappings: Query<&mut InputMap>, -) { - Area::new("Settings buttons area") - .anchor(Align2::RIGHT_BOTTOM, (-UI_MARGIN, -UI_MARGIN)) - .show(egui.ctx_mut(), |ui| { - ui.horizontal(|ui| { - if ui.button("Restore defaults").clicked() { - *control_settings = ControlSettings::default(); - } - if ui.button("Apply").clicked() { - *player_mappings.single_mut() = control_settings.input.clone(); - } - }) - }); -} +// fn controls_window_system( +// mut commands: Commands, +// mut egui: ResMut, +// windows: Res, +// control_settings: ResMut, +// ) { +// let main_window = windows.get_primary().unwrap(); +// let window_width_margin = egui.ctx_mut().style().spacing.window_margin.left * 2.0; -fn binding_window_system( - mut commands: Commands, - mut egui: ResMut, - mut input_events: InputEvents, - active_binding: Option>, - mut control_settings: ResMut, - ui_action_state: Res>, -) { - let mut active_binding = match active_binding { - Some(active_binding) => active_binding, - None => return, - }; +// Window::new("Settings") +// .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) +// .collapsible(false) +// .resizable(false) +// .default_width(main_window.width() - UI_MARGIN * 2.0 - window_width_margin) +// .show(egui.ctx_mut(), |ui| { +// const INPUT_VARIANTS: usize = 3; +// const COLUMNS_COUNT: usize = INPUT_VARIANTS + 1; - Window::new(format!("Binding \"{}\"", active_binding.action)) - .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) - .collapsible(false) - .resizable(false) - .show(egui.ctx_mut(), |ui| { - if let Some(conflict) = &active_binding.conflict { - ui.label(format!( - "Input \"{}\" is already used by \"{}\"", - conflict.input_button, conflict.action - )); - ui.horizontal(|ui| { - if ui.button("Replace").clicked() { - control_settings - .input - .remove(conflict.action, conflict.input_button); - control_settings.input.insert_at( - conflict.input_button, - active_binding.action, - active_binding.index, - ); - commands.remove_resource::(); - } - if ui.button("Cancel").clicked() { - commands.remove_resource::(); - } - }); - } else { - ui.label("Press any key now or Esc to cancel"); - if ui_action_state.just_pressed(UiAction::Back) { - commands.remove_resource::(); - } else if let Some(input_button) = input_events.input_button() { - let conflict_action = - control_settings.input.iter().find_map(|(inputs, action)| { - if action != active_binding.action - && inputs.contains(&input_button.into()) - { - return Some(action); - } - None - }); - if let Some(action) = conflict_action { - active_binding.conflict.replace(BindingConflict { - action, - input_button, - }); - } else { - control_settings.input.insert_at( - input_button, - active_binding.action, - active_binding.index, - ); - commands.remove_resource::(); - } - } - } - }); -} +// Grid::new("Control grid") +// .num_columns(COLUMNS_COUNT) +// .striped(true) +// .min_col_width(ui.available_width() / COLUMNS_COUNT as f32 - window_width_margin) +// .show(ui, |ui| { +// for action in ControlAction::variants() { +// ui.label(action.to_string()); +// let inputs = control_settings.input.get(action); +// for index in 0..INPUT_VARIANTS { +// let button_text = match inputs.get_at(index) { +// Some(UserInput::Single(InputKind::GamepadButton( +// gamepad_button, +// ))) => { +// format!("🎮 {:?}", gamepad_button) +// } +// Some(UserInput::Single(InputKind::Keyboard(keycode))) => { +// format!("🖮 {:?}", keycode) +// } +// Some(UserInput::Single(InputKind::Mouse(mouse_button))) => { +// format!("🖱 {:?}", mouse_button) +// } +// _ => "Empty".to_string(), +// }; +// if ui.button(button_text).clicked() { +// commands.insert_resource(ActiveBinding::new(action, index)); +// } +// } +// ui.end_row(); +// } +// }); +// ui.expand_to_include_rect(ui.available_rect_before_wrap()); +// }); +// } -#[derive(Actionlike, PartialEq, Clone, Copy, Display)] -pub(crate) enum ControlAction { - // Movement - Forward, - Backward, - Left, - Right, - Jump, +// fn buttons_system( +// mut egui: ResMut, +// mut control_settings: ResMut, +// mut player_mappings: Query<&mut InputMap>, +// ) { +// Area::new("Settings buttons area") +// .anchor(Align2::RIGHT_BOTTOM, (-UI_MARGIN, -UI_MARGIN)) +// .show(egui.ctx_mut(), |ui| { +// ui.horizontal(|ui| { +// if ui.button("Restore defaults").clicked() { +// *control_settings = ControlSettings::default(); +// } +// if ui.button("Apply").clicked() { +// *player_mappings.single_mut() = control_settings.input.clone(); +// } +// }) +// }); +// } - // Abilities activation - BaseAttack, - Ability1, - Ability2, - Ability3, - Ultimate, -} +// fn binding_window_system( +// mut commands: Commands, +// mut egui: ResMut, +// mut input_events: InputEvents, +// active_binding: Option>, +// mut control_settings: ResMut, +// ui_action_state: Res>, +// ) { +// let mut active_binding = match active_binding { +// Some(active_binding) => active_binding, +// None => return, +// }; -#[derive(Actionlike, PartialEq, Clone, Copy)] -pub(crate) enum UiAction { - Back, -} +// Window::new(format!("Binding \"{}\"", active_binding.action)) +// .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) +// .collapsible(false) +// .resizable(false) +// .show(egui.ctx_mut(), |ui| { +// if let Some(conflict) = &active_binding.conflict { +// ui.label(format!( +// "Input \"{}\" is already used by \"{}\"", +// conflict.input_button, conflict.action +// )); +// ui.horizontal(|ui| { +// if ui.button("Replace").clicked() { +// control_settings +// .input +// .remove(conflict.action, conflict.input_button); +// control_settings.input.insert_at( +// conflict.input_button, +// active_binding.action, +// active_binding.index, +// ); +// commands.remove_resource::(); +// } +// if ui.button("Cancel").clicked() { +// commands.remove_resource::(); +// } +// }); +// } else { +// ui.label("Press any key now or Esc to cancel"); +// if ui_action_state.just_pressed(UiAction::Back) { +// commands.remove_resource::(); +// } else if let Some(input_button) = input_events.input_button() { +// let conflict_action = +// control_settings.input.iter().find_map(|(inputs, action)| { +// if action != active_binding.action +// && inputs.contains(&input_button.into()) +// { +// return Some(action); +// } +// None +// }); +// if let Some(action) = conflict_action { +// active_binding.conflict.replace(BindingConflict { +// action, +// input_button, +// }); +// } else { +// control_settings.input.insert_at( +// input_button, +// active_binding.action, +// active_binding.index, +// ); +// commands.remove_resource::(); +// } +// } +// } +// }); +// } -struct ControlSettings { - input: InputMap, -} +// #[derive(Actionlike, PartialEq, Clone, Copy, Display)] +// pub(crate) enum ControlAction { +// // Movement +// Forward, +// Backward, +// Left, +// Right, +// Jump, -impl Default for ControlSettings { - fn default() -> Self { - let mut input = InputMap::default(); - input - .insert(KeyCode::W, ControlAction::Forward) - .insert(KeyCode::S, ControlAction::Backward) - .insert(KeyCode::A, ControlAction::Left) - .insert(KeyCode::D, ControlAction::Right) - .insert(KeyCode::Space, ControlAction::Jump) - .insert(MouseButton::Left, ControlAction::BaseAttack) - .insert(KeyCode::Q, ControlAction::Ability1) - .insert(KeyCode::E, ControlAction::Ability2) - .insert(KeyCode::LShift, ControlAction::Ability3) - .insert(KeyCode::R, ControlAction::Ultimate); +// // Abilities activation +// BaseAttack, +// Ability1, +// Ability2, +// Ability3, +// Ultimate, +// } - Self { input } - } -} +// #[derive(Actionlike, PartialEq, Clone, Copy)] +// pub(crate) enum UiAction { +// Back, +// } -struct ActiveBinding { - action: ControlAction, - index: usize, - conflict: Option, -} +// struct ControlSettings { +// input: InputMap, +// } -impl ActiveBinding { - fn new(action: ControlAction, index: usize) -> Self { - Self { - action, - index, - conflict: None, - } - } -} +// impl Default for ControlSettings { +// fn default() -> Self { +// let mut input = InputMap::default(); +// input +// .insert(KeyCode::W, ControlAction::Forward) +// .insert(KeyCode::S, ControlAction::Backward) +// .insert(KeyCode::A, ControlAction::Left) +// .insert(KeyCode::D, ControlAction::Right) +// .insert(KeyCode::Space, ControlAction::Jump) +// .insert(MouseButton::Left, ControlAction::BaseAttack) +// .insert(KeyCode::Q, ControlAction::Ability1) +// .insert(KeyCode::E, ControlAction::Ability2) +// .insert(KeyCode::LShift, ControlAction::Ability3) +// .insert(KeyCode::R, ControlAction::Ultimate); -struct BindingConflict { - action: ControlAction, - input_button: InputKind, -} +// Self { input } +// } +// } -/// Helper for collecting input -#[derive(SystemParam)] -struct InputEvents<'w, 's> { - keys: EventReader<'w, 's, KeyboardInput>, - mouse_buttons: EventReader<'w, 's, MouseButtonInput>, - gamepad_events: EventReader<'w, 's, GamepadEvent>, -} +// struct ActiveBinding { +// action: ControlAction, +// index: usize, +// conflict: Option, +// } -impl InputEvents<'_, '_> { - fn input_button(&mut self) -> Option { - if let Some(keyboard_input) = self.keys.iter().next() { - if keyboard_input.state == ButtonState::Released { - if let Some(key_code) = keyboard_input.key_code { - return Some(key_code.into()); - } - } - } +// impl ActiveBinding { +// fn new(action: ControlAction, index: usize) -> Self { +// Self { +// action, +// index, +// conflict: None, +// } +// } +// } - if let Some(mouse_input) = self.mouse_buttons.iter().next() { - if mouse_input.state == ButtonState::Released { - return Some(mouse_input.button.into()); - } - } +// struct BindingConflict { +// action: ControlAction, +// input_button: InputKind, +// } - if let Some(GamepadEvent { - gamepad: _, - event_type, - }) = self.gamepad_events.iter().next() - { - if let GamepadEventType::ButtonChanged(button, strength) = event_type.to_owned() { - if strength <= 0.5 { - return Some(button.into()); - } - } - } +// /// Helper for collecting input +// #[derive(SystemParam)] +// struct InputEvents<'w, 's> { +// keys: EventReader<'w, 's, KeyboardInput>, +// mouse_buttons: EventReader<'w, 's, MouseButtonInput>, +// gamepad_events: EventReader<'w, 's, GamepadEvent>, +// } - None - } -} +// impl InputEvents<'_, '_> { +// fn input_button(&mut self) -> Option { +// if let Some(keyboard_input) = self.keys.iter().next() { +// if keyboard_input.state == ButtonState::Released { +// if let Some(key_code) = keyboard_input.key_code { +// return Some(key_code.into()); +// } +// } +// } + +// if let Some(mouse_input) = self.mouse_buttons.iter().next() { +// if mouse_input.state == ButtonState::Released { +// return Some(mouse_input.button.into()); +// } +// } + +// if let Some(GamepadEvent { +// gamepad: _, +// event_type, +// }) = self.gamepad_events.iter().next() +// { +// if let GamepadEventType::ButtonChanged(button, strength) = event_type.to_owned() { +// if strength <= 0.5 { +// return Some(button.into()); +// } +// } +// } + +// None +// } +// } diff --git a/src/action_state.rs b/src/action_state.rs index b7a07cb9..db2788c8 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -245,10 +245,8 @@ impl ActionState { /// Get the [`DualAxisData`] associated with the corresponding `action`, clamped to `[-1.0, 1.0]`. pub fn clamped_axis_pair(&self, action: A) -> Option { - self.axis_pair(action).map(|pair| DualAxisData::new( - pair.x().clamp(-1.0, 1.0), - pair.y().clamp(-1.0, 1.0), - )) + self.axis_pair(action) + .map(|pair| DualAxisData::new(pair.x().clamp(-1.0, 1.0), pair.y().clamp(-1.0, 1.0))) } /// Manually sets the [`ActionData`] of the corresponding `action` diff --git a/src/axislike.rs b/src/axislike.rs index f7dfe5b8..e44a68e4 100644 --- a/src/axislike.rs +++ b/src/axislike.rs @@ -39,7 +39,7 @@ impl SingleAxis { SingleAxis { axis_type: axis_type.into(), positive_low: threshold, - negative_low: threshold, + negative_low: -threshold, value: None, } } diff --git a/src/input_map.rs b/src/input_map.rs index a1e06e1a..f3f0e9cd 100644 --- a/src/input_map.rs +++ b/src/input_map.rs @@ -323,21 +323,22 @@ impl InputMap { let mut inputs = Vec::new(); for input in self.get(action.clone()).iter() { - let value = input_streams.input_value(input); + let action = &mut action_data[action.index()]; + + // Merge axis pair into action data let axis_pair = input_streams.input_axis_pair(input); + if let Some(axis_pair) = axis_pair { + if let Some(current_axis_pair) = &mut action.axis_pair { + *current_axis_pair = current_axis_pair.merged_with(axis_pair); + } else { + action.axis_pair = Some(axis_pair); + } + } + if input_streams.input_pressed(input) { inputs.push(input.clone()); - let action = &mut action_data[action.index()]; - action.value += value; - - // Merge axis pair into action data - if let Some(axis_pair) = axis_pair { - if let Some(current_axis_pair) = &mut action.axis_pair { - *current_axis_pair = current_axis_pair.merged_with(axis_pair); - } else { - action.axis_pair = Some(axis_pair); - } - } + + action.value += input_streams.input_value(input); } } diff --git a/src/input_streams.rs b/src/input_streams.rs index 795496d6..3d975a31 100644 --- a/src/input_streams.rs +++ b/src/input_streams.rs @@ -11,7 +11,7 @@ use petitset::PetitSet; use bevy_ecs::prelude::{Events, Res, ResMut, World}; use bevy_ecs::system::SystemState; -use crate::axislike::{AxisType, DualAxisData, MouseWheelAxisType, VirtualDPad}; +use crate::axislike::{AxisType, DualAxisData, MouseWheelAxisType, SingleAxis, VirtualDPad}; use crate::buttonlike::MouseWheelDirection; use crate::user_input::{InputKind, UserInput}; @@ -166,20 +166,15 @@ impl<'a> InputStreams<'a> { #[must_use] pub fn button_pressed(&self, button: InputKind) -> bool { match button { - InputKind::DualAxis(axis) => { + InputKind::DualAxis(_) => { let axis_pair = self.input_axis_pair(&UserInput::Single(button)).unwrap(); - let x = axis_pair.x(); - let y = axis_pair.y(); - x > axis.x.positive_low - || x < axis.x.negative_low - || y > axis.y.positive_low - || y < axis.y.negative_low + axis_pair.length() != 0.0 } - InputKind::SingleAxis(axis) => { + InputKind::SingleAxis(_) => { let value = self.input_value(&UserInput::Single(button)); - value < axis.negative_low || value > axis.positive_low + value != 0.0 } InputKind::GamepadButton(gamepad_button) => { // If a gamepad was registered, just check that one @@ -300,14 +295,28 @@ impl<'a> InputStreams<'a> { } }; + // Helper that takes the value returned by an axis and returns 0.0 if it is not within the + // triggering range. + let value_in_axis_range = |axis: &SingleAxis, value: f32| -> f32 { + if value >= axis.negative_low && value <= axis.positive_low { + 0.0 + } else { + value + } + }; + match input { UserInput::Single(InputKind::SingleAxis(single_axis)) => { match single_axis.axis_type { AxisType::Gamepad(axis_type) => { if let Some(axes) = self.gamepad_axes { if let Some(gamepad) = self.associated_gamepad { - axes.get(GamepadAxis { gamepad, axis_type }) - .unwrap_or_default() + let value = axes + .get(GamepadAxis { gamepad, axis_type }) + .unwrap_or_default(); + + value_in_axis_range(single_axis, value) + // If no gamepad is registered, return the first non-zero input found } else if let Some(gamepads) = self.gamepads { for &gamepad in gamepads.iter() { @@ -317,6 +326,7 @@ impl<'a> InputStreams<'a> { axis_type: single_axis.axis_type.try_into().unwrap(), }) .unwrap_or_default(); + let value = value_in_axis_range(single_axis, value); if value != 0.0 { // A matching input was pressed on a gamepad @@ -349,7 +359,7 @@ impl<'a> InputStreams<'a> { } } } - total_mouse_wheel_movement + value_in_axis_range(single_axis, total_mouse_wheel_movement) } } } @@ -419,7 +429,16 @@ impl<'a> InputStreams<'a> { UserInput::Single(InputKind::DualAxis(dual_axis)) => { let x = self.input_value(&UserInput::Single(InputKind::SingleAxis(dual_axis.x))); let y = self.input_value(&UserInput::Single(InputKind::SingleAxis(dual_axis.y))); - Some(DualAxisData::new(x, y)) + + if x > dual_axis.x.positive_low + || x < dual_axis.x.negative_low + || y > dual_axis.y.positive_low + || y < dual_axis.y.negative_low + { + Some(DualAxisData::new(x, y)) + } else { + Some(DualAxisData::new(0.0, 0.0)) + } } UserInput::VirtualDPad(VirtualDPad { up, @@ -427,9 +446,9 @@ impl<'a> InputStreams<'a> { left, right, }) => { - let x = self.input_value(&UserInput::Single(*right)) + let x = self.input_value(&UserInput::Single(*right)).abs() - self.input_value(&UserInput::Single(*left)).abs(); - let y = self.input_value(&UserInput::Single(*up)) + let y = self.input_value(&UserInput::Single(*up)).abs() - self.input_value(&UserInput::Single(*down)).abs(); Some(DualAxisData::new(x, y)) }