mirror of
https://github.com/bevyengine/bevy
synced 2024-11-24 21:53:07 +00:00
# Objective #16222 regressed the user experience of actually using gamepads: ```rust // Before 16222 gamepad.just_pressed(GamepadButton::South) // After 16222 gamepad.digital.just_pressed(GamepadButton::South) // Before 16222 gamepad.get(GamepadButton::RightTrigger2) // After 16222 gamepad.analog.get(GamepadButton::RighTrigger2) ``` Users shouldn't need to think about "digital vs analog" when checking if a button is pressed. This abstraction was intentional and I strongly believe it is in our users' best interest. Buttons and Axes are _both_ digital and analog, and this is largely an implementation detail. I don't think reverting this will be controversial. ## Solution - Revert most of #16222 - Add the `Into<T>` from #16222 to the internals - Expose read/write `digital` and `analog` accessors on gamepad, in the interest of enabling the mocking scenarios covered in #16222 (and allowing the minority of users that care about the "digital" vs "analog" distinction in this context to make that distinction) --------- Co-authored-by: Hennadii Chernyshchyk <genaloner@gmail.com> Co-authored-by: Rob Parrett <robparrett@gmail.com>
This commit is contained in:
parent
a5ec9be0b2
commit
bd850e73c2
5 changed files with 221 additions and 47 deletions
|
@ -210,7 +210,17 @@ where
|
|||
|
||||
/// Returns `true` if any item in `inputs` has just been released.
|
||||
pub fn any_just_released(&self, inputs: impl IntoIterator<Item = T>) -> bool {
|
||||
inputs.into_iter().any(|it| self.just_released(it))
|
||||
inputs.into_iter().any(|input| self.just_released(input))
|
||||
}
|
||||
|
||||
/// Returns `true` if all items in `inputs` have just been released.
|
||||
pub fn all_just_released(&self, inputs: impl IntoIterator<Item = T>) -> bool {
|
||||
inputs.into_iter().all(|input| self.just_released(input))
|
||||
}
|
||||
|
||||
/// Returns `true` if all items in `inputs` have been just pressed.
|
||||
pub fn all_just_pressed(&self, inputs: impl IntoIterator<Item = T>) -> bool {
|
||||
inputs.into_iter().all(|input| self.just_pressed(input))
|
||||
}
|
||||
|
||||
/// Clears the `just_released` state of the `input` and returns `true` if the `input` has just been released.
|
||||
|
|
|
@ -307,7 +307,7 @@ pub enum ButtonSettingsError {
|
|||
},
|
||||
}
|
||||
|
||||
/// Stores a connected gamepad's state and any metadata such as the device name.
|
||||
/// Stores a connected gamepad's metadata such as the name and its [`GamepadButton`] and [`GamepadAxis`].
|
||||
///
|
||||
/// An entity with this component is spawned automatically after [`GamepadConnectionEvent`]
|
||||
/// and updated by [`gamepad_event_processing_system`].
|
||||
|
@ -325,11 +325,11 @@ pub enum ButtonSettingsError {
|
|||
/// for (name, gamepad) in &gamepads {
|
||||
/// println!("{name}");
|
||||
///
|
||||
/// if gamepad.digital.just_pressed(GamepadButton::North) {
|
||||
/// println!("{name} just pressed North")
|
||||
/// if gamepad.just_pressed(GamepadButton::North) {
|
||||
/// println!("{} just pressed North", name)
|
||||
/// }
|
||||
///
|
||||
/// if let Some(left_stick_x) = gamepad.analog.get(GamepadAxis::LeftStickX) {
|
||||
/// if let Some(left_stick_x) = gamepad.get(GamepadAxis::LeftStickX) {
|
||||
/// println!("left stick X: {}", left_stick_x)
|
||||
/// }
|
||||
/// }
|
||||
|
@ -340,46 +340,175 @@ pub enum ButtonSettingsError {
|
|||
#[require(GamepadSettings)]
|
||||
pub struct Gamepad {
|
||||
/// The USB vendor ID as assigned by the USB-IF, if available.
|
||||
pub vendor_id: Option<u16>,
|
||||
pub(crate) vendor_id: Option<u16>,
|
||||
|
||||
/// The USB product ID as assigned by the [vendor], if available.
|
||||
///
|
||||
/// [vendor]: Self::vendor_id
|
||||
pub product_id: Option<u16>,
|
||||
pub(crate) product_id: Option<u16>,
|
||||
|
||||
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
|
||||
pub digital: ButtonInput<GamepadButton>,
|
||||
pub(crate) digital: ButtonInput<GamepadButton>,
|
||||
|
||||
/// [`Axis`] of [`GamepadButton`] representing their analog state.
|
||||
pub analog: Axis<GamepadInput>,
|
||||
pub(crate) analog: Axis<GamepadInput>,
|
||||
}
|
||||
|
||||
impl Gamepad {
|
||||
/// Returns the USB vendor ID as assigned by the USB-IF, if available.
|
||||
pub fn vendor_id(&self) -> Option<u16> {
|
||||
self.vendor_id
|
||||
}
|
||||
|
||||
/// Returns the USB product ID as assigned by the [vendor], if available.
|
||||
///
|
||||
/// [vendor]: Self::vendor_id
|
||||
pub fn product_id(&self) -> Option<u16> {
|
||||
self.product_id
|
||||
}
|
||||
|
||||
/// Returns the analog data of the provided [`GamepadAxis`] or [`GamepadButton`].
|
||||
///
|
||||
/// This will be clamped between [[`Axis::MIN`],[`Axis::MAX`]].
|
||||
pub fn get(&self, input: impl Into<GamepadInput>) -> Option<f32> {
|
||||
self.analog.get(input.into())
|
||||
}
|
||||
|
||||
/// Returns the unclamped analog data of the provided [`GamepadAxis`] or [`GamepadButton`].
|
||||
///
|
||||
/// This value may be outside the [`Axis::MIN`] and [`Axis::MAX`] range.
|
||||
pub fn get_unclamped(&self, input: impl Into<GamepadInput>) -> Option<f32> {
|
||||
self.analog.get_unclamped(input.into())
|
||||
}
|
||||
|
||||
/// Returns the left stick as a [`Vec2`]
|
||||
pub fn left_stick(&self) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.analog.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
|
||||
y: self.analog.get(GamepadAxis::LeftStickY).unwrap_or(0.0),
|
||||
x: self.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
|
||||
y: self.get(GamepadAxis::LeftStickY).unwrap_or(0.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the right stick as a [`Vec2`]
|
||||
pub fn right_stick(&self) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.analog.get(GamepadAxis::RightStickX).unwrap_or(0.0),
|
||||
y: self.analog.get(GamepadAxis::RightStickY).unwrap_or(0.0),
|
||||
x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0),
|
||||
y: self.get(GamepadAxis::RightStickY).unwrap_or(0.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the directional pad as a [`Vec2`]
|
||||
pub fn dpad(&self) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.analog.get(GamepadButton::DPadRight).unwrap_or(0.0)
|
||||
- self.analog.get(GamepadButton::DPadLeft).unwrap_or(0.0),
|
||||
y: self.analog.get(GamepadButton::DPadUp).unwrap_or(0.0)
|
||||
- self.analog.get(GamepadButton::DPadDown).unwrap_or(0.0),
|
||||
x: self.get(GamepadButton::DPadRight).unwrap_or(0.0)
|
||||
- self.get(GamepadButton::DPadLeft).unwrap_or(0.0),
|
||||
y: self.get(GamepadButton::DPadUp).unwrap_or(0.0)
|
||||
- self.get(GamepadButton::DPadDown).unwrap_or(0.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`GamepadButton`] has been pressed.
|
||||
pub fn pressed(&self, button_type: GamepadButton) -> bool {
|
||||
self.digital.pressed(button_type)
|
||||
}
|
||||
|
||||
/// Returns `true` if any item in the [`GamepadButton`] iterator has been pressed.
|
||||
pub fn any_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
|
||||
self.digital.any_pressed(button_inputs)
|
||||
}
|
||||
|
||||
/// Returns `true` if all items in the [`GamepadButton`] iterator have been pressed.
|
||||
pub fn all_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
|
||||
self.digital.all_pressed(button_inputs)
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`GamepadButton`] has been pressed during the current frame.
|
||||
///
|
||||
/// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_released`].
|
||||
pub fn just_pressed(&self, button_type: GamepadButton) -> bool {
|
||||
self.digital.just_pressed(button_type)
|
||||
}
|
||||
|
||||
/// Returns `true` if any item in the [`GamepadButton`] iterator has been pressed during the current frame.
|
||||
pub fn any_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
|
||||
self.digital.any_just_pressed(button_inputs)
|
||||
}
|
||||
|
||||
/// Returns `true` if all items in the [`GamepadButton`] iterator have been just pressed.
|
||||
pub fn all_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
|
||||
self.digital.all_just_pressed(button_inputs)
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`GamepadButton`] has been released during the current frame.
|
||||
///
|
||||
/// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_pressed`].
|
||||
pub fn just_released(&self, button_type: GamepadButton) -> bool {
|
||||
self.digital.just_released(button_type)
|
||||
}
|
||||
|
||||
/// Returns `true` if any item in the [`GamepadButton`] iterator has just been released.
|
||||
pub fn any_just_released(
|
||||
&self,
|
||||
button_inputs: impl IntoIterator<Item = GamepadButton>,
|
||||
) -> bool {
|
||||
self.digital.any_just_released(button_inputs)
|
||||
}
|
||||
|
||||
/// Returns `true` if all items in the [`GamepadButton`] iterator have just been released.
|
||||
pub fn all_just_released(
|
||||
&self,
|
||||
button_inputs: impl IntoIterator<Item = GamepadButton>,
|
||||
) -> bool {
|
||||
self.digital.all_just_released(button_inputs)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all digital [button]s that are pressed.
|
||||
///
|
||||
/// [button]: GamepadButton
|
||||
pub fn get_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
|
||||
self.digital.get_pressed()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all digital [button]s that were just pressed.
|
||||
///
|
||||
/// [button]: GamepadButton
|
||||
pub fn get_just_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
|
||||
self.digital.get_just_pressed()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all digital [button]s that were just released.
|
||||
///
|
||||
/// [button]: GamepadButton
|
||||
pub fn get_just_released(&self) -> impl Iterator<Item = &GamepadButton> {
|
||||
self.digital.get_just_released()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all analog [axes].
|
||||
///
|
||||
/// [axes]: GamepadInput
|
||||
pub fn get_analog_axes(&self) -> impl Iterator<Item = &GamepadInput> {
|
||||
self.analog.all_axes()
|
||||
}
|
||||
|
||||
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
|
||||
pub fn digital(&self) -> &ButtonInput<GamepadButton> {
|
||||
&self.digital
|
||||
}
|
||||
|
||||
/// Mutable [`ButtonInput`] of [`GamepadButton`] representing their digital state. Useful for mocking inputs.
|
||||
pub fn digital_mut(&mut self) -> &mut ButtonInput<GamepadButton> {
|
||||
&mut self.digital
|
||||
}
|
||||
|
||||
/// [`Axis`] of [`GamepadButton`] representing their analog state.
|
||||
pub fn analog(&self) -> &Axis<GamepadInput> {
|
||||
&self.analog
|
||||
}
|
||||
|
||||
/// Mutable [`Axis`] of [`GamepadButton`] representing their analog state. Useful for mocking inputs.
|
||||
pub fn analog_mut(&mut self) -> &mut Axis<GamepadInput> {
|
||||
&mut self.analog
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Gamepad {
|
||||
|
@ -1307,7 +1436,7 @@ pub fn gamepad_event_processing_system(
|
|||
};
|
||||
let Some(filtered_value) = gamepad_settings
|
||||
.get_axis_settings(axis)
|
||||
.filter(value, gamepad_axis.analog.get(axis))
|
||||
.filter(value, gamepad_axis.get(axis))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
@ -1328,7 +1457,7 @@ pub fn gamepad_event_processing_system(
|
|||
};
|
||||
let Some(filtered_value) = settings
|
||||
.get_button_axis_settings(button)
|
||||
.filter(value, gamepad_buttons.analog.get(button))
|
||||
.filter(value, gamepad_buttons.get(button))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
@ -1337,7 +1466,7 @@ pub fn gamepad_event_processing_system(
|
|||
|
||||
if button_settings.is_released(filtered_value) {
|
||||
// Check if button was previously pressed
|
||||
if gamepad_buttons.digital.pressed(button) {
|
||||
if gamepad_buttons.pressed(button) {
|
||||
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
|
||||
gamepad,
|
||||
button,
|
||||
|
@ -1349,7 +1478,7 @@ pub fn gamepad_event_processing_system(
|
|||
gamepad_buttons.digital.release(button);
|
||||
} else if button_settings.is_pressed(filtered_value) {
|
||||
// Check if button was previously not pressed
|
||||
if !gamepad_buttons.digital.pressed(button) {
|
||||
if !gamepad_buttons.pressed(button) {
|
||||
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
|
||||
gamepad,
|
||||
button,
|
||||
|
@ -2403,8 +2532,13 @@ mod tests {
|
|||
assert_eq!(event.button, GamepadButton::DPadDown);
|
||||
assert_eq!(event.state, ButtonState::Pressed);
|
||||
}
|
||||
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
|
||||
assert!(gamepad.digital.pressed(GamepadButton::DPadDown));
|
||||
assert!(ctx
|
||||
.app
|
||||
.world_mut()
|
||||
.query::<&Gamepad>()
|
||||
.get(ctx.app.world(), entity)
|
||||
.unwrap()
|
||||
.pressed(GamepadButton::DPadDown));
|
||||
|
||||
ctx.app
|
||||
.world_mut()
|
||||
|
@ -2419,8 +2553,13 @@ mod tests {
|
|||
.len(),
|
||||
0
|
||||
);
|
||||
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
|
||||
assert!(gamepad.digital.pressed(GamepadButton::DPadDown));
|
||||
assert!(ctx
|
||||
.app
|
||||
.world_mut()
|
||||
.query::<&Gamepad>()
|
||||
.get(ctx.app.world(), entity)
|
||||
.unwrap()
|
||||
.pressed(GamepadButton::DPadDown));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2439,13 +2578,23 @@ mod tests {
|
|||
ctx.update();
|
||||
|
||||
// Check it is flagged for this frame
|
||||
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
|
||||
assert!(gamepad.digital.just_pressed(GamepadButton::DPadDown));
|
||||
assert!(ctx
|
||||
.app
|
||||
.world_mut()
|
||||
.query::<&Gamepad>()
|
||||
.get(ctx.app.world(), entity)
|
||||
.unwrap()
|
||||
.just_pressed(GamepadButton::DPadDown));
|
||||
ctx.update();
|
||||
|
||||
//Check it clears next frame
|
||||
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
|
||||
assert!(!gamepad.digital.just_pressed(GamepadButton::DPadDown));
|
||||
assert!(!ctx
|
||||
.app
|
||||
.world_mut()
|
||||
.query::<&Gamepad>()
|
||||
.get(ctx.app.world(), entity)
|
||||
.unwrap()
|
||||
.just_pressed(GamepadButton::DPadDown));
|
||||
}
|
||||
#[test]
|
||||
fn gamepad_buttons_released() {
|
||||
|
@ -2488,8 +2637,13 @@ mod tests {
|
|||
assert_eq!(event.button, GamepadButton::DPadDown);
|
||||
assert_eq!(event.state, ButtonState::Released);
|
||||
}
|
||||
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
|
||||
assert!(!gamepad.digital.pressed(GamepadButton::DPadDown));
|
||||
assert!(!ctx
|
||||
.app
|
||||
.world_mut()
|
||||
.query::<&Gamepad>()
|
||||
.get(ctx.app.world(), entity)
|
||||
.unwrap()
|
||||
.pressed(GamepadButton::DPadDown));
|
||||
ctx.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
|
||||
|
@ -2528,13 +2682,23 @@ mod tests {
|
|||
ctx.update();
|
||||
|
||||
// Check it is flagged for this frame
|
||||
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
|
||||
assert!(gamepad.digital.just_released(GamepadButton::DPadDown));
|
||||
assert!(ctx
|
||||
.app
|
||||
.world_mut()
|
||||
.query::<&Gamepad>()
|
||||
.get(ctx.app.world(), entity)
|
||||
.unwrap()
|
||||
.just_released(GamepadButton::DPadDown));
|
||||
ctx.update();
|
||||
|
||||
// Check it clears next frame
|
||||
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
|
||||
assert!(!gamepad.digital.just_released(GamepadButton::DPadDown));
|
||||
//Check it clears next frame
|
||||
assert!(!ctx
|
||||
.app
|
||||
.world_mut()
|
||||
.query::<&Gamepad>()
|
||||
.get(ctx.app.world(), entity)
|
||||
.unwrap()
|
||||
.just_released(GamepadButton::DPadDown));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -11,18 +11,18 @@ fn main() {
|
|||
|
||||
fn gamepad_system(gamepads: Query<(Entity, &Gamepad)>) {
|
||||
for (entity, gamepad) in &gamepads {
|
||||
if gamepad.digital.just_pressed(GamepadButton::South) {
|
||||
if gamepad.just_pressed(GamepadButton::South) {
|
||||
info!("{:?} just pressed South", entity);
|
||||
} else if gamepad.digital.just_released(GamepadButton::South) {
|
||||
} else if gamepad.just_released(GamepadButton::South) {
|
||||
info!("{:?} just released South", entity);
|
||||
}
|
||||
|
||||
let right_trigger = gamepad.analog.get(GamepadButton::RightTrigger2).unwrap();
|
||||
let right_trigger = gamepad.get(GamepadButton::RightTrigger2).unwrap();
|
||||
if right_trigger.abs() > 0.01 {
|
||||
info!("{:?} RightTrigger2 value is {}", entity, right_trigger);
|
||||
}
|
||||
|
||||
let left_stick_x = gamepad.analog.get(GamepadAxis::LeftStickX).unwrap();
|
||||
let left_stick_x = gamepad.get(GamepadAxis::LeftStickX).unwrap();
|
||||
if left_stick_x.abs() > 0.01 {
|
||||
info!("{:?} LeftStickX value is {}", entity, left_stick_x);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ fn gamepad_system(
|
|||
mut rumble_requests: EventWriter<GamepadRumbleRequest>,
|
||||
) {
|
||||
for (entity, gamepad) in &gamepads {
|
||||
if gamepad.digital.just_pressed(GamepadButton::North) {
|
||||
if gamepad.just_pressed(GamepadButton::North) {
|
||||
info!(
|
||||
"North face button: strong (low-frequency) with low intensity for rumble for 5 seconds. Press multiple times to increase intensity."
|
||||
);
|
||||
|
@ -30,7 +30,7 @@ fn gamepad_system(
|
|||
});
|
||||
}
|
||||
|
||||
if gamepad.digital.just_pressed(GamepadButton::East) {
|
||||
if gamepad.just_pressed(GamepadButton::East) {
|
||||
info!("East face button: maximum rumble on both motors for 5 seconds");
|
||||
rumble_requests.send(GamepadRumbleRequest::Add {
|
||||
gamepad: entity,
|
||||
|
@ -39,7 +39,7 @@ fn gamepad_system(
|
|||
});
|
||||
}
|
||||
|
||||
if gamepad.digital.just_pressed(GamepadButton::South) {
|
||||
if gamepad.just_pressed(GamepadButton::South) {
|
||||
info!("South face button: low-intensity rumble on the weak motor for 0.5 seconds");
|
||||
rumble_requests.send(GamepadRumbleRequest::Add {
|
||||
gamepad: entity,
|
||||
|
@ -48,7 +48,7 @@ fn gamepad_system(
|
|||
});
|
||||
}
|
||||
|
||||
if gamepad.digital.just_pressed(GamepadButton::West) {
|
||||
if gamepad.just_pressed(GamepadButton::West) {
|
||||
info!("West face button: custom rumble intensity for 5 second");
|
||||
rumble_requests.send(GamepadRumbleRequest::Add {
|
||||
gamepad: entity,
|
||||
|
@ -62,7 +62,7 @@ fn gamepad_system(
|
|||
});
|
||||
}
|
||||
|
||||
if gamepad.digital.just_pressed(GamepadButton::Start) {
|
||||
if gamepad.just_pressed(GamepadButton::Start) {
|
||||
info!("Start button: Interrupt the current rumble");
|
||||
rumble_requests.send(GamepadRumbleRequest::Stop { gamepad: entity });
|
||||
}
|
||||
|
|
|
@ -395,10 +395,10 @@ fn update_buttons(
|
|||
) {
|
||||
for gamepad in &gamepads {
|
||||
for (mut handle, react_to) in query.iter_mut() {
|
||||
if gamepad.digital.just_pressed(**react_to) {
|
||||
if gamepad.just_pressed(**react_to) {
|
||||
*handle = materials.active.clone();
|
||||
}
|
||||
if gamepad.digital.just_released(**react_to) {
|
||||
if gamepad.just_released(**react_to) {
|
||||
*handle = materials.normal.clone();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue