2023-08-23 12:44:49 +00:00
|
|
|
//! The gamepad input functionality.
|
|
|
|
|
2023-12-06 20:32:34 +00:00
|
|
|
use crate::{Axis, ButtonInput, ButtonState};
|
2022-12-11 18:22:09 +00:00
|
|
|
use bevy_ecs::{
|
2023-01-11 15:41:54 +00:00
|
|
|
change_detection::DetectChangesMut,
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
component::Component,
|
|
|
|
entity::Entity,
|
2024-09-24 11:42:59 +00:00
|
|
|
event::{Event, EventReader, EventWriter},
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
system::{Commands, Query},
|
2022-12-11 18:22:09 +00:00
|
|
|
};
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
use bevy_math::Vec2;
|
2024-09-27 00:59:59 +00:00
|
|
|
#[cfg(feature = "bevy_reflect")]
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|
|
|
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
|
|
|
|
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
|
|
|
use bevy_utils::{
|
|
|
|
tracing::{info, warn},
|
|
|
|
Duration, HashMap,
|
2024-09-27 00:59:59 +00:00
|
|
|
};
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
use thiserror::Error;
|
|
|
|
|
|
|
|
/// A gamepad event.
|
|
|
|
///
|
|
|
|
/// This event type is used over the [`GamepadConnectionEvent`],
|
|
|
|
/// [`GamepadButtonChangedEvent`] and [`GamepadAxisChangedEvent`] when
|
|
|
|
/// the in-frame relative ordering of events is important.
|
|
|
|
///
|
|
|
|
/// This event is produced by `bevy_input`
|
|
|
|
#[derive(Event, Debug, Clone, PartialEq)]
|
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
|
|
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
|
|
|
#[cfg_attr(
|
|
|
|
all(feature = "serialize", feature = "bevy_reflect"),
|
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
|
|
|
pub enum GamepadEvent {
|
|
|
|
/// A gamepad has been connected or disconnected.
|
|
|
|
Connection(GamepadConnectionEvent),
|
|
|
|
/// A button of the gamepad has been triggered.
|
|
|
|
Button(GamepadButtonChangedEvent),
|
|
|
|
/// An axis of the gamepad has been triggered.
|
|
|
|
Axis(GamepadAxisChangedEvent),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<GamepadConnectionEvent> for GamepadEvent {
|
|
|
|
fn from(value: GamepadConnectionEvent) -> Self {
|
|
|
|
Self::Connection(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<GamepadButtonChangedEvent> for GamepadEvent {
|
|
|
|
fn from(value: GamepadButtonChangedEvent) -> Self {
|
|
|
|
Self::Button(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<GamepadAxisChangedEvent> for GamepadEvent {
|
|
|
|
fn from(value: GamepadAxisChangedEvent) -> Self {
|
|
|
|
Self::Axis(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A raw gamepad event.
|
|
|
|
///
|
|
|
|
/// This event type is used over the [`GamepadConnectionEvent`],
|
|
|
|
/// [`RawGamepadButtonChangedEvent`] and [`RawGamepadAxisChangedEvent`] when
|
|
|
|
/// the in-frame relative ordering of events is important.
|
|
|
|
///
|
|
|
|
/// This event type is used by `bevy_input` to feed its components.
|
|
|
|
#[derive(Event, Debug, Clone, PartialEq, Reflect)]
|
|
|
|
#[reflect(Debug, PartialEq)]
|
|
|
|
#[cfg_attr(
|
|
|
|
feature = "serialize",
|
|
|
|
derive(serde::Serialize, serde::Deserialize),
|
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
|
|
|
pub enum RawGamepadEvent {
|
|
|
|
/// A gamepad has been connected or disconnected.
|
|
|
|
Connection(GamepadConnectionEvent),
|
|
|
|
/// A button of the gamepad has been triggered.
|
|
|
|
Button(RawGamepadButtonChangedEvent),
|
|
|
|
/// An axis of the gamepad has been triggered.
|
|
|
|
Axis(RawGamepadAxisChangedEvent),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<GamepadConnectionEvent> for RawGamepadEvent {
|
|
|
|
fn from(value: GamepadConnectionEvent) -> Self {
|
|
|
|
Self::Connection(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<RawGamepadButtonChangedEvent> for RawGamepadEvent {
|
|
|
|
fn from(value: RawGamepadButtonChangedEvent) -> Self {
|
|
|
|
Self::Button(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<RawGamepadAxisChangedEvent> for RawGamepadEvent {
|
|
|
|
fn from(value: RawGamepadAxisChangedEvent) -> Self {
|
|
|
|
Self::Axis(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// [`GamepadButton`] changed event unfiltered by [`GamepadSettings`]
|
|
|
|
#[derive(Event, Debug, Copy, Clone, PartialEq, Reflect)]
|
|
|
|
#[reflect(Debug, PartialEq)]
|
|
|
|
#[cfg_attr(
|
|
|
|
feature = "serialize",
|
|
|
|
derive(serde::Serialize, serde::Deserialize),
|
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
|
|
|
pub struct RawGamepadButtonChangedEvent {
|
|
|
|
/// The gamepad on which the button is triggered.
|
|
|
|
pub gamepad: Entity,
|
|
|
|
/// The type of the triggered button.
|
|
|
|
pub button: GamepadButton,
|
|
|
|
/// The value of the button.
|
|
|
|
pub value: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RawGamepadButtonChangedEvent {
|
|
|
|
/// Creates a [`RawGamepadButtonChangedEvent`].
|
|
|
|
pub fn new(gamepad: Entity, button_type: GamepadButton, value: f32) -> Self {
|
|
|
|
Self {
|
|
|
|
gamepad,
|
|
|
|
button: button_type,
|
|
|
|
value,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// [`GamepadAxis`] changed event unfiltered by [`GamepadSettings`]
|
|
|
|
#[derive(Event, Debug, Copy, Clone, PartialEq, Reflect)]
|
|
|
|
#[reflect(Debug, PartialEq)]
|
|
|
|
#[cfg_attr(
|
|
|
|
feature = "serialize",
|
|
|
|
derive(serde::Serialize, serde::Deserialize),
|
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
|
|
|
pub struct RawGamepadAxisChangedEvent {
|
|
|
|
/// The gamepad on which the axis is triggered.
|
|
|
|
pub gamepad: Entity,
|
|
|
|
/// The type of the triggered axis.
|
|
|
|
pub axis: GamepadAxis,
|
|
|
|
/// The value of the axis.
|
|
|
|
pub value: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RawGamepadAxisChangedEvent {
|
|
|
|
/// Creates a [`RawGamepadAxisChangedEvent`].
|
|
|
|
pub fn new(gamepad: Entity, axis_type: GamepadAxis, value: f32) -> Self {
|
|
|
|
Self {
|
|
|
|
gamepad,
|
|
|
|
axis: axis_type,
|
|
|
|
value,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A Gamepad connection event. Created when a connection to a gamepad
|
|
|
|
/// is established and when a gamepad is disconnected.
|
|
|
|
#[derive(Event, Debug, Clone, PartialEq, Reflect)]
|
|
|
|
#[reflect(Debug, PartialEq)]
|
|
|
|
#[cfg_attr(
|
|
|
|
feature = "serialize",
|
|
|
|
derive(serde::Serialize, serde::Deserialize),
|
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
|
|
|
pub struct GamepadConnectionEvent {
|
|
|
|
/// The gamepad whose connection status changed.
|
|
|
|
pub gamepad: Entity,
|
|
|
|
/// The change in the gamepads connection.
|
|
|
|
pub connection: GamepadConnection,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GamepadConnectionEvent {
|
|
|
|
/// Creates a [`GamepadConnectionEvent`].
|
|
|
|
pub fn new(gamepad: Entity, connection: GamepadConnection) -> Self {
|
|
|
|
Self {
|
|
|
|
gamepad,
|
|
|
|
connection,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Is the gamepad connected?
|
|
|
|
pub fn connected(&self) -> bool {
|
|
|
|
matches!(self.connection, GamepadConnection::Connected(_))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Is the gamepad disconnected?
|
|
|
|
pub fn disconnected(&self) -> bool {
|
|
|
|
!self.connected()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// [`GamepadButton`] event triggered by a digital state change
|
|
|
|
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
|
|
|
|
#[reflect(Debug, PartialEq)]
|
|
|
|
#[cfg_attr(
|
|
|
|
feature = "serialize",
|
|
|
|
derive(serde::Serialize, serde::Deserialize),
|
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
|
|
|
pub struct GamepadButtonStateChangedEvent {
|
|
|
|
/// The entity that represents this gamepad.
|
|
|
|
pub entity: Entity,
|
|
|
|
/// The gamepad button assigned to the event.
|
|
|
|
pub button: GamepadButton,
|
|
|
|
/// The pressed state of the button.
|
|
|
|
pub state: ButtonState,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GamepadButtonStateChangedEvent {
|
|
|
|
/// Creates a new [`GamepadButtonStateChangedEvent`]
|
|
|
|
pub fn new(entity: Entity, button: GamepadButton, state: ButtonState) -> Self {
|
|
|
|
Self {
|
|
|
|
entity,
|
|
|
|
button,
|
|
|
|
state,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// [`GamepadButton`] event triggered by an analog state change
|
|
|
|
#[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)]
|
|
|
|
#[reflect(Debug, PartialEq)]
|
|
|
|
#[cfg_attr(
|
|
|
|
feature = "serialize",
|
|
|
|
derive(serde::Serialize, serde::Deserialize),
|
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
|
|
|
pub struct GamepadButtonChangedEvent {
|
|
|
|
/// The entity that represents this gamepad.
|
|
|
|
pub entity: Entity,
|
|
|
|
/// The gamepad button assigned to the event.
|
|
|
|
pub button: GamepadButton,
|
|
|
|
/// The pressed state of the button.
|
|
|
|
pub state: ButtonState,
|
|
|
|
/// The analog value of the button.
|
|
|
|
pub value: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GamepadButtonChangedEvent {
|
|
|
|
/// Creates a new [`GamepadButtonChangedEvent`]
|
|
|
|
pub fn new(entity: Entity, button: GamepadButton, state: ButtonState, value: f32) -> Self {
|
|
|
|
Self {
|
|
|
|
entity,
|
|
|
|
button,
|
|
|
|
state,
|
|
|
|
value,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// [`GamepadAxis`] event triggered by an analog state change
|
|
|
|
#[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)]
|
|
|
|
#[reflect(Debug, PartialEq)]
|
|
|
|
#[cfg_attr(
|
|
|
|
feature = "serialize",
|
|
|
|
derive(serde::Serialize, serde::Deserialize),
|
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
|
|
|
pub struct GamepadAxisChangedEvent {
|
|
|
|
/// The entity that represents this gamepad.
|
|
|
|
pub entity: Entity,
|
|
|
|
/// The gamepad axis assigned to the event.
|
|
|
|
pub axis: GamepadAxis,
|
|
|
|
/// The value of this axis.
|
|
|
|
pub value: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GamepadAxisChangedEvent {
|
|
|
|
/// Creates a new [`GamepadAxisChangedEvent`]
|
|
|
|
pub fn new(entity: Entity, axis: GamepadAxis, value: f32) -> Self {
|
|
|
|
Self {
|
|
|
|
entity,
|
|
|
|
axis,
|
|
|
|
value,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-17 14:38:55 +00:00
|
|
|
|
|
|
|
/// Errors that occur when setting axis settings for gamepad input.
|
|
|
|
#[derive(Error, Debug, PartialEq)]
|
|
|
|
pub enum AxisSettingsError {
|
|
|
|
/// The given parameter `livezone_lowerbound` was not in range -1.0..=0.0.
|
|
|
|
#[error("invalid livezone_lowerbound {0}, expected value [-1.0..=0.0]")]
|
|
|
|
LiveZoneLowerBoundOutOfRange(f32),
|
|
|
|
/// The given parameter `deadzone_lowerbound` was not in range -1.0..=0.0.
|
|
|
|
#[error("invalid deadzone_lowerbound {0}, expected value [-1.0..=0.0]")]
|
|
|
|
DeadZoneLowerBoundOutOfRange(f32),
|
|
|
|
/// The given parameter `deadzone_lowerbound` was not in range -1.0..=0.0.
|
|
|
|
#[error("invalid deadzone_upperbound {0}, expected value [0.0..=1.0]")]
|
|
|
|
DeadZoneUpperBoundOutOfRange(f32),
|
|
|
|
/// The given parameter `deadzone_lowerbound` was not in range -1.0..=0.0.
|
|
|
|
#[error("invalid livezone_upperbound {0}, expected value [0.0..=1.0]")]
|
|
|
|
LiveZoneUpperBoundOutOfRange(f32),
|
|
|
|
/// Parameter `livezone_lowerbound` was not less than or equal to parameter `deadzone_lowerbound`.
|
|
|
|
#[error("invalid parameter values livezone_lowerbound {} deadzone_lowerbound {}, expected livezone_lowerbound <= deadzone_lowerbound", .livezone_lowerbound, .deadzone_lowerbound)]
|
|
|
|
LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The value of the `livezone_lowerbound` parameter.
|
2022-10-17 14:38:55 +00:00
|
|
|
livezone_lowerbound: f32,
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The value of the `deadzone_lowerbound` parameter.
|
2022-10-17 14:38:55 +00:00
|
|
|
deadzone_lowerbound: f32,
|
|
|
|
},
|
|
|
|
/// Parameter `deadzone_upperbound` was not less than or equal to parameter `livezone_upperbound`.
|
|
|
|
#[error("invalid parameter values livezone_upperbound {} deadzone_upperbound {}, expected deadzone_upperbound <= livezone_upperbound", .livezone_upperbound, .deadzone_upperbound)]
|
|
|
|
DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The value of the `livezone_upperbound` parameter.
|
2022-10-17 14:38:55 +00:00
|
|
|
livezone_upperbound: f32,
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The value of the `deadzone_upperbound` parameter.
|
2022-10-17 14:38:55 +00:00
|
|
|
deadzone_upperbound: f32,
|
|
|
|
},
|
|
|
|
/// The given parameter was not in range 0.0..=2.0.
|
|
|
|
#[error("invalid threshold {0}, expected 0.0 <= threshold <= 2.0")]
|
|
|
|
Threshold(f32),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Errors that occur when setting button settings for gamepad input.
|
|
|
|
#[derive(Error, Debug, PartialEq)]
|
|
|
|
pub enum ButtonSettingsError {
|
2022-11-12 22:59:49 +00:00
|
|
|
/// The given parameter was not in range 0.0..=1.0.
|
2022-10-17 14:38:55 +00:00
|
|
|
#[error("invalid release_threshold {0}, expected value [0.0..=1.0]")]
|
|
|
|
ReleaseThresholdOutOfRange(f32),
|
2022-11-12 22:59:49 +00:00
|
|
|
/// The given parameter was not in range 0.0..=1.0.
|
2022-10-17 14:38:55 +00:00
|
|
|
#[error("invalid press_threshold {0}, expected [0.0..=1.0]")]
|
|
|
|
PressThresholdOutOfRange(f32),
|
|
|
|
/// Parameter `release_threshold` was not less than or equal to `press_threshold`.
|
|
|
|
#[error("invalid parameter values release_threshold {} press_threshold {}, expected release_threshold <= press_threshold", .release_threshold, .press_threshold)]
|
|
|
|
ReleaseThresholdGreaterThanPressThreshold {
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The value of the `press_threshold` parameter.
|
2022-10-17 14:38:55 +00:00
|
|
|
press_threshold: f32,
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The value of the `release_threshold` parameter.
|
2022-10-17 14:38:55 +00:00
|
|
|
release_threshold: f32,
|
|
|
|
},
|
|
|
|
}
|
2020-10-21 17:27:00 +00:00
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// The [`Gamepad`] [`component`](Component) stores a connected gamepad's metadata such as the `name` and its [`GamepadButton`] and [`GamepadAxis`].
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// The [`entity`](Entity) representing a gamepad and its [`minimal components`](GamepadSettings) are automatically managed.
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// # Usage
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// The only way to obtain a [`Gamepad`] is by [`query`](Query).
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use bevy_input::gamepad::{Gamepad, GamepadAxis, GamepadButton};
|
|
|
|
/// # use bevy_ecs::system::Query;
|
|
|
|
/// #
|
|
|
|
/// fn gamepad_usage_system(gamepads: Query<&Gamepad>) {
|
|
|
|
/// for gamepad in gamepads.iter() {
|
|
|
|
/// println!("{}", gamepad.name());
|
|
|
|
///
|
|
|
|
/// if gamepad.just_pressed(GamepadButton::North) {
|
|
|
|
/// println!("{} just pressed North", gamepad.name())
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// if let Some(left_stick_x) = gamepad.get(GamepadAxis::LeftStickX) {
|
|
|
|
/// println!("left stick X: {}", left_stick_x)
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
#[derive(Component, Debug)]
|
2024-10-08 12:19:38 +00:00
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
#[require(GamepadSettings)]
|
2022-05-02 13:20:55 +00:00
|
|
|
pub struct Gamepad {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
info: GamepadInfo,
|
|
|
|
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
|
|
|
|
pub(crate) digital: ButtonInput<GamepadButton>,
|
|
|
|
/// [`Axis`] of [`GamepadButton`] representing their analog state.
|
|
|
|
pub(crate) analog: Axis<GamepadInput>,
|
2022-05-02 13:20:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Gamepad {
|
2024-10-08 12:19:38 +00:00
|
|
|
/// Creates a gamepad with the given metadata.
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
fn new(info: GamepadInfo) -> Self {
|
|
|
|
let mut analog = Axis::default();
|
|
|
|
for button in GamepadButton::all().iter().copied() {
|
|
|
|
analog.set(button.into(), 0.0);
|
|
|
|
}
|
|
|
|
for axis_type in GamepadAxis::all().iter().copied() {
|
|
|
|
analog.set(axis_type.into(), 0.0);
|
|
|
|
}
|
|
|
|
Self {
|
|
|
|
info,
|
|
|
|
analog,
|
|
|
|
digital: ButtonInput::default(),
|
|
|
|
}
|
2022-05-02 13:20:55 +00:00
|
|
|
}
|
2020-09-18 21:43:47 +00:00
|
|
|
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The name of the gamepad.
|
|
|
|
///
|
|
|
|
/// This name is generally defined by the OS.
|
|
|
|
///
|
|
|
|
/// For example on Windows the name may be "HID-compliant game controller".
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
pub fn name(&self) -> &str {
|
|
|
|
self.info.name.as_str()
|
|
|
|
}
|
2022-10-24 14:33:50 +00:00
|
|
|
|
2024-10-08 12:19:38 +00:00
|
|
|
/// Returns the USB vendor ID as assigned by the USB-IF, if available.
|
|
|
|
pub fn vendor_id(&self) -> Option<u16> {
|
|
|
|
self.info.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.info.product_id
|
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// 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.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
|
|
|
|
y: self.get(GamepadAxis::LeftStickY).unwrap_or(0.0),
|
|
|
|
}
|
|
|
|
}
|
2021-12-08 20:28:08 +00:00
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Returns the right stick as a [`Vec2`]
|
|
|
|
pub fn right_stick(&self) -> Vec2 {
|
|
|
|
Vec2 {
|
|
|
|
x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0),
|
|
|
|
y: self.get(GamepadAxis::RightStickY).unwrap_or(0.0),
|
|
|
|
}
|
2021-12-08 20:28:08 +00:00
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Returns the directional pad as a [`Vec2`]
|
|
|
|
pub fn dpad(&self) -> Vec2 {
|
|
|
|
Vec2 {
|
|
|
|
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),
|
|
|
|
}
|
2022-10-24 14:33:50 +00:00
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Returns `true` if the [`GamepadButton`] has been pressed.
|
|
|
|
pub fn pressed(&self, button_type: GamepadButton) -> bool {
|
|
|
|
self.digital.pressed(button_type)
|
2021-12-08 20:28:08 +00:00
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Returns `true` if any item in [`GamepadButton`] has been pressed.
|
|
|
|
pub fn any_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
|
|
|
|
button_inputs
|
|
|
|
.into_iter()
|
|
|
|
.any(|button_type| self.pressed(button_type))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if all items in [`GamepadButton`] have been pressed.
|
|
|
|
pub fn all_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
|
|
|
|
button_inputs
|
|
|
|
.into_iter()
|
|
|
|
.all(|button_type| self.pressed(button_type))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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)
|
2021-12-08 20:28:08 +00:00
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Returns `true` if any item in [`GamepadButton`] has been pressed during the current frame.
|
|
|
|
pub fn any_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
|
|
|
|
button_inputs
|
|
|
|
.into_iter()
|
|
|
|
.any(|button_type| self.just_pressed(button_type))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if all items in [`GamepadButton`] have been just pressed.
|
|
|
|
pub fn all_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
|
|
|
|
button_inputs
|
|
|
|
.into_iter()
|
|
|
|
.all(|button_type| self.just_pressed(button_type))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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 [`GamepadButton`] has just been released.
|
|
|
|
pub fn any_just_released(
|
|
|
|
&self,
|
|
|
|
button_inputs: impl IntoIterator<Item = GamepadButton>,
|
|
|
|
) -> bool {
|
|
|
|
button_inputs
|
|
|
|
.into_iter()
|
|
|
|
.any(|button_type| self.just_released(button_type))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if all items in [`GamepadButton`] have just been released.
|
|
|
|
pub fn all_just_released(
|
|
|
|
&self,
|
|
|
|
button_inputs: impl IntoIterator<Item = GamepadButton>,
|
|
|
|
) -> bool {
|
|
|
|
button_inputs
|
|
|
|
.into_iter()
|
|
|
|
.all(|button_type| self.just_released(button_type))
|
2021-12-08 20:28:08 +00:00
|
|
|
}
|
2024-10-08 12:19:38 +00:00
|
|
|
|
|
|
|
/// 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()
|
|
|
|
}
|
2021-12-08 20:28:08 +00:00
|
|
|
}
|
|
|
|
|
2024-10-08 12:19:38 +00:00
|
|
|
// Note that we don't expose `gilrs::Gamepad::uuid` due to
|
|
|
|
// https://gitlab.com/gilrs-project/gilrs/-/issues/153.
|
|
|
|
//
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Metadata associated with a [`Gamepad`].
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
|
|
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
|
|
|
#[cfg_attr(
|
|
|
|
all(feature = "serialize", feature = "bevy_reflect"),
|
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
|
|
|
pub struct GamepadInfo {
|
|
|
|
/// The name of the gamepad.
|
|
|
|
///
|
|
|
|
/// This name is generally defined by the OS.
|
|
|
|
///
|
|
|
|
/// For example on Windows the name may be "HID-compliant game controller".
|
|
|
|
pub name: String,
|
2024-10-08 12:19:38 +00:00
|
|
|
|
|
|
|
/// The USB vendor ID as assigned by the USB-IF, if available.
|
|
|
|
pub vendor_id: Option<u16>,
|
|
|
|
|
|
|
|
/// The USB product ID as assigned by the [vendor], if available.
|
|
|
|
///
|
|
|
|
/// [vendor]: Self::vendor_id
|
|
|
|
pub product_id: Option<u16>,
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents gamepad input types that are mapped in the range [0.0, 1.0].
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
|
|
|
/// ## Usage
|
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// This is used to determine which button has changed its value when receiving gamepad button events
|
|
|
|
/// It is also used in the [`Gamepad`] component.
|
2024-07-08 01:09:07 +00:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
|
|
#[cfg_attr(
|
|
|
|
feature = "bevy_reflect",
|
|
|
|
derive(Reflect),
|
|
|
|
reflect(Debug, Hash, PartialEq)
|
|
|
|
)]
|
|
|
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
2022-10-26 19:52:20 +00:00
|
|
|
#[cfg_attr(
|
2024-07-08 01:09:07 +00:00
|
|
|
all(feature = "serialize", feature = "bevy_reflect"),
|
2022-10-26 19:52:20 +00:00
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
pub enum GamepadButton {
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The bottom action button of the action pad (i.e. PS: Cross, Xbox: A).
|
2020-09-18 21:43:47 +00:00
|
|
|
South,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The right action button of the action pad (i.e. PS: Circle, Xbox: B).
|
2020-09-18 21:43:47 +00:00
|
|
|
East,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The upper action button of the action pad (i.e. PS: Triangle, Xbox: Y).
|
2020-09-18 21:43:47 +00:00
|
|
|
North,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The left action button of the action pad (i.e. PS: Square, Xbox: X).
|
2020-09-18 21:43:47 +00:00
|
|
|
West,
|
2022-08-05 02:28:07 +00:00
|
|
|
|
|
|
|
/// The C button.
|
2020-09-18 21:43:47 +00:00
|
|
|
C,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The Z button.
|
2020-09-18 21:43:47 +00:00
|
|
|
Z,
|
2022-08-05 02:28:07 +00:00
|
|
|
|
|
|
|
/// The first left trigger.
|
2020-09-18 21:43:47 +00:00
|
|
|
LeftTrigger,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The second left trigger.
|
2020-09-18 21:43:47 +00:00
|
|
|
LeftTrigger2,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The first right trigger.
|
2020-09-18 21:43:47 +00:00
|
|
|
RightTrigger,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The second right trigger.
|
2020-09-18 21:43:47 +00:00
|
|
|
RightTrigger2,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The select button.
|
2020-09-18 21:43:47 +00:00
|
|
|
Select,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The start button.
|
2020-09-18 21:43:47 +00:00
|
|
|
Start,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The mode button.
|
2020-09-18 21:43:47 +00:00
|
|
|
Mode,
|
2022-08-05 02:28:07 +00:00
|
|
|
|
|
|
|
/// The left thumb stick button.
|
2020-09-18 21:43:47 +00:00
|
|
|
LeftThumb,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The right thumb stick button.
|
2020-09-18 21:43:47 +00:00
|
|
|
RightThumb,
|
2022-08-05 02:28:07 +00:00
|
|
|
|
|
|
|
/// The up button of the D-Pad.
|
2020-09-18 21:43:47 +00:00
|
|
|
DPadUp,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The down button of the D-Pad.
|
2020-09-18 21:43:47 +00:00
|
|
|
DPadDown,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The left button of the D-Pad.
|
2020-09-18 21:43:47 +00:00
|
|
|
DPadLeft,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The right button of the D-Pad.
|
2020-09-18 21:43:47 +00:00
|
|
|
DPadRight,
|
2022-09-02 02:16:18 +00:00
|
|
|
|
|
|
|
/// Miscellaneous buttons, considered non-standard (i.e. Extra buttons on a flight stick that do not have a gamepad equivalent).
|
|
|
|
Other(u8),
|
2020-09-18 21:43:47 +00:00
|
|
|
}
|
|
|
|
|
2022-05-02 13:20:55 +00:00
|
|
|
impl GamepadButton {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Returns an array of all the standard [`GamepadButton`]
|
|
|
|
pub const fn all() -> [GamepadButton; 19] {
|
|
|
|
[
|
|
|
|
GamepadButton::South,
|
|
|
|
GamepadButton::East,
|
|
|
|
GamepadButton::North,
|
|
|
|
GamepadButton::West,
|
|
|
|
GamepadButton::C,
|
|
|
|
GamepadButton::Z,
|
|
|
|
GamepadButton::LeftTrigger,
|
|
|
|
GamepadButton::LeftTrigger2,
|
|
|
|
GamepadButton::RightTrigger,
|
|
|
|
GamepadButton::RightTrigger2,
|
|
|
|
GamepadButton::Select,
|
|
|
|
GamepadButton::Start,
|
|
|
|
GamepadButton::Mode,
|
|
|
|
GamepadButton::LeftThumb,
|
|
|
|
GamepadButton::RightThumb,
|
|
|
|
GamepadButton::DPadUp,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
GamepadButton::DPadLeft,
|
|
|
|
GamepadButton::DPadRight,
|
|
|
|
]
|
2022-05-02 13:20:55 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-18 21:43:47 +00:00
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Represents gamepad input types that are mapped in the range [-1.0, 1.0]
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
|
|
|
/// ## Usage
|
|
|
|
///
|
|
|
|
/// This is used to determine which axis has changed its value when receiving a
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// gamepad axis event. It is also used in the [`Gamepad`] component.
|
2024-07-08 01:09:07 +00:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
|
|
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
2022-10-26 19:52:20 +00:00
|
|
|
#[cfg_attr(
|
2024-07-08 01:09:07 +00:00
|
|
|
all(feature = "serialize", feature = "bevy_reflect"),
|
2022-10-26 19:52:20 +00:00
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
pub enum GamepadAxis {
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The horizontal value of the left stick.
|
2020-09-18 21:43:47 +00:00
|
|
|
LeftStickX,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The vertical value of the left stick.
|
2020-09-18 21:43:47 +00:00
|
|
|
LeftStickY,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The value of the left `Z` button.
|
2020-09-18 21:43:47 +00:00
|
|
|
LeftZ,
|
2022-08-05 02:28:07 +00:00
|
|
|
|
|
|
|
/// The horizontal value of the right stick.
|
2020-09-18 21:43:47 +00:00
|
|
|
RightStickX,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The vertical value of the right stick.
|
2020-09-18 21:43:47 +00:00
|
|
|
RightStickY,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The value of the right `Z` button.
|
2020-09-18 21:43:47 +00:00
|
|
|
RightZ,
|
2022-09-02 02:16:18 +00:00
|
|
|
|
|
|
|
/// Non-standard support for other axis types (i.e. HOTAS sliders, potentiometers, etc).
|
|
|
|
Other(u8),
|
2020-09-18 21:43:47 +00:00
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
impl GamepadAxis {
|
|
|
|
/// Returns an array of all the standard [`GamepadAxis`].
|
|
|
|
pub const fn all() -> [GamepadAxis; 6] {
|
|
|
|
[
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
GamepadAxis::LeftStickY,
|
|
|
|
GamepadAxis::LeftZ,
|
|
|
|
GamepadAxis::RightStickX,
|
|
|
|
GamepadAxis::RightStickY,
|
|
|
|
GamepadAxis::RightZ,
|
|
|
|
]
|
|
|
|
}
|
2022-05-02 13:20:55 +00:00
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Encapsulation over [`GamepadAxis`] and [`GamepadButton`]
|
|
|
|
// This is done so Gamepad can share a single Axis<T> and simplifies the API by having only one get/get_unclamped method
|
|
|
|
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
|
2024-10-08 12:19:38 +00:00
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
pub enum GamepadInput {
|
|
|
|
/// A [`GamepadAxis`]
|
|
|
|
Axis(GamepadAxis),
|
|
|
|
/// A [`GamepadButton`]
|
|
|
|
Button(GamepadButton),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<GamepadAxis> for GamepadInput {
|
|
|
|
fn from(value: GamepadAxis) -> Self {
|
|
|
|
GamepadInput::Axis(value)
|
2022-05-02 13:20:55 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-21 17:27:00 +00:00
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
impl From<GamepadButton> for GamepadInput {
|
|
|
|
fn from(value: GamepadButton) -> Self {
|
|
|
|
GamepadInput::Button(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gamepad settings component.
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
|
|
|
/// ## Usage
|
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// It is used to create a `bevy` component that stores the settings of [`GamepadButton`] and [`GamepadAxis`] in [`Gamepad`].
|
|
|
|
/// If no user defined [`ButtonSettings`], [`AxisSettings`], or [`ButtonAxisSettings`]
|
2022-08-05 02:28:07 +00:00
|
|
|
/// are defined, the default settings of each are used as a fallback accordingly.
|
|
|
|
///
|
|
|
|
/// ## Note
|
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// The [`GamepadSettings`] are used to determine when raw gamepad events
|
|
|
|
/// should register. Events that don't meet the change thresholds defined in [`GamepadSettings`]
|
|
|
|
/// will not register. To modify these settings, mutate the corresponding component.
|
|
|
|
#[derive(Component, Clone, Default, Debug)]
|
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Default))]
|
2020-10-21 22:56:07 +00:00
|
|
|
pub struct GamepadSettings {
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The default button settings.
|
2020-10-21 22:56:07 +00:00
|
|
|
pub default_button_settings: ButtonSettings,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The default axis settings.
|
2020-10-21 22:56:07 +00:00
|
|
|
pub default_axis_settings: AxisSettings,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The default button axis settings.
|
2020-10-21 22:56:07 +00:00
|
|
|
pub default_button_axis_settings: ButtonAxisSettings,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The user defined button settings.
|
2020-10-21 22:56:07 +00:00
|
|
|
pub button_settings: HashMap<GamepadButton, ButtonSettings>,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The user defined axis settings.
|
2020-10-21 22:56:07 +00:00
|
|
|
pub axis_settings: HashMap<GamepadAxis, AxisSettings>,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The user defined button axis settings.
|
2020-10-21 22:56:07 +00:00
|
|
|
pub button_axis_settings: HashMap<GamepadButton, ButtonAxisSettings>,
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 22:56:07 +00:00
|
|
|
impl GamepadSettings {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Returns the [`ButtonSettings`] of the [`GamepadButton`].
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
|
|
|
/// If no user defined [`ButtonSettings`] are specified the default [`ButtonSettings`] get returned.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// # use bevy_input::gamepad::{GamepadSettings, GamepadButton};
|
2022-08-05 02:28:07 +00:00
|
|
|
/// #
|
|
|
|
/// # let settings = GamepadSettings::default();
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// let button_settings = settings.get_button_settings(GamepadButton::South);
|
2022-08-05 02:28:07 +00:00
|
|
|
/// ```
|
2020-10-21 22:56:07 +00:00
|
|
|
pub fn get_button_settings(&self, button: GamepadButton) -> &ButtonSettings {
|
2020-10-21 17:27:00 +00:00
|
|
|
self.button_settings
|
|
|
|
.get(&button)
|
2020-10-21 22:56:07 +00:00
|
|
|
.unwrap_or(&self.default_button_settings)
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Returns the [`AxisSettings`] of the [`GamepadAxis`].
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
|
|
|
/// If no user defined [`AxisSettings`] are specified the default [`AxisSettings`] get returned.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// # use bevy_input::gamepad::{GamepadSettings, GamepadAxis};
|
2022-08-05 02:28:07 +00:00
|
|
|
/// #
|
|
|
|
/// # let settings = GamepadSettings::default();
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// let axis_settings = settings.get_axis_settings(GamepadAxis::LeftStickX);
|
2022-08-05 02:28:07 +00:00
|
|
|
/// ```
|
2020-10-21 22:56:07 +00:00
|
|
|
pub fn get_axis_settings(&self, axis: GamepadAxis) -> &AxisSettings {
|
2020-10-21 17:27:00 +00:00
|
|
|
self.axis_settings
|
|
|
|
.get(&axis)
|
2020-10-21 22:56:07 +00:00
|
|
|
.unwrap_or(&self.default_axis_settings)
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Returns the [`ButtonAxisSettings`] of the [`GamepadButton`].
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
|
|
|
/// If no user defined [`ButtonAxisSettings`] are specified the default [`ButtonAxisSettings`] get returned.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// # use bevy_input::gamepad::{GamepadSettings, GamepadButton};
|
2022-08-05 02:28:07 +00:00
|
|
|
/// #
|
|
|
|
/// # let settings = GamepadSettings::default();
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// let button_axis_settings = settings.get_button_axis_settings(GamepadButton::South);
|
2022-08-05 02:28:07 +00:00
|
|
|
/// ```
|
2020-10-21 22:56:07 +00:00
|
|
|
pub fn get_button_axis_settings(&self, button: GamepadButton) -> &ButtonAxisSettings {
|
2020-10-21 17:27:00 +00:00
|
|
|
self.button_axis_settings
|
|
|
|
.get(&button)
|
2020-10-21 22:56:07 +00:00
|
|
|
.unwrap_or(&self.default_button_axis_settings)
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-17 14:38:55 +00:00
|
|
|
/// Manages settings for gamepad buttons.
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// It is used inside [`GamepadSettings`] to define the threshold for a [`GamepadButton`]
|
2022-10-17 14:38:55 +00:00
|
|
|
/// to be considered pressed or released. A button is considered pressed if the `press_threshold`
|
|
|
|
/// value is surpassed and released if the `release_threshold` value is undercut.
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
2022-10-17 14:38:55 +00:00
|
|
|
/// Allowed values: `0.0 <= ``release_threshold`` <= ``press_threshold`` <= 1.0`
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
2024-07-08 01:09:07 +00:00
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Default))]
|
2020-10-21 22:56:07 +00:00
|
|
|
pub struct ButtonSettings {
|
2022-10-17 14:38:55 +00:00
|
|
|
press_threshold: f32,
|
|
|
|
release_threshold: f32,
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 22:56:07 +00:00
|
|
|
impl Default for ButtonSettings {
|
2020-10-21 17:27:00 +00:00
|
|
|
fn default() -> Self {
|
2020-10-21 22:56:07 +00:00
|
|
|
ButtonSettings {
|
2022-10-17 14:38:55 +00:00
|
|
|
press_threshold: 0.75,
|
|
|
|
release_threshold: 0.65,
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-21 22:56:07 +00:00
|
|
|
impl ButtonSettings {
|
2022-10-17 14:38:55 +00:00
|
|
|
/// Creates a new [`ButtonSettings`] instance.
|
|
|
|
///
|
|
|
|
/// # Parameters
|
|
|
|
///
|
|
|
|
/// + `press_threshold` is the button input value above which the button is considered pressed.
|
|
|
|
/// + `release_threshold` is the button input value below which the button is considered released.
|
|
|
|
///
|
|
|
|
/// Restrictions:
|
|
|
|
/// + `0.0 <= ``release_threshold`` <= ``press_threshold`` <= 1.0`
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// If the restrictions are not met, returns one of
|
|
|
|
/// `GamepadSettingsError::ButtonReleaseThresholdOutOfRange`,
|
|
|
|
/// `GamepadSettingsError::ButtonPressThresholdOutOfRange`, or
|
|
|
|
/// `GamepadSettingsError::ButtonReleaseThresholdGreaterThanPressThreshold`.
|
|
|
|
pub fn new(
|
|
|
|
press_threshold: f32,
|
|
|
|
release_threshold: f32,
|
|
|
|
) -> Result<ButtonSettings, ButtonSettingsError> {
|
|
|
|
if !(0.0..=1.0).contains(&release_threshold) {
|
|
|
|
Err(ButtonSettingsError::ReleaseThresholdOutOfRange(
|
|
|
|
release_threshold,
|
|
|
|
))
|
|
|
|
} else if !(0.0..=1.0).contains(&press_threshold) {
|
|
|
|
Err(ButtonSettingsError::PressThresholdOutOfRange(
|
|
|
|
press_threshold,
|
|
|
|
))
|
|
|
|
} else if release_threshold > press_threshold {
|
|
|
|
Err(
|
|
|
|
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
|
|
|
|
press_threshold,
|
|
|
|
release_threshold,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
Ok(ButtonSettings {
|
|
|
|
press_threshold,
|
|
|
|
release_threshold,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 02:28:07 +00:00
|
|
|
/// Returns `true` if the button is pressed.
|
|
|
|
///
|
2022-10-17 14:38:55 +00:00
|
|
|
/// A button is considered pressed if the `value` passed is greater than or equal to the press threshold.
|
2023-11-15 14:29:33 +00:00
|
|
|
pub fn is_pressed(&self, value: f32) -> bool {
|
2022-10-17 14:38:55 +00:00
|
|
|
value >= self.press_threshold
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
|
2022-08-05 02:28:07 +00:00
|
|
|
/// Returns `true` if the button is released.
|
|
|
|
///
|
2022-10-17 14:38:55 +00:00
|
|
|
/// A button is considered released if the `value` passed is lower than or equal to the release threshold.
|
2023-11-15 14:29:33 +00:00
|
|
|
pub fn is_released(&self, value: f32) -> bool {
|
2022-10-17 14:38:55 +00:00
|
|
|
value <= self.release_threshold
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the button input threshold above which the button is considered pressed.
|
|
|
|
pub fn press_threshold(&self) -> f32 {
|
|
|
|
self.press_threshold
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to set the button input threshold above which the button is considered pressed.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// If the value passed is outside the range [release threshold..=1.0], returns either
|
|
|
|
/// `GamepadSettingsError::ButtonPressThresholdOutOfRange` or
|
|
|
|
/// `GamepadSettingsError::ButtonReleaseThresholdGreaterThanPressThreshold`.
|
|
|
|
pub fn try_set_press_threshold(&mut self, value: f32) -> Result<(), ButtonSettingsError> {
|
|
|
|
if (self.release_threshold..=1.0).contains(&value) {
|
|
|
|
self.press_threshold = value;
|
|
|
|
Ok(())
|
|
|
|
} else if !(0.0..1.0).contains(&value) {
|
|
|
|
Err(ButtonSettingsError::PressThresholdOutOfRange(value))
|
|
|
|
} else {
|
|
|
|
Err(
|
|
|
|
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
|
|
|
|
press_threshold: value,
|
|
|
|
release_threshold: self.release_threshold,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to set the button input threshold above which the button is considered pressed.
|
|
|
|
/// If the value passed is outside the range [release threshold..=1.0], the value will not be changed.
|
|
|
|
///
|
|
|
|
/// Returns the new value of the press threshold.
|
|
|
|
pub fn set_press_threshold(&mut self, value: f32) -> f32 {
|
|
|
|
self.try_set_press_threshold(value).ok();
|
|
|
|
self.press_threshold
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the button input threshold below which the button is considered released.
|
|
|
|
pub fn release_threshold(&self) -> f32 {
|
|
|
|
self.release_threshold
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to set the button input threshold below which the button is considered released.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// If the value passed is outside the range [0.0..=press threshold], returns
|
|
|
|
/// `ButtonSettingsError::ReleaseThresholdOutOfRange` or
|
|
|
|
/// `ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold`.
|
|
|
|
pub fn try_set_release_threshold(&mut self, value: f32) -> Result<(), ButtonSettingsError> {
|
|
|
|
if (0.0..=self.press_threshold).contains(&value) {
|
|
|
|
self.release_threshold = value;
|
|
|
|
Ok(())
|
|
|
|
} else if !(0.0..1.0).contains(&value) {
|
|
|
|
Err(ButtonSettingsError::ReleaseThresholdOutOfRange(value))
|
|
|
|
} else {
|
|
|
|
Err(
|
|
|
|
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
|
|
|
|
press_threshold: self.press_threshold,
|
|
|
|
release_threshold: value,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to set the button input threshold below which the button is considered released. If the
|
|
|
|
/// value passed is outside the range [0.0..=press threshold], the value will not be changed.
|
|
|
|
///
|
|
|
|
/// Returns the new value of the release threshold.
|
|
|
|
pub fn set_release_threshold(&mut self, value: f32) -> f32 {
|
|
|
|
self.try_set_release_threshold(value).ok();
|
|
|
|
self.release_threshold
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 02:28:07 +00:00
|
|
|
/// Settings for a [`GamepadAxis`].
|
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// It is used inside the [`GamepadSettings`] to define the sensitivity range and
|
2022-08-05 02:28:07 +00:00
|
|
|
/// threshold for an axis.
|
2022-10-17 14:38:55 +00:00
|
|
|
/// Values that are higher than `livezone_upperbound` will be rounded up to 1.0.
|
|
|
|
/// Values that are lower than `livezone_lowerbound` will be rounded down to -1.0.
|
|
|
|
/// Values that are in-between `deadzone_lowerbound` and `deadzone_upperbound` will be rounded
|
|
|
|
/// to 0.0.
|
|
|
|
/// Otherwise, values will not be rounded.
|
2021-12-18 20:00:18 +00:00
|
|
|
///
|
2022-10-17 14:38:55 +00:00
|
|
|
/// The valid range is `[-1.0, 1.0]`.
|
2024-07-08 01:09:07 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Default))]
|
2020-10-21 22:56:07 +00:00
|
|
|
pub struct AxisSettings {
|
2023-05-03 19:36:31 +00:00
|
|
|
/// Values that are higher than `livezone_upperbound` will be rounded up to 1.0.
|
2022-10-17 14:38:55 +00:00
|
|
|
livezone_upperbound: f32,
|
|
|
|
/// Positive values that are less than `deadzone_upperbound` will be rounded down to 0.0.
|
|
|
|
deadzone_upperbound: f32,
|
|
|
|
/// Negative values that are greater than `deadzone_lowerbound` will be rounded up to 0.0.
|
|
|
|
deadzone_lowerbound: f32,
|
|
|
|
/// Values that are lower than `livezone_lowerbound` will be rounded down to -1.0.
|
|
|
|
livezone_lowerbound: f32,
|
|
|
|
/// `threshold` defines the minimum difference between old and new values to apply the changes.
|
|
|
|
threshold: f32,
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 22:56:07 +00:00
|
|
|
impl Default for AxisSettings {
|
2020-10-21 17:27:00 +00:00
|
|
|
fn default() -> Self {
|
2020-10-21 22:56:07 +00:00
|
|
|
AxisSettings {
|
2023-10-12 17:58:32 +00:00
|
|
|
livezone_upperbound: 1.0,
|
2022-10-17 14:38:55 +00:00
|
|
|
deadzone_upperbound: 0.05,
|
|
|
|
deadzone_lowerbound: -0.05,
|
2023-10-12 17:58:32 +00:00
|
|
|
livezone_lowerbound: -1.0,
|
2020-10-21 17:27:00 +00:00
|
|
|
threshold: 0.01,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-21 22:56:07 +00:00
|
|
|
impl AxisSettings {
|
2023-01-16 18:13:04 +00:00
|
|
|
/// Creates a new [`AxisSettings`] instance.
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
2022-10-17 14:38:55 +00:00
|
|
|
/// # Arguments
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
2022-10-17 14:38:55 +00:00
|
|
|
/// + `livezone_lowerbound` - the value below which inputs will be rounded down to -1.0.
|
|
|
|
/// + `deadzone_lowerbound` - the value above which negative inputs will be rounded up to 0.0.
|
|
|
|
/// + `deadzone_upperbound` - the value below which positive inputs will be rounded down to 0.0.
|
|
|
|
/// + `livezone_upperbound` - the value above which inputs will be rounded up to 1.0.
|
|
|
|
/// + `threshold` - the minimum value by which input must change before the change is registered.
|
|
|
|
///
|
|
|
|
/// Restrictions:
|
2023-01-16 18:13:04 +00:00
|
|
|
///
|
|
|
|
/// + `-1.0 <= livezone_lowerbound <= deadzone_lowerbound <= 0.0`
|
|
|
|
/// + `0.0 <= deadzone_upperbound <= livezone_upperbound <= 1.0`
|
|
|
|
/// + `0.0 <= threshold <= 2.0`
|
2022-10-17 14:38:55 +00:00
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
2023-04-23 17:28:36 +00:00
|
|
|
/// Returns an [`AxisSettingsError`] if any restrictions on the zone values are not met.
|
|
|
|
/// If the zone restrictions are met, but the `threshold` value restrictions are not met,
|
|
|
|
/// returns [`AxisSettingsError::Threshold`].
|
2022-10-17 14:38:55 +00:00
|
|
|
pub fn new(
|
|
|
|
livezone_lowerbound: f32,
|
|
|
|
deadzone_lowerbound: f32,
|
|
|
|
deadzone_upperbound: f32,
|
|
|
|
livezone_upperbound: f32,
|
|
|
|
threshold: f32,
|
|
|
|
) -> Result<AxisSettings, AxisSettingsError> {
|
|
|
|
if !(-1.0..=0.0).contains(&livezone_lowerbound) {
|
|
|
|
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(
|
|
|
|
livezone_lowerbound,
|
|
|
|
))
|
|
|
|
} else if !(-1.0..=0.0).contains(&deadzone_lowerbound) {
|
|
|
|
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(
|
|
|
|
deadzone_lowerbound,
|
|
|
|
))
|
2023-01-16 18:13:04 +00:00
|
|
|
} else if !(0.0..=1.0).contains(&deadzone_upperbound) {
|
2022-10-17 14:38:55 +00:00
|
|
|
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(
|
|
|
|
deadzone_upperbound,
|
|
|
|
))
|
2023-01-16 18:13:04 +00:00
|
|
|
} else if !(0.0..=1.0).contains(&livezone_upperbound) {
|
2022-10-17 14:38:55 +00:00
|
|
|
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(
|
|
|
|
livezone_upperbound,
|
|
|
|
))
|
|
|
|
} else if livezone_lowerbound > deadzone_lowerbound {
|
|
|
|
Err(
|
|
|
|
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
|
|
|
|
livezone_lowerbound,
|
|
|
|
deadzone_lowerbound,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
} else if deadzone_upperbound > livezone_upperbound {
|
|
|
|
Err(
|
|
|
|
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
|
|
|
|
livezone_upperbound,
|
|
|
|
deadzone_upperbound,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
} else if !(0.0..=2.0).contains(&threshold) {
|
|
|
|
Err(AxisSettingsError::Threshold(threshold))
|
2021-01-07 20:35:40 +00:00
|
|
|
} else {
|
2022-10-17 14:38:55 +00:00
|
|
|
Ok(Self {
|
|
|
|
livezone_lowerbound,
|
|
|
|
deadzone_lowerbound,
|
|
|
|
deadzone_upperbound,
|
|
|
|
livezone_upperbound,
|
|
|
|
threshold,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the value above which inputs will be rounded up to 1.0.
|
|
|
|
pub fn livezone_upperbound(&self) -> f32 {
|
|
|
|
self.livezone_upperbound
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to set the value above which inputs will be rounded up to 1.0.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// If the value passed is less than the dead zone upper bound,
|
|
|
|
/// returns `AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound`.
|
2023-01-06 00:43:30 +00:00
|
|
|
/// If the value passed is not in range [0.0..=1.0], returns `AxisSettingsError::LiveZoneUpperBoundOutOfRange`.
|
2022-10-17 14:38:55 +00:00
|
|
|
pub fn try_set_livezone_upperbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
|
|
|
|
if !(0.0..=1.0).contains(&value) {
|
|
|
|
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(value))
|
|
|
|
} else if value < self.deadzone_upperbound {
|
|
|
|
Err(
|
|
|
|
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
|
|
|
|
livezone_upperbound: value,
|
|
|
|
deadzone_upperbound: self.deadzone_upperbound,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
self.livezone_upperbound = value;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to set the value above which inputs will be rounded up to 1.0.
|
2023-05-04 23:23:45 +00:00
|
|
|
/// If the value passed is negative or less than `deadzone_upperbound`,
|
2022-10-17 14:38:55 +00:00
|
|
|
/// the value will not be changed.
|
2023-05-04 23:23:45 +00:00
|
|
|
///
|
2022-10-17 14:38:55 +00:00
|
|
|
/// Returns the new value of `livezone_upperbound`.
|
|
|
|
pub fn set_livezone_upperbound(&mut self, value: f32) -> f32 {
|
|
|
|
self.try_set_livezone_upperbound(value).ok();
|
|
|
|
self.livezone_upperbound
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the value below which positive inputs will be rounded down to 0.0.
|
|
|
|
pub fn deadzone_upperbound(&self) -> f32 {
|
|
|
|
self.deadzone_upperbound
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to set the value below which positive inputs will be rounded down to 0.0.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// If the value passed is greater than the live zone upper bound,
|
|
|
|
/// returns `AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound`.
|
2023-01-06 00:43:30 +00:00
|
|
|
/// If the value passed is not in range [0.0..=1.0], returns `AxisSettingsError::DeadZoneUpperBoundOutOfRange`.
|
2022-10-17 14:38:55 +00:00
|
|
|
pub fn try_set_deadzone_upperbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
|
|
|
|
if !(0.0..=1.0).contains(&value) {
|
|
|
|
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(value))
|
|
|
|
} else if self.livezone_upperbound < value {
|
|
|
|
Err(
|
|
|
|
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
|
|
|
|
livezone_upperbound: self.livezone_upperbound,
|
|
|
|
deadzone_upperbound: value,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
self.deadzone_upperbound = value;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to set the value below which positive inputs will be rounded down to 0.0.
|
|
|
|
/// If the value passed is negative or greater than `livezone_upperbound`,
|
|
|
|
/// the value will not be changed.
|
|
|
|
///
|
|
|
|
/// Returns the new value of `deadzone_upperbound`.
|
|
|
|
pub fn set_deadzone_upperbound(&mut self, value: f32) -> f32 {
|
|
|
|
self.try_set_deadzone_upperbound(value).ok();
|
|
|
|
self.deadzone_upperbound
|
|
|
|
}
|
|
|
|
|
2023-05-04 23:23:45 +00:00
|
|
|
/// Get the value below which negative inputs will be rounded down to -1.0.
|
2022-10-17 14:38:55 +00:00
|
|
|
pub fn livezone_lowerbound(&self) -> f32 {
|
|
|
|
self.livezone_lowerbound
|
|
|
|
}
|
|
|
|
|
2023-05-04 23:23:45 +00:00
|
|
|
/// Try to set the value below which negative inputs will be rounded down to -1.0.
|
2022-10-17 14:38:55 +00:00
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// If the value passed is less than the dead zone lower bound,
|
|
|
|
/// returns `AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound`.
|
2023-01-06 00:43:30 +00:00
|
|
|
/// If the value passed is not in range [-1.0..=0.0], returns `AxisSettingsError::LiveZoneLowerBoundOutOfRange`.
|
2022-10-17 14:38:55 +00:00
|
|
|
pub fn try_set_livezone_lowerbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
|
|
|
|
if !(-1.0..=0.0).contains(&value) {
|
|
|
|
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(value))
|
|
|
|
} else if value > self.deadzone_lowerbound {
|
|
|
|
Err(
|
|
|
|
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
|
|
|
|
livezone_lowerbound: value,
|
|
|
|
deadzone_lowerbound: self.deadzone_lowerbound,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
self.livezone_lowerbound = value;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-04 23:23:45 +00:00
|
|
|
/// Try to set the value below which negative inputs will be rounded down to -1.0.
|
|
|
|
/// If the value passed is positive or greater than `deadzone_lowerbound`,
|
2022-10-17 14:38:55 +00:00
|
|
|
/// the value will not be changed.
|
|
|
|
///
|
|
|
|
/// Returns the new value of `livezone_lowerbound`.
|
|
|
|
pub fn set_livezone_lowerbound(&mut self, value: f32) -> f32 {
|
|
|
|
self.try_set_livezone_lowerbound(value).ok();
|
|
|
|
self.livezone_lowerbound
|
|
|
|
}
|
|
|
|
|
2023-05-04 23:23:45 +00:00
|
|
|
/// Get the value above which inputs will be rounded up to 0.0.
|
2022-10-17 14:38:55 +00:00
|
|
|
pub fn deadzone_lowerbound(&self) -> f32 {
|
|
|
|
self.deadzone_lowerbound
|
|
|
|
}
|
|
|
|
|
2023-05-04 23:23:45 +00:00
|
|
|
/// Try to set the value above which inputs will be rounded up to 0.0.
|
2022-10-17 14:38:55 +00:00
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// If the value passed is less than the live zone lower bound,
|
|
|
|
/// returns `AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound`.
|
2023-01-06 00:43:30 +00:00
|
|
|
/// If the value passed is not in range [-1.0..=0.0], returns `AxisSettingsError::DeadZoneLowerBoundOutOfRange`.
|
2022-10-17 14:38:55 +00:00
|
|
|
pub fn try_set_deadzone_lowerbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
|
|
|
|
if !(-1.0..=0.0).contains(&value) {
|
|
|
|
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(value))
|
|
|
|
} else if self.livezone_lowerbound > value {
|
|
|
|
Err(
|
|
|
|
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
|
|
|
|
livezone_lowerbound: self.livezone_lowerbound,
|
|
|
|
deadzone_lowerbound: value,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
self.deadzone_lowerbound = value;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-04 23:23:45 +00:00
|
|
|
/// Try to set the value above which inputs will be rounded up to 0.0.
|
|
|
|
/// If the value passed is less than -1.0 or less than `livezone_lowerbound`,
|
2022-10-17 14:38:55 +00:00
|
|
|
/// the value will not be changed.
|
|
|
|
///
|
|
|
|
/// Returns the new value of `deadzone_lowerbound`.
|
|
|
|
pub fn set_deadzone_lowerbound(&mut self, value: f32) -> f32 {
|
|
|
|
self.try_set_deadzone_lowerbound(value).ok();
|
|
|
|
self.deadzone_lowerbound
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the minimum value by which input must change before the change is registered.
|
|
|
|
pub fn threshold(&self) -> f32 {
|
|
|
|
self.threshold
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to set the minimum value by which input must change before the change is registered.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// If the value passed is not within [0.0..=2.0], returns `GamepadSettingsError::AxisThreshold`.
|
|
|
|
pub fn try_set_threshold(&mut self, value: f32) -> Result<(), AxisSettingsError> {
|
|
|
|
if !(0.0..=2.0).contains(&value) {
|
|
|
|
Err(AxisSettingsError::Threshold(value))
|
|
|
|
} else {
|
|
|
|
self.threshold = value;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to set the minimum value by which input must change before the changes will be applied.
|
|
|
|
/// If the value passed is not within [0.0..=2.0], the value will not be changed.
|
|
|
|
///
|
|
|
|
/// Returns the new value of threshold.
|
|
|
|
pub fn set_threshold(&mut self, value: f32) -> f32 {
|
|
|
|
self.try_set_threshold(value).ok();
|
|
|
|
self.threshold
|
|
|
|
}
|
|
|
|
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
/// Clamps the `raw_value` according to the `AxisSettings`.
|
|
|
|
pub fn clamp(&self, new_value: f32) -> f32 {
|
|
|
|
if self.deadzone_lowerbound <= new_value && new_value <= self.deadzone_upperbound {
|
|
|
|
0.0
|
|
|
|
} else if new_value >= self.livezone_upperbound {
|
|
|
|
1.0
|
|
|
|
} else if new_value <= self.livezone_lowerbound {
|
|
|
|
-1.0
|
|
|
|
} else {
|
|
|
|
new_value
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
}
|
2021-01-07 20:35:40 +00:00
|
|
|
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
/// Determines whether the change from `old_value` to `new_value` should
|
2023-04-23 17:28:36 +00:00
|
|
|
/// be registered as a change, according to the [`AxisSettings`].
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
fn should_register_change(&self, new_value: f32, old_value: Option<f32>) -> bool {
|
|
|
|
if old_value.is_none() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
f32::abs(new_value - old_value.unwrap()) > self.threshold
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Filters the `new_value` based on the `old_value`, according to the [`AxisSettings`].
|
|
|
|
///
|
|
|
|
/// Returns the clamped `new_value` if the change exceeds the settings threshold,
|
|
|
|
/// and `None` otherwise.
|
|
|
|
pub fn filter(&self, new_value: f32, old_value: Option<f32>) -> Option<f32> {
|
|
|
|
let new_value = self.clamp(new_value);
|
|
|
|
|
|
|
|
if self.should_register_change(new_value, old_value) {
|
|
|
|
return Some(new_value);
|
|
|
|
}
|
|
|
|
None
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 02:28:07 +00:00
|
|
|
/// Settings for a [`GamepadButton`].
|
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// It is used inside the [`GamepadSettings`] to define the sensitivity range and
|
2022-08-05 02:28:07 +00:00
|
|
|
/// threshold for a button axis.
|
|
|
|
///
|
|
|
|
/// ## Logic
|
|
|
|
///
|
|
|
|
/// - Values that are higher than or equal to `high` will be rounded to 1.0.
|
|
|
|
/// - Values that are lower than or equal to `low` will be rounded to 0.0.
|
|
|
|
/// - Otherwise, values will not be rounded.
|
|
|
|
///
|
|
|
|
/// The valid range is from 0.0 to 1.0, inclusive.
|
2024-07-08 01:09:07 +00:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Default))]
|
2020-10-21 22:56:07 +00:00
|
|
|
pub struct ButtonAxisSettings {
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The high value at which to apply rounding.
|
2020-10-21 17:27:00 +00:00
|
|
|
pub high: f32,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The low value at which to apply rounding.
|
2020-10-21 17:27:00 +00:00
|
|
|
pub low: f32,
|
2022-08-05 02:28:07 +00:00
|
|
|
/// The threshold to apply rounding.
|
2020-10-21 17:27:00 +00:00
|
|
|
pub threshold: f32,
|
|
|
|
}
|
|
|
|
|
2020-10-21 22:56:07 +00:00
|
|
|
impl Default for ButtonAxisSettings {
|
2020-10-21 17:27:00 +00:00
|
|
|
fn default() -> Self {
|
2020-10-21 22:56:07 +00:00
|
|
|
ButtonAxisSettings {
|
2020-10-21 17:27:00 +00:00
|
|
|
high: 0.95,
|
|
|
|
low: 0.05,
|
|
|
|
threshold: 0.01,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-21 22:56:07 +00:00
|
|
|
impl ButtonAxisSettings {
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
/// Clamps the `raw_value` according to the specified settings.
|
2022-08-05 02:28:07 +00:00
|
|
|
///
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
/// If the `raw_value` is:
|
2022-08-05 02:28:07 +00:00
|
|
|
/// - lower than or equal to `low` it will be rounded to 0.0.
|
|
|
|
/// - higher than or equal to `high` it will be rounded to 1.0.
|
|
|
|
/// - Otherwise it will not be rounded.
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
fn clamp(&self, raw_value: f32) -> f32 {
|
|
|
|
if raw_value <= self.low {
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
if raw_value >= self.high {
|
|
|
|
return 1.0;
|
|
|
|
}
|
2021-12-08 01:12:49 +00:00
|
|
|
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
raw_value
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Determines whether the change from an `old_value` to a `new_value` should
|
|
|
|
/// be registered as a change event, according to the specified settings.
|
|
|
|
fn should_register_change(&self, new_value: f32, old_value: Option<f32>) -> bool {
|
|
|
|
if old_value.is_none() {
|
|
|
|
return true;
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
2021-12-08 01:12:49 +00:00
|
|
|
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
f32::abs(new_value - old_value.unwrap()) > self.threshold
|
|
|
|
}
|
|
|
|
|
2023-04-23 17:28:36 +00:00
|
|
|
/// Filters the `new_value` based on the `old_value`, according to the [`ButtonAxisSettings`].
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
///
|
|
|
|
/// Returns the clamped `new_value`, according to the [`ButtonAxisSettings`], if the change
|
|
|
|
/// exceeds the settings threshold, and `None` otherwise.
|
|
|
|
pub fn filter(&self, new_value: f32, old_value: Option<f32>) -> Option<f32> {
|
|
|
|
let new_value = self.clamp(new_value);
|
|
|
|
|
|
|
|
if self.should_register_change(new_value, old_value) {
|
|
|
|
return Some(new_value);
|
|
|
|
}
|
|
|
|
None
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Handles [`GamepadConnectionEvent`]s events.
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
///
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// On connection, adds the components representing a [`Gamepad`] to the entity.
|
|
|
|
/// On disconnection, removes the [`Gamepad`] and other related components.
|
|
|
|
/// Entities are left alive and might leave components like [`GamepadSettings`] to preserve state in the case of a reconnection
|
2021-12-08 20:28:08 +00:00
|
|
|
///
|
2022-08-05 02:28:07 +00:00
|
|
|
/// ## Note
|
|
|
|
///
|
|
|
|
/// Whenever a [`Gamepad`] connects or disconnects, an information gets printed to the console using the [`info!`] macro.
|
2021-12-08 20:28:08 +00:00
|
|
|
pub fn gamepad_connection_system(
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
mut commands: Commands,
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
mut connection_events: EventReader<GamepadConnectionEvent>,
|
2021-12-08 20:28:08 +00:00
|
|
|
) {
|
2023-08-30 14:20:03 +00:00
|
|
|
for connection_event in connection_events.read() {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
let id = connection_event.gamepad;
|
|
|
|
match &connection_event.connection {
|
|
|
|
GamepadConnection::Connected(info) => {
|
2024-10-02 12:47:26 +00:00
|
|
|
let Some(mut gamepad) = commands.get_entity(id) else {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
warn!("Gamepad {:} removed before handling connection event.", id);
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
gamepad.insert(Gamepad::new(info.clone()));
|
|
|
|
info!("Gamepad {:?} connected.", id);
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
}
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
GamepadConnection::Disconnected => {
|
2024-10-02 12:47:26 +00:00
|
|
|
let Some(mut gamepad) = commands.get_entity(id) else {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
warn!("Gamepad {:} removed before handling disconnection event. You can ignore this if you manually removed it.", id);
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
// Gamepad entities are left alive to preserve their state (e.g. [`GamepadSettings`]).
|
|
|
|
// Instead of despawning, we remove Gamepad components that don't need to preserve state
|
|
|
|
// and re-add them if they ever reconnect.
|
|
|
|
gamepad.remove::<Gamepad>();
|
|
|
|
info!("Gamepad {:} disconnected.", id);
|
2021-12-08 20:28:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The connection status of a gamepad.
|
2024-07-08 01:09:07 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
|
|
|
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
#[cfg_attr(
|
2024-07-08 01:09:07 +00:00
|
|
|
all(feature = "serialize", feature = "bevy_reflect"),
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
reflect(Serialize, Deserialize)
|
|
|
|
)]
|
|
|
|
pub enum GamepadConnection {
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The gamepad is connected.
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
Connected(GamepadInfo),
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The gamepad is disconnected.
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
Disconnected,
|
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Consumes [`RawGamepadEvent`] events, filters them using their [`GamepadSettings`] and if successful,
|
|
|
|
/// updates the [`Gamepad`] and sends [`GamepadAxisChangedEvent`], [`GamepadButtonStateChangedEvent`], [`GamepadButtonChangedEvent`] events.
|
|
|
|
pub fn gamepad_event_processing_system(
|
|
|
|
mut gamepads: Query<(&mut Gamepad, &GamepadSettings)>,
|
|
|
|
mut raw_events: EventReader<RawGamepadEvent>,
|
|
|
|
mut processed_events: EventWriter<GamepadEvent>,
|
|
|
|
mut processed_axis_events: EventWriter<GamepadAxisChangedEvent>,
|
|
|
|
mut processed_digital_events: EventWriter<GamepadButtonStateChangedEvent>,
|
|
|
|
mut processed_analog_events: EventWriter<GamepadButtonChangedEvent>,
|
2020-10-21 17:27:00 +00:00
|
|
|
) {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
// Clear digital buttons state
|
|
|
|
for (mut gamepad, _) in gamepads.iter_mut() {
|
|
|
|
gamepad.bypass_change_detection().digital.clear();
|
Gamepad events refactor (#6965)
# Objective
- Remove redundant gamepad events
- Simplify consuming gamepad events.
- Refactor: Separate handling of gamepad events into multiple systems.
## Solution
- Removed `GamepadEventRaw`, and `GamepadEventType`.
- Added bespoke `GamepadConnectionEvent`, `GamepadAxisChangedEvent`, and `GamepadButtonChangedEvent`.
- Refactored `gamepad_event_system`.
- Added `gamepad_button_event_system`, `gamepad_axis_event_system`, and `gamepad_connection_system`, which update the `Input` and `Axis` resources using their corresponding event type.
Gamepad events are now handled in their own systems and have their own types.
This allows for querying for gamepad events without having to match on `GamepadEventType` and makes creating handlers for specific gamepad event types, like a `GamepadConnectionEvent` or `GamepadButtonChangedEvent` possible.
We remove `GamepadEventRaw` by filtering the gamepad events, using `GamepadSettings`, _at the source_, in `bevy_gilrs`. This way we can create `GamepadEvent`s directly and avoid creating `GamepadEventRaw` which do not pass the user defined filters.
We expose ordered `GamepadEvent`s and we can respond to individual gamepad event types.
## Migration Guide
- Replace `GamepadEvent` and `GamepadEventRaw` types with their specific gamepad event type.
2023-01-09 19:24:52 +00:00
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
for event in raw_events.read() {
|
|
|
|
match event {
|
|
|
|
// Connections require inserting/removing components so they are done in a separate system
|
|
|
|
RawGamepadEvent::Connection(send_event) => {
|
|
|
|
processed_events.send(GamepadEvent::from(send_event.clone()));
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
|
|
|
|
gamepad,
|
|
|
|
axis,
|
|
|
|
value,
|
|
|
|
}) => {
|
|
|
|
let (gamepad, axis, value) = (*gamepad, *axis, *value);
|
|
|
|
let Ok((mut gamepad_axis, gamepad_settings)) = gamepads.get_mut(gamepad) else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
let Some(filtered_value) = gamepad_settings
|
|
|
|
.get_axis_settings(axis)
|
|
|
|
.filter(value, gamepad_axis.get(axis))
|
|
|
|
else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
|
|
|
gamepad_axis.analog.set(axis.into(), filtered_value);
|
|
|
|
let send_event = GamepadAxisChangedEvent::new(gamepad, axis, filtered_value);
|
|
|
|
processed_axis_events.send(send_event);
|
|
|
|
processed_events.send(GamepadEvent::from(send_event));
|
Update `Event` send methods to return `EventId` (#10551)
# Objective
- Fixes #10532
## Solution
I've updated the various `Event` send methods to return the sent
`EventId`(s). Since these methods previously returned nothing, and this
information is cheap to copy, there should be minimal negative
consequences to providing this additional information. In the case of
`send_batch`, an iterator is returned built from `Range` and `Map`,
which only consumes 16 bytes on the stack with no heap allocations for
all batch sizes. As such, the cost of this information is negligible.
These changes are reflected for `EventWriter` and `World`. For `World`,
the return types are optional to account for the possible lack of an
`Events` resource. Again, these methods previously returned no
information, so its inclusion should only be a benefit.
## Usage
Now when sending events, the IDs of those events is available for
immediate use:
```rust
// Example of a request-response system where the requester can track handled requests.
/// A system which can make and track requests
fn requester(
mut requests: EventWriter<Request>,
mut handled: EventReader<Handled>,
mut pending: Local<HashSet<EventId<Request>>>,
) {
// Check status of previous requests
for Handled(id) in handled.read() {
pending.remove(&id);
}
if !pending.is_empty() {
error!("Not all my requests were handled on the previous frame!");
pending.clear();
}
// Send a new request and remember its ID for later
let request_id = requests.send(Request::MyRequest { /* ... */ });
pending.insert(request_id);
}
/// A system which handles requests
fn responder(
mut requests: EventReader<Request>,
mut handled: EventWriter<Handled>,
) {
for (request, id) in requests.read_with_id() {
if handle(request).is_ok() {
handled.send(Handled(id));
}
}
}
```
In the above example, a `requester` system can send request events, and
keep track of which ones are currently pending by `EventId`. Then, a
`responder` system can act on that event, providing the ID as a
reference that the `requester` can use. Before this PR, it was not
trivial for a system sending events to keep track of events by ID. This
is unfortunate, since for a system reading events, it is trivial to
access the ID of a event.
---
## Changelog
- Updated `Events`:
- Added `send_batch`
- Modified `send` to return the sent `EventId`
- Modified `send_default` to return the sent `EventId`
- Updated `EventWriter`
- Modified `send_batch` to return all sent `EventId`s
- Modified `send` to return the sent `EventId`
- Modified `send_default` to return the sent `EventId`
- Updated `World`
- Modified `send_event` to return the sent `EventId` if sent, otherwise
`None`.
- Modified `send_event_default` to return the sent `EventId` if sent,
otherwise `None`.
- Modified `send_event_batch` to return all sent `EventId`s if sent,
otherwise `None`.
- Added unit test `test_send_events_ids` to ensure returned `EventId`s
match the sent `Event`s
- Updated uses of modified methods.
## Migration Guide
### `send` / `send_default` / `send_batch`
For the following methods:
- `Events::send`
- `Events::send_default`
- `Events::send_batch`
- `EventWriter::send`
- `EventWriter::send_default`
- `EventWriter::send_batch`
- `World::send_event`
- `World::send_event_default`
- `World::send_event_batch`
Ensure calls to these methods either handle the returned value, or
suppress the result with `;`.
```rust
// Now fails to compile due to mismatched return type
fn send_my_event(mut events: EventWriter<MyEvent>) {
events.send_default()
}
// Fix
fn send_my_event(mut events: EventWriter<MyEvent>) {
events.send_default();
}
```
This will most likely be noticed within `match` statements:
```rust
// Before
match is_pressed {
true => events.send(PlayerAction::Fire),
// ^--^ No longer returns ()
false => {}
}
// After
match is_pressed {
true => {
events.send(PlayerAction::Fire);
},
false => {}
}
```
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
2023-11-16 17:20:43 +00:00
|
|
|
}
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
|
|
|
|
gamepad,
|
|
|
|
button,
|
|
|
|
value,
|
|
|
|
}) => {
|
|
|
|
let (gamepad, button, value) = (*gamepad, *button, *value);
|
|
|
|
let Ok((mut gamepad_buttons, settings)) = gamepads.get_mut(gamepad) else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
let Some(filtered_value) = settings
|
|
|
|
.get_button_axis_settings(button)
|
|
|
|
.filter(value, gamepad_buttons.get(button))
|
|
|
|
else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
let button_settings = settings.get_button_settings(button);
|
|
|
|
gamepad_buttons.analog.set(button.into(), filtered_value);
|
|
|
|
|
|
|
|
if button_settings.is_released(filtered_value) {
|
|
|
|
// Check if button was previously pressed
|
|
|
|
if gamepad_buttons.pressed(button) {
|
|
|
|
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
|
|
|
|
gamepad,
|
|
|
|
button,
|
|
|
|
ButtonState::Released,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
// We don't have to check if the button was previously pressed here
|
|
|
|
// because that check is performed within Input<T>::release()
|
|
|
|
gamepad_buttons.digital.release(button);
|
|
|
|
} else if button_settings.is_pressed(filtered_value) {
|
|
|
|
// Check if button was previously not pressed
|
|
|
|
if !gamepad_buttons.pressed(button) {
|
|
|
|
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
|
|
|
|
gamepad,
|
|
|
|
button,
|
|
|
|
ButtonState::Pressed,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
gamepad_buttons.digital.press(button);
|
|
|
|
};
|
|
|
|
|
|
|
|
let button_state = if gamepad_buttons.digital.pressed(button) {
|
|
|
|
ButtonState::Pressed
|
|
|
|
} else {
|
|
|
|
ButtonState::Released
|
|
|
|
};
|
|
|
|
let send_event =
|
|
|
|
GamepadButtonChangedEvent::new(gamepad, button, button_state, filtered_value);
|
|
|
|
processed_analog_events.send(send_event);
|
|
|
|
processed_events.send(GamepadEvent::from(send_event));
|
Update `Event` send methods to return `EventId` (#10551)
# Objective
- Fixes #10532
## Solution
I've updated the various `Event` send methods to return the sent
`EventId`(s). Since these methods previously returned nothing, and this
information is cheap to copy, there should be minimal negative
consequences to providing this additional information. In the case of
`send_batch`, an iterator is returned built from `Range` and `Map`,
which only consumes 16 bytes on the stack with no heap allocations for
all batch sizes. As such, the cost of this information is negligible.
These changes are reflected for `EventWriter` and `World`. For `World`,
the return types are optional to account for the possible lack of an
`Events` resource. Again, these methods previously returned no
information, so its inclusion should only be a benefit.
## Usage
Now when sending events, the IDs of those events is available for
immediate use:
```rust
// Example of a request-response system where the requester can track handled requests.
/// A system which can make and track requests
fn requester(
mut requests: EventWriter<Request>,
mut handled: EventReader<Handled>,
mut pending: Local<HashSet<EventId<Request>>>,
) {
// Check status of previous requests
for Handled(id) in handled.read() {
pending.remove(&id);
}
if !pending.is_empty() {
error!("Not all my requests were handled on the previous frame!");
pending.clear();
}
// Send a new request and remember its ID for later
let request_id = requests.send(Request::MyRequest { /* ... */ });
pending.insert(request_id);
}
/// A system which handles requests
fn responder(
mut requests: EventReader<Request>,
mut handled: EventWriter<Handled>,
) {
for (request, id) in requests.read_with_id() {
if handle(request).is_ok() {
handled.send(Handled(id));
}
}
}
```
In the above example, a `requester` system can send request events, and
keep track of which ones are currently pending by `EventId`. Then, a
`responder` system can act on that event, providing the ID as a
reference that the `requester` can use. Before this PR, it was not
trivial for a system sending events to keep track of events by ID. This
is unfortunate, since for a system reading events, it is trivial to
access the ID of a event.
---
## Changelog
- Updated `Events`:
- Added `send_batch`
- Modified `send` to return the sent `EventId`
- Modified `send_default` to return the sent `EventId`
- Updated `EventWriter`
- Modified `send_batch` to return all sent `EventId`s
- Modified `send` to return the sent `EventId`
- Modified `send_default` to return the sent `EventId`
- Updated `World`
- Modified `send_event` to return the sent `EventId` if sent, otherwise
`None`.
- Modified `send_event_default` to return the sent `EventId` if sent,
otherwise `None`.
- Modified `send_event_batch` to return all sent `EventId`s if sent,
otherwise `None`.
- Added unit test `test_send_events_ids` to ensure returned `EventId`s
match the sent `Event`s
- Updated uses of modified methods.
## Migration Guide
### `send` / `send_default` / `send_batch`
For the following methods:
- `Events::send`
- `Events::send_default`
- `Events::send_batch`
- `EventWriter::send`
- `EventWriter::send_default`
- `EventWriter::send_batch`
- `World::send_event`
- `World::send_event_default`
- `World::send_event_batch`
Ensure calls to these methods either handle the returned value, or
suppress the result with `;`.
```rust
// Now fails to compile due to mismatched return type
fn send_my_event(mut events: EventWriter<MyEvent>) {
events.send_default()
}
// Fix
fn send_my_event(mut events: EventWriter<MyEvent>) {
events.send_default();
}
```
This will most likely be noticed within `match` statements:
```rust
// Before
match is_pressed {
true => events.send(PlayerAction::Fire),
// ^--^ No longer returns ()
false => {}
}
// After
match is_pressed {
true => {
events.send(PlayerAction::Fire);
},
false => {}
}
```
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
2023-11-16 17:20:43 +00:00
|
|
|
}
|
2020-10-21 17:27:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-24 15:28:53 +00:00
|
|
|
/// The intensity at which a gamepad's force-feedback motors may rumble.
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
|
|
pub struct GamepadRumbleIntensity {
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The rumble intensity of the strong gamepad motor.
|
2023-04-24 15:28:53 +00:00
|
|
|
///
|
2023-08-19 18:19:46 +00:00
|
|
|
/// Ranges from `0.0` to `1.0`.
|
2023-04-24 15:28:53 +00:00
|
|
|
///
|
|
|
|
/// By convention, this is usually a low-frequency motor on the left-hand
|
|
|
|
/// side of the gamepad, though it may vary across platforms and hardware.
|
|
|
|
pub strong_motor: f32,
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The rumble intensity of the weak gamepad motor.
|
2023-04-24 15:28:53 +00:00
|
|
|
///
|
2023-08-19 18:19:46 +00:00
|
|
|
/// Ranges from `0.0` to `1.0`.
|
2023-04-24 15:28:53 +00:00
|
|
|
///
|
|
|
|
/// By convention, this is usually a high-frequency motor on the right-hand
|
|
|
|
/// side of the gamepad, though it may vary across platforms and hardware.
|
|
|
|
pub weak_motor: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GamepadRumbleIntensity {
|
2023-08-19 18:19:46 +00:00
|
|
|
/// Rumble both gamepad motors at maximum intensity.
|
2023-04-24 15:28:53 +00:00
|
|
|
pub const MAX: Self = GamepadRumbleIntensity {
|
|
|
|
strong_motor: 1.0,
|
|
|
|
weak_motor: 1.0,
|
|
|
|
};
|
|
|
|
|
2023-08-19 18:19:46 +00:00
|
|
|
/// Rumble the weak motor at maximum intensity.
|
2023-04-24 15:28:53 +00:00
|
|
|
pub const WEAK_MAX: Self = GamepadRumbleIntensity {
|
|
|
|
strong_motor: 0.0,
|
|
|
|
weak_motor: 1.0,
|
|
|
|
};
|
|
|
|
|
2023-08-19 18:19:46 +00:00
|
|
|
/// Rumble the strong motor at maximum intensity.
|
2023-04-24 15:28:53 +00:00
|
|
|
pub const STRONG_MAX: Self = GamepadRumbleIntensity {
|
|
|
|
strong_motor: 1.0,
|
|
|
|
weak_motor: 0.0,
|
|
|
|
};
|
|
|
|
|
2023-08-19 18:19:46 +00:00
|
|
|
/// Creates a new rumble intensity with weak motor intensity set to the given value.
|
2023-04-24 15:28:53 +00:00
|
|
|
///
|
2023-08-19 18:19:46 +00:00
|
|
|
/// Clamped within the `0.0` to `1.0` range.
|
2023-04-24 15:28:53 +00:00
|
|
|
pub const fn weak_motor(intensity: f32) -> Self {
|
|
|
|
Self {
|
|
|
|
weak_motor: intensity,
|
|
|
|
strong_motor: 0.0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-19 18:19:46 +00:00
|
|
|
/// Creates a new rumble intensity with strong motor intensity set to the given value.
|
2023-04-24 15:28:53 +00:00
|
|
|
///
|
2023-08-19 18:19:46 +00:00
|
|
|
/// Clamped within the `0.0` to `1.0` range.
|
2023-04-24 15:28:53 +00:00
|
|
|
pub const fn strong_motor(intensity: f32) -> Self {
|
|
|
|
Self {
|
|
|
|
strong_motor: intensity,
|
|
|
|
weak_motor: 0.0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// An event that controls force-feedback rumbling of a [`Gamepad`] [`entity`](Entity).
|
2023-04-24 15:28:53 +00:00
|
|
|
///
|
|
|
|
/// # Notes
|
|
|
|
///
|
|
|
|
/// Does nothing if the gamepad or platform does not support rumble.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// # use bevy_input::gamepad::{Gamepad, GamepadRumbleRequest, GamepadRumbleIntensity};
|
|
|
|
/// # use bevy_ecs::prelude::{EventWriter, Res, Query, Entity, With};
|
2023-04-24 15:28:53 +00:00
|
|
|
/// # use bevy_utils::Duration;
|
|
|
|
/// fn rumble_gamepad_system(
|
|
|
|
/// mut rumble_requests: EventWriter<GamepadRumbleRequest>,
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// gamepads: Query<Entity, With<Gamepad>>,
|
2023-04-24 15:28:53 +00:00
|
|
|
/// ) {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// for entity in gamepads.iter() {
|
2023-04-24 15:28:53 +00:00
|
|
|
/// rumble_requests.send(GamepadRumbleRequest::Add {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// gamepad: entity,
|
2023-04-24 15:28:53 +00:00
|
|
|
/// intensity: GamepadRumbleIntensity::MAX,
|
|
|
|
/// duration: Duration::from_secs_f32(0.5),
|
|
|
|
/// });
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
#[doc(alias = "haptic feedback")]
|
|
|
|
#[doc(alias = "force feedback")]
|
|
|
|
#[doc(alias = "vibration")]
|
|
|
|
#[doc(alias = "vibrate")]
|
2023-06-06 14:44:32 +00:00
|
|
|
#[derive(Event, Clone)]
|
2023-04-24 15:28:53 +00:00
|
|
|
pub enum GamepadRumbleRequest {
|
|
|
|
/// Add a rumble to the given gamepad.
|
|
|
|
///
|
|
|
|
/// Simultaneous rumble effects add up to the sum of their strengths.
|
|
|
|
///
|
|
|
|
/// Consequently, if two rumbles at half intensity are added at the same
|
|
|
|
/// time, their intensities will be added up, and the controller will rumble
|
|
|
|
/// at full intensity until one of the rumbles finishes, then the rumble
|
|
|
|
/// will continue at the intensity of the remaining event.
|
|
|
|
///
|
|
|
|
/// To replace an existing rumble, send a [`GamepadRumbleRequest::Stop`] event first.
|
|
|
|
Add {
|
2023-08-19 18:19:46 +00:00
|
|
|
/// How long the gamepad should rumble.
|
2023-04-24 15:28:53 +00:00
|
|
|
duration: Duration,
|
2023-08-19 18:19:46 +00:00
|
|
|
/// How intense the rumble should be.
|
2023-04-24 15:28:53 +00:00
|
|
|
intensity: GamepadRumbleIntensity,
|
2023-08-19 18:19:46 +00:00
|
|
|
/// The gamepad to rumble.
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
gamepad: Entity,
|
2023-08-19 18:19:46 +00:00
|
|
|
},
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Stop all running rumbles on the given [`Entity`].
|
2023-08-19 18:19:46 +00:00
|
|
|
Stop {
|
|
|
|
/// The gamepad to stop rumble.
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
gamepad: Entity,
|
2023-04-24 15:28:53 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GamepadRumbleRequest {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
/// Get the [`Entity`] associated with this request.
|
|
|
|
pub fn gamepad(&self) -> Entity {
|
2023-04-24 15:28:53 +00:00
|
|
|
match self {
|
|
|
|
Self::Add { gamepad, .. } | Self::Stop { gamepad } => *gamepad,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-08 01:12:49 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
use super::{
|
|
|
|
gamepad_connection_system, gamepad_event_processing_system, AxisSettings,
|
|
|
|
AxisSettingsError, ButtonAxisSettings, ButtonSettings, ButtonSettingsError, Gamepad,
|
|
|
|
GamepadAxis, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
|
|
|
|
GamepadButtonStateChangedEvent,
|
|
|
|
GamepadConnection::{Connected, Disconnected},
|
|
|
|
GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadSettings,
|
|
|
|
RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
|
|
|
|
};
|
|
|
|
use crate::ButtonState;
|
|
|
|
use bevy_app::{App, PreUpdate};
|
|
|
|
use bevy_ecs::entity::Entity;
|
|
|
|
use bevy_ecs::event::Events;
|
|
|
|
use bevy_ecs::schedule::IntoSystemConfigs;
|
2021-12-08 01:12:49 +00:00
|
|
|
|
|
|
|
fn test_button_axis_settings_filter(
|
|
|
|
settings: ButtonAxisSettings,
|
|
|
|
new_value: f32,
|
|
|
|
old_value: Option<f32>,
|
|
|
|
expected: Option<f32>,
|
|
|
|
) {
|
|
|
|
let actual = settings.filter(new_value, old_value);
|
|
|
|
assert_eq!(
|
|
|
|
expected, actual,
|
2023-01-11 09:51:22 +00:00
|
|
|
"Testing filtering for {settings:?} with new_value = {new_value:?}, old_value = {old_value:?}",
|
2021-12-08 01:12:49 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_button_axis_settings_default_filter() {
|
|
|
|
let cases = [
|
|
|
|
(1.0, None, Some(1.0)),
|
|
|
|
(0.99, None, Some(1.0)),
|
|
|
|
(0.96, None, Some(1.0)),
|
|
|
|
(0.95, None, Some(1.0)),
|
|
|
|
(0.9499, None, Some(0.9499)),
|
|
|
|
(0.84, None, Some(0.84)),
|
|
|
|
(0.43, None, Some(0.43)),
|
|
|
|
(0.05001, None, Some(0.05001)),
|
|
|
|
(0.05, None, Some(0.0)),
|
|
|
|
(0.04, None, Some(0.0)),
|
|
|
|
(0.01, None, Some(0.0)),
|
|
|
|
(0.0, None, Some(0.0)),
|
|
|
|
];
|
|
|
|
|
|
|
|
for (new_value, old_value, expected) in cases {
|
|
|
|
let settings = ButtonAxisSettings::default();
|
|
|
|
test_button_axis_settings_filter(settings, new_value, old_value, expected);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_button_axis_settings_default_filter_with_old_value() {
|
|
|
|
let cases = [
|
|
|
|
(0.43, Some(0.44001), Some(0.43)),
|
|
|
|
(0.43, Some(0.44), None),
|
|
|
|
(0.43, Some(0.43), None),
|
|
|
|
(0.43, Some(0.41999), Some(0.43)),
|
|
|
|
(0.43, Some(0.17), Some(0.43)),
|
|
|
|
(0.43, Some(0.84), Some(0.43)),
|
|
|
|
(0.05, Some(0.055), Some(0.0)),
|
|
|
|
(0.95, Some(0.945), Some(1.0)),
|
|
|
|
];
|
|
|
|
|
|
|
|
for (new_value, old_value, expected) in cases {
|
|
|
|
let settings = ButtonAxisSettings::default();
|
|
|
|
test_button_axis_settings_filter(settings, new_value, old_value, expected);
|
|
|
|
}
|
|
|
|
}
|
2021-12-18 20:00:18 +00:00
|
|
|
|
|
|
|
fn test_axis_settings_filter(
|
|
|
|
settings: AxisSettings,
|
|
|
|
new_value: f32,
|
|
|
|
old_value: Option<f32>,
|
|
|
|
expected: Option<f32>,
|
|
|
|
) {
|
|
|
|
let actual = settings.filter(new_value, old_value);
|
|
|
|
assert_eq!(
|
|
|
|
expected, actual,
|
2023-01-11 09:51:22 +00:00
|
|
|
"Testing filtering for {settings:?} with new_value = {new_value:?}, old_value = {old_value:?}",
|
2021-12-18 20:00:18 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_axis_settings_default_filter() {
|
|
|
|
let cases = [
|
|
|
|
(1.0, Some(1.0)),
|
|
|
|
(0.99, Some(1.0)),
|
|
|
|
(0.96, Some(1.0)),
|
|
|
|
(0.95, Some(1.0)),
|
|
|
|
(0.9499, Some(0.9499)),
|
|
|
|
(0.84, Some(0.84)),
|
|
|
|
(0.43, Some(0.43)),
|
|
|
|
(0.05001, Some(0.05001)),
|
|
|
|
(0.05, Some(0.0)),
|
|
|
|
(0.04, Some(0.0)),
|
|
|
|
(0.01, Some(0.0)),
|
|
|
|
(0.0, Some(0.0)),
|
|
|
|
(-1.0, Some(-1.0)),
|
|
|
|
(-0.99, Some(-1.0)),
|
|
|
|
(-0.96, Some(-1.0)),
|
|
|
|
(-0.95, Some(-1.0)),
|
|
|
|
(-0.9499, Some(-0.9499)),
|
|
|
|
(-0.84, Some(-0.84)),
|
|
|
|
(-0.43, Some(-0.43)),
|
|
|
|
(-0.05001, Some(-0.05001)),
|
|
|
|
(-0.05, Some(0.0)),
|
|
|
|
(-0.04, Some(0.0)),
|
|
|
|
(-0.01, Some(0.0)),
|
|
|
|
];
|
|
|
|
|
|
|
|
for (new_value, expected) in cases {
|
2023-10-12 17:58:32 +00:00
|
|
|
let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.01).unwrap();
|
2021-12-18 20:00:18 +00:00
|
|
|
test_axis_settings_filter(settings, new_value, None, expected);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_axis_settings_default_filter_with_old_values() {
|
|
|
|
let cases = [
|
|
|
|
(0.43, Some(0.44001), Some(0.43)),
|
|
|
|
(0.43, Some(0.44), None),
|
|
|
|
(0.43, Some(0.43), None),
|
|
|
|
(0.43, Some(0.41999), Some(0.43)),
|
|
|
|
(0.43, Some(0.17), Some(0.43)),
|
|
|
|
(0.43, Some(0.84), Some(0.43)),
|
|
|
|
(0.05, Some(0.055), Some(0.0)),
|
|
|
|
(0.95, Some(0.945), Some(1.0)),
|
|
|
|
(-0.43, Some(-0.44001), Some(-0.43)),
|
|
|
|
(-0.43, Some(-0.44), None),
|
|
|
|
(-0.43, Some(-0.43), None),
|
|
|
|
(-0.43, Some(-0.41999), Some(-0.43)),
|
|
|
|
(-0.43, Some(-0.17), Some(-0.43)),
|
|
|
|
(-0.43, Some(-0.84), Some(-0.43)),
|
|
|
|
(-0.05, Some(-0.055), Some(0.0)),
|
|
|
|
(-0.95, Some(-0.945), Some(-1.0)),
|
|
|
|
];
|
|
|
|
|
|
|
|
for (new_value, old_value, expected) in cases {
|
2023-10-12 17:58:32 +00:00
|
|
|
let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.01).unwrap();
|
2021-12-18 20:00:18 +00:00
|
|
|
test_axis_settings_filter(settings, new_value, old_value, expected);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_button_settings_default_is_pressed() {
|
|
|
|
let cases = [
|
|
|
|
(1.0, true),
|
|
|
|
(0.95, true),
|
|
|
|
(0.9, true),
|
|
|
|
(0.8, true),
|
|
|
|
(0.75, true),
|
|
|
|
(0.7, false),
|
|
|
|
(0.65, false),
|
|
|
|
(0.5, false),
|
|
|
|
(0.0, false),
|
|
|
|
];
|
|
|
|
|
|
|
|
for (value, expected) in cases {
|
|
|
|
let settings = ButtonSettings::default();
|
|
|
|
let actual = settings.is_pressed(value);
|
|
|
|
|
2022-10-17 14:38:55 +00:00
|
|
|
assert_eq!(
|
|
|
|
expected, actual,
|
2023-01-11 09:51:22 +00:00
|
|
|
"testing ButtonSettings::is_pressed() for value: {value}",
|
2022-10-17 14:38:55 +00:00
|
|
|
);
|
2021-12-18 20:00:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_button_settings_default_is_released() {
|
|
|
|
let cases = [
|
|
|
|
(1.0, false),
|
|
|
|
(0.95, false),
|
|
|
|
(0.9, false),
|
|
|
|
(0.8, false),
|
|
|
|
(0.75, false),
|
|
|
|
(0.7, false),
|
|
|
|
(0.65, true),
|
|
|
|
(0.5, true),
|
|
|
|
(0.0, true),
|
|
|
|
];
|
|
|
|
|
|
|
|
for (value, expected) in cases {
|
|
|
|
let settings = ButtonSettings::default();
|
|
|
|
let actual = settings.is_released(value);
|
|
|
|
|
2022-10-17 14:38:55 +00:00
|
|
|
assert_eq!(
|
|
|
|
expected, actual,
|
2023-01-11 09:51:22 +00:00
|
|
|
"testing ButtonSettings::is_released() for value: {value}",
|
2022-10-17 14:38:55 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_new_button_settings_given_valid_parameters() {
|
|
|
|
let cases = [
|
|
|
|
(1.0, 0.0),
|
|
|
|
(1.0, 1.0),
|
|
|
|
(1.0, 0.9),
|
|
|
|
(0.9, 0.9),
|
|
|
|
(0.9, 0.0),
|
|
|
|
(0.0, 0.0),
|
|
|
|
];
|
|
|
|
|
|
|
|
for (press_threshold, release_threshold) in cases {
|
|
|
|
let bs = ButtonSettings::new(press_threshold, release_threshold);
|
|
|
|
match bs {
|
|
|
|
Ok(button_settings) => {
|
|
|
|
assert_eq!(button_settings.press_threshold, press_threshold);
|
|
|
|
assert_eq!(button_settings.release_threshold, release_threshold);
|
|
|
|
}
|
|
|
|
Err(_) => {
|
|
|
|
panic!(
|
2023-01-11 09:51:22 +00:00
|
|
|
"ButtonSettings::new({press_threshold}, {release_threshold}) should be valid"
|
2022-10-17 14:38:55 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_new_button_settings_given_invalid_parameters() {
|
|
|
|
let cases = [
|
|
|
|
(1.1, 0.0),
|
|
|
|
(1.1, 1.0),
|
|
|
|
(1.0, 1.1),
|
|
|
|
(-1.0, 0.9),
|
|
|
|
(-1.0, 0.0),
|
|
|
|
(-1.0, -0.4),
|
|
|
|
(0.9, 1.0),
|
|
|
|
(0.0, 0.1),
|
|
|
|
];
|
|
|
|
|
|
|
|
for (press_threshold, release_threshold) in cases {
|
|
|
|
let bs = ButtonSettings::new(press_threshold, release_threshold);
|
|
|
|
match bs {
|
|
|
|
Ok(_) => {
|
|
|
|
panic!(
|
2023-01-11 09:51:22 +00:00
|
|
|
"ButtonSettings::new({press_threshold}, {release_threshold}) should be invalid"
|
2022-10-17 14:38:55 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
Err(err_code) => match err_code {
|
|
|
|
ButtonSettingsError::PressThresholdOutOfRange(_press_threshold) => {}
|
|
|
|
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
|
|
|
|
press_threshold: _press_threshold,
|
|
|
|
release_threshold: _release_threshold,
|
|
|
|
} => {}
|
|
|
|
ButtonSettingsError::ReleaseThresholdOutOfRange(_release_threshold) => {}
|
|
|
|
},
|
|
|
|
}
|
2021-12-18 20:00:18 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-17 14:38:55 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_try_out_of_range_axis_settings() {
|
|
|
|
let mut axis_settings = AxisSettings::default();
|
2023-01-16 18:13:04 +00:00
|
|
|
assert_eq!(
|
|
|
|
AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.001),
|
|
|
|
Ok(AxisSettings {
|
|
|
|
livezone_lowerbound: -0.95,
|
|
|
|
deadzone_lowerbound: -0.05,
|
|
|
|
deadzone_upperbound: 0.05,
|
|
|
|
livezone_upperbound: 0.95,
|
|
|
|
threshold: 0.001,
|
|
|
|
})
|
|
|
|
);
|
2022-10-17 14:38:55 +00:00
|
|
|
assert_eq!(
|
|
|
|
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(-2.0)),
|
|
|
|
axis_settings.try_set_livezone_lowerbound(-2.0)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(0.1)),
|
|
|
|
axis_settings.try_set_livezone_lowerbound(0.1)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(-2.0)),
|
|
|
|
axis_settings.try_set_deadzone_lowerbound(-2.0)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(0.1)),
|
|
|
|
axis_settings.try_set_deadzone_lowerbound(0.1)
|
|
|
|
);
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(-0.1)),
|
|
|
|
axis_settings.try_set_deadzone_upperbound(-0.1)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(1.1)),
|
|
|
|
axis_settings.try_set_deadzone_upperbound(1.1)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(-0.1)),
|
|
|
|
axis_settings.try_set_livezone_upperbound(-0.1)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(1.1)),
|
|
|
|
axis_settings.try_set_livezone_upperbound(1.1)
|
|
|
|
);
|
|
|
|
|
|
|
|
axis_settings.set_livezone_lowerbound(-0.7);
|
|
|
|
axis_settings.set_deadzone_lowerbound(-0.3);
|
|
|
|
assert_eq!(
|
|
|
|
Err(
|
|
|
|
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
|
|
|
|
livezone_lowerbound: -0.1,
|
|
|
|
deadzone_lowerbound: -0.3,
|
|
|
|
}
|
|
|
|
),
|
|
|
|
axis_settings.try_set_livezone_lowerbound(-0.1)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Err(
|
|
|
|
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
|
|
|
|
livezone_lowerbound: -0.7,
|
|
|
|
deadzone_lowerbound: -0.9
|
|
|
|
}
|
|
|
|
),
|
|
|
|
axis_settings.try_set_deadzone_lowerbound(-0.9)
|
|
|
|
);
|
|
|
|
|
|
|
|
axis_settings.set_deadzone_upperbound(0.3);
|
|
|
|
axis_settings.set_livezone_upperbound(0.7);
|
|
|
|
assert_eq!(
|
|
|
|
Err(
|
|
|
|
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
|
|
|
|
deadzone_upperbound: 0.8,
|
|
|
|
livezone_upperbound: 0.7
|
|
|
|
}
|
|
|
|
),
|
|
|
|
axis_settings.try_set_deadzone_upperbound(0.8)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
Err(
|
|
|
|
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
|
|
|
|
deadzone_upperbound: 0.3,
|
|
|
|
livezone_upperbound: 0.1
|
|
|
|
}
|
|
|
|
),
|
|
|
|
axis_settings.try_set_livezone_upperbound(0.1)
|
|
|
|
);
|
|
|
|
}
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
|
|
|
|
struct TestContext {
|
|
|
|
pub app: App,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TestContext {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
let mut app = App::new();
|
|
|
|
app.add_systems(
|
|
|
|
PreUpdate,
|
|
|
|
(
|
|
|
|
gamepad_connection_system,
|
|
|
|
gamepad_event_processing_system.after(gamepad_connection_system),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.add_event::<GamepadEvent>()
|
|
|
|
.add_event::<GamepadConnectionEvent>()
|
|
|
|
.add_event::<RawGamepadButtonChangedEvent>()
|
|
|
|
.add_event::<GamepadButtonChangedEvent>()
|
|
|
|
.add_event::<GamepadButtonStateChangedEvent>()
|
|
|
|
.add_event::<GamepadAxisChangedEvent>()
|
|
|
|
.add_event::<RawGamepadAxisChangedEvent>()
|
|
|
|
.add_event::<RawGamepadEvent>();
|
|
|
|
Self { app }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update(&mut self) {
|
|
|
|
self.app.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn send_gamepad_connection_event(&mut self, gamepad: Option<Entity>) -> Entity {
|
|
|
|
let gamepad = gamepad.unwrap_or_else(|| self.app.world_mut().spawn_empty().id());
|
|
|
|
self.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<GamepadConnectionEvent>>()
|
|
|
|
.send(GamepadConnectionEvent::new(
|
|
|
|
gamepad,
|
|
|
|
Connected(GamepadInfo {
|
|
|
|
name: String::from("Gamepad test"),
|
2024-10-08 12:19:38 +00:00
|
|
|
vendor_id: None,
|
|
|
|
product_id: None,
|
Implement gamepads as entities (#12770)
# Objective
- Significantly improve the ergonomics of gamepads and allow new
features
Gamepads are a bit unergonomic to work with, they use resources but
unlike other inputs, they are not limited to a single gamepad, to get
around this it uses an identifier (Gamepad) to interact with anything
causing all sorts of issues.
1. There are too many: Gamepads, GamepadSettings, GamepadInfo,
ButtonInput<T>, 2 Axis<T>.
2. ButtonInput/Axis generic methods become really inconvenient to use
e.g. any_pressed()
3. GamepadButton/Axis structs are unnecessary boilerplate:
```rust
for gamepad in gamepads.iter() {
if button_inputs.just_pressed(GamepadButton::new(gamepad, GamepadButtonType::South)) {
info!("{:?} just pressed South", gamepad);
} else if button_inputs.just_released(GamepadButton::new(gamepad, GamepadButtonType::South))
{
info!("{:?} just released South", gamepad);
}
}
```
4. Projects often need to create resources to store the selected gamepad
and have to manually check if their gamepad is still valid anyways.
- Previously attempted by #3419 and #12674
## Solution
- Implement gamepads as entities.
Using entities solves all the problems above and opens new
possibilities.
1. Reduce boilerplate and allows iteration
```rust
let is_pressed = gamepads_buttons.iter().any(|buttons| buttons.pressed(GamepadButtonType::South))
```
2. ButtonInput/Axis generic methods become ergonomic again
```rust
gamepad_buttons.any_just_pressed([GamepadButtonType::Start, GamepadButtonType::Select])
```
3. Reduces the number of public components significantly (Gamepad,
GamepadSettings, GamepadButtons, GamepadAxes)
4. Components are highly convenient. Gamepad optional features could now
be expressed naturally (`Option<Rumble> or Option<Gyro>`), allows devs
to attach their own components and filter them, so code like this
becomes possible:
```rust
fn move_player<const T: usize>(
player: Query<&Transform, With<Player<T>>>,
gamepads_buttons: Query<&GamepadButtons, With<Player<T>>>,
) {
if let Ok(gamepad_buttons) = gamepads_buttons.get_single() {
if gamepad_buttons.pressed(GamepadButtonType::South) {
// move player
}
}
}
```
---
## Follow-up
- [ ] Run conditions?
- [ ] Rumble component
# Changelog
## Added
TODO
## Changed
TODO
## Removed
TODO
## Migration Guide
TODO
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-09-27 20:07:20 +00:00
|
|
|
}),
|
|
|
|
));
|
|
|
|
gamepad
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn send_gamepad_disconnection_event(&mut self, gamepad: Entity) {
|
|
|
|
self.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<GamepadConnectionEvent>>()
|
|
|
|
.send(GamepadConnectionEvent::new(gamepad, Disconnected));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn send_raw_gamepad_event(&mut self, event: RawGamepadEvent) {
|
|
|
|
self.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<RawGamepadEvent>>()
|
|
|
|
.send(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn send_raw_gamepad_event_batch(
|
|
|
|
&mut self,
|
|
|
|
events: impl IntoIterator<Item = RawGamepadEvent>,
|
|
|
|
) {
|
|
|
|
self.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<RawGamepadEvent>>()
|
|
|
|
.send_batch(events);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn connection_event() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.iter(ctx.app.world())
|
|
|
|
.len(),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
ctx.send_gamepad_connection_event(None);
|
|
|
|
ctx.update();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<(&Gamepad, &GamepadSettings)>()
|
|
|
|
.iter(ctx.app.world())
|
|
|
|
.len(),
|
|
|
|
1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn disconnection_event() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.iter(ctx.app.world())
|
|
|
|
.len(),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
ctx.update();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<(&Gamepad, &GamepadSettings)>()
|
|
|
|
.iter(ctx.app.world())
|
|
|
|
.len(),
|
|
|
|
1
|
|
|
|
);
|
|
|
|
ctx.send_gamepad_disconnection_event(entity);
|
|
|
|
ctx.update();
|
|
|
|
// Gamepad component should be removed
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.is_err());
|
|
|
|
// Settings should be kept
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&GamepadSettings>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.is_ok());
|
|
|
|
|
|
|
|
// Mistakenly sending a second disconnection event shouldn't break anything
|
|
|
|
ctx.send_gamepad_disconnection_event(entity);
|
|
|
|
ctx.update();
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.is_err());
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&GamepadSettings>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn connection_disconnection_frame_event() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.iter(ctx.app.world())
|
|
|
|
.len(),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
ctx.send_gamepad_disconnection_event(entity);
|
|
|
|
ctx.update();
|
|
|
|
// Gamepad component should be removed
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.is_err());
|
|
|
|
// Settings should be kept
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&GamepadSettings>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn reconnection_event() {
|
|
|
|
let button_settings = ButtonSettings::new(0.7, 0.2).expect("correct parameters");
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.iter(ctx.app.world())
|
|
|
|
.len(),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
ctx.update();
|
|
|
|
let mut settings = ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&mut GamepadSettings>()
|
|
|
|
.get_mut(ctx.app.world_mut(), entity)
|
|
|
|
.expect("be alive");
|
|
|
|
assert_ne!(settings.default_button_settings, button_settings);
|
|
|
|
settings.default_button_settings = button_settings.clone();
|
|
|
|
ctx.send_gamepad_disconnection_event(entity);
|
|
|
|
ctx.update();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.iter(ctx.app.world())
|
|
|
|
.len(),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
ctx.send_gamepad_connection_event(Some(entity));
|
|
|
|
ctx.update();
|
|
|
|
let settings = ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&GamepadSettings>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.expect("be alive");
|
|
|
|
assert_eq!(settings.default_button_settings, button_settings);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn reconnection_same_frame_event() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.iter(ctx.app.world())
|
|
|
|
.len(),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
ctx.send_gamepad_disconnection_event(entity);
|
|
|
|
ctx.update();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.iter(ctx.app.world())
|
|
|
|
.len(),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<(Entity, &GamepadSettings)>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gamepad_axis_valid() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<RawGamepadEvent>>()
|
|
|
|
.send_batch([
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickY,
|
|
|
|
0.5,
|
|
|
|
)),
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::RightStickX,
|
|
|
|
0.6,
|
|
|
|
)),
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::RightZ,
|
|
|
|
-0.4,
|
|
|
|
)),
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::RightStickY,
|
|
|
|
-0.8,
|
|
|
|
)),
|
|
|
|
]);
|
|
|
|
ctx.update();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadAxisChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
4
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gamepad_axis_threshold_filter() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
let settings = GamepadSettings::default().default_axis_settings;
|
|
|
|
// Set of events to ensure they are being properly filtered
|
|
|
|
let base_value = 0.5;
|
|
|
|
let events = [
|
|
|
|
// Event above threshold
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
base_value,
|
|
|
|
)),
|
|
|
|
// Event below threshold, should be filtered
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
base_value + settings.threshold - 0.01,
|
|
|
|
)),
|
|
|
|
// Event above threshold
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
base_value + settings.threshold + 0.01,
|
|
|
|
)),
|
|
|
|
];
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<RawGamepadEvent>>()
|
|
|
|
.send_batch(events);
|
|
|
|
ctx.update();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadAxisChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
2
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gamepad_axis_deadzone_filter() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
let settings = GamepadSettings::default().default_axis_settings;
|
|
|
|
|
|
|
|
// Set of events to ensure they are being properly filtered
|
|
|
|
let events = [
|
|
|
|
// Event below deadzone upperbound should be filtered
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
settings.deadzone_upperbound - 0.01,
|
|
|
|
)),
|
|
|
|
// Event above deadzone lowerbound should be filtered
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
settings.deadzone_lowerbound + 0.01,
|
|
|
|
)),
|
|
|
|
];
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<RawGamepadEvent>>()
|
|
|
|
.send_batch(events);
|
|
|
|
ctx.update();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadAxisChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gamepad_axis_deadzone_rounded() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
let settings = GamepadSettings::default().default_axis_settings;
|
|
|
|
|
|
|
|
// Set of events to ensure they are being properly filtered
|
|
|
|
let events = [
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
1.0,
|
|
|
|
)),
|
|
|
|
// Event below deadzone upperbound should be rounded to 0
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
settings.deadzone_upperbound - 0.01,
|
|
|
|
)),
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
1.0,
|
|
|
|
)),
|
|
|
|
// Event above deadzone lowerbound should be rounded to 0
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
settings.deadzone_lowerbound + 0.01,
|
|
|
|
)),
|
|
|
|
];
|
|
|
|
let results = [1.0, 0.0, 1.0, 0.0];
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<RawGamepadEvent>>()
|
|
|
|
.send_batch(events);
|
|
|
|
ctx.update();
|
|
|
|
|
|
|
|
let events = ctx
|
|
|
|
.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadAxisChangedEvent>>();
|
|
|
|
let mut event_reader = events.get_cursor();
|
|
|
|
for (event, result) in event_reader.read(events).zip(results) {
|
|
|
|
assert_eq!(event.value, result);
|
|
|
|
}
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadAxisChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
4
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gamepad_axis_livezone_filter() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
let settings = GamepadSettings::default().default_axis_settings;
|
|
|
|
|
|
|
|
// Set of events to ensure they are being properly filtered
|
|
|
|
let events = [
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
1.0,
|
|
|
|
)),
|
|
|
|
// Event above livezone upperbound should be filtered
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
settings.livezone_upperbound + 0.01,
|
|
|
|
)),
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
-1.0,
|
|
|
|
)),
|
|
|
|
// Event below livezone lowerbound should be filtered
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
settings.livezone_lowerbound - 0.01,
|
|
|
|
)),
|
|
|
|
];
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<RawGamepadEvent>>()
|
|
|
|
.send_batch(events);
|
|
|
|
ctx.update();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadAxisChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
2
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gamepad_axis_livezone_rounded() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
let settings = GamepadSettings::default().default_axis_settings;
|
|
|
|
|
|
|
|
// Set of events to ensure they are being properly filtered
|
|
|
|
let events = [
|
|
|
|
// Event above livezone upperbound should be rounded to 1
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
settings.livezone_upperbound + 0.01,
|
|
|
|
)),
|
|
|
|
// Event below livezone lowerbound should be rounded to -1
|
|
|
|
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadAxis::LeftStickX,
|
|
|
|
settings.livezone_lowerbound - 0.01,
|
|
|
|
)),
|
|
|
|
];
|
|
|
|
let results = [1.0, -1.0];
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<RawGamepadEvent>>()
|
|
|
|
.send_batch(events);
|
|
|
|
ctx.update();
|
|
|
|
|
|
|
|
let events = ctx
|
|
|
|
.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadAxisChangedEvent>>();
|
|
|
|
let mut event_reader = events.get_cursor();
|
|
|
|
for (event, result) in event_reader.read(events).zip(results) {
|
|
|
|
assert_eq!(event.value, result);
|
|
|
|
}
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadAxisChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
2
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gamepad_buttons_pressed() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
let digital_settings = GamepadSettings::default().default_button_settings;
|
|
|
|
|
|
|
|
let events = [RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.press_threshold,
|
|
|
|
))];
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<RawGamepadEvent>>()
|
|
|
|
.send_batch(events);
|
|
|
|
ctx.update();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadButtonStateChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
1
|
|
|
|
);
|
|
|
|
let events = ctx
|
|
|
|
.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadButtonStateChangedEvent>>();
|
|
|
|
let mut event_reader = events.get_cursor();
|
|
|
|
for event in event_reader.read(events) {
|
|
|
|
assert_eq!(event.button, GamepadButton::DPadDown);
|
|
|
|
assert_eq!(event.state, ButtonState::Pressed);
|
|
|
|
}
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.unwrap()
|
|
|
|
.pressed(GamepadButton::DPadDown));
|
|
|
|
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
|
|
|
|
.clear();
|
|
|
|
ctx.update();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadButtonStateChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.unwrap()
|
|
|
|
.pressed(GamepadButton::DPadDown));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gamepad_buttons_just_pressed() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
let digital_settings = GamepadSettings::default().default_button_settings;
|
|
|
|
|
|
|
|
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.press_threshold,
|
|
|
|
)));
|
|
|
|
ctx.update();
|
|
|
|
|
|
|
|
// Check it is flagged for this frame
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.unwrap()
|
|
|
|
.just_pressed(GamepadButton::DPadDown));
|
|
|
|
ctx.update();
|
|
|
|
|
|
|
|
//Check it clears next frame
|
|
|
|
assert!(!ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.unwrap()
|
|
|
|
.just_pressed(GamepadButton::DPadDown));
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn gamepad_buttons_released() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
let digital_settings = GamepadSettings::default().default_button_settings;
|
|
|
|
|
|
|
|
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.press_threshold,
|
|
|
|
)));
|
|
|
|
ctx.update();
|
|
|
|
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
|
|
|
|
.clear();
|
|
|
|
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.release_threshold - 0.01,
|
|
|
|
)));
|
|
|
|
ctx.update();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadButtonStateChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
1
|
|
|
|
);
|
|
|
|
let events = ctx
|
|
|
|
.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadButtonStateChangedEvent>>();
|
|
|
|
let mut event_reader = events.get_cursor();
|
|
|
|
for event in event_reader.read(events) {
|
|
|
|
assert_eq!(event.button, GamepadButton::DPadDown);
|
|
|
|
assert_eq!(event.state, ButtonState::Released);
|
|
|
|
}
|
|
|
|
assert!(!ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.unwrap()
|
|
|
|
.pressed(GamepadButton::DPadDown));
|
|
|
|
ctx.app
|
|
|
|
.world_mut()
|
|
|
|
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
|
|
|
|
.clear();
|
|
|
|
ctx.update();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadButtonStateChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gamepad_buttons_just_released() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
let digital_settings = GamepadSettings::default().default_button_settings;
|
|
|
|
|
|
|
|
ctx.send_raw_gamepad_event_batch([
|
|
|
|
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.press_threshold,
|
|
|
|
)),
|
|
|
|
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.release_threshold - 0.01,
|
|
|
|
)),
|
|
|
|
]);
|
|
|
|
ctx.update();
|
|
|
|
|
|
|
|
// Check it is flagged for this frame
|
|
|
|
assert!(ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.unwrap()
|
|
|
|
.just_released(GamepadButton::DPadDown));
|
|
|
|
ctx.update();
|
|
|
|
|
|
|
|
//Check it clears next frame
|
|
|
|
assert!(!ctx
|
|
|
|
.app
|
|
|
|
.world_mut()
|
|
|
|
.query::<&Gamepad>()
|
|
|
|
.get(ctx.app.world(), entity)
|
|
|
|
.unwrap()
|
|
|
|
.just_released(GamepadButton::DPadDown));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn gamepad_buttons_axis() {
|
|
|
|
let mut ctx = TestContext::new();
|
|
|
|
|
|
|
|
// Create test gamepad
|
|
|
|
let entity = ctx.send_gamepad_connection_event(None);
|
|
|
|
let digital_settings = GamepadSettings::default().default_button_settings;
|
|
|
|
let analog_settings = GamepadSettings::default().default_button_axis_settings;
|
|
|
|
|
|
|
|
// Test events
|
|
|
|
let events = [
|
|
|
|
// Should trigger event
|
|
|
|
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.press_threshold,
|
|
|
|
)),
|
|
|
|
// Should trigger event
|
|
|
|
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.release_threshold,
|
|
|
|
)),
|
|
|
|
// Shouldn't trigger a state changed event
|
|
|
|
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.release_threshold - analog_settings.threshold * 1.01,
|
|
|
|
)),
|
|
|
|
// Shouldn't trigger any event
|
|
|
|
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.release_threshold - (analog_settings.threshold * 1.5),
|
|
|
|
)),
|
|
|
|
// Shouldn't trigger a state changed event
|
|
|
|
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
|
|
|
|
entity,
|
|
|
|
GamepadButton::DPadDown,
|
|
|
|
digital_settings.release_threshold - (analog_settings.threshold * 2.02),
|
|
|
|
)),
|
|
|
|
];
|
|
|
|
ctx.send_raw_gamepad_event_batch(events);
|
|
|
|
ctx.update();
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadButtonStateChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
2
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
ctx.app
|
|
|
|
.world()
|
|
|
|
.resource::<Events<GamepadButtonChangedEvent>>()
|
|
|
|
.len(),
|
|
|
|
4
|
|
|
|
);
|
|
|
|
}
|
2021-12-08 01:12:49 +00:00
|
|
|
}
|