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.
This commit is contained in:
DevinLeamy 2023-01-09 19:24:52 +00:00
parent fa15b31930
commit e94215c4c6
5 changed files with 415 additions and 396 deletions

View file

@ -1,30 +1,45 @@
use crate::converter::{convert_axis, convert_button, convert_gamepad_id};
use bevy_ecs::event::EventWriter;
use bevy_ecs::system::{NonSend, NonSendMut};
use bevy_input::gamepad::GamepadInfo;
use bevy_input::{gamepad::GamepadEventRaw, prelude::*};
use bevy_ecs::system::{NonSend, NonSendMut, Res};
use bevy_input::gamepad::{
GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnection, GamepadConnectionEvent,
GamepadSettings,
};
use bevy_input::gamepad::{GamepadEvent, GamepadInfo};
use bevy_input::prelude::{GamepadAxis, GamepadButton};
use bevy_input::Axis;
use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter, Gilrs};
pub fn gilrs_event_startup_system(gilrs: NonSend<Gilrs>, mut events: EventWriter<GamepadEventRaw>) {
pub fn gilrs_event_startup_system(
gilrs: NonSend<Gilrs>,
mut connection_events: EventWriter<GamepadConnectionEvent>,
) {
for (id, gamepad) in gilrs.gamepads() {
let info = GamepadInfo {
name: gamepad.name().into(),
};
events.send(GamepadEventRaw::new(
convert_gamepad_id(id),
GamepadEventType::Connected(info),
));
connection_events.send(GamepadConnectionEvent {
gamepad: convert_gamepad_id(id),
connection: GamepadConnection::Connected(info),
});
}
}
pub fn gilrs_event_system(mut gilrs: NonSendMut<Gilrs>, mut events: EventWriter<GamepadEventRaw>) {
pub fn gilrs_event_system(
mut gilrs: NonSendMut<Gilrs>,
mut events: EventWriter<GamepadEvent>,
gamepad_axis: Res<Axis<GamepadAxis>>,
gamepad_buttons: Res<Axis<GamepadButton>>,
gamepad_settings: Res<GamepadSettings>,
) {
while let Some(gilrs_event) = gilrs
.next_event()
.filter_ev(&axis_dpad_to_button, &mut gilrs)
{
gilrs.update(&gilrs_event);
let gamepad = convert_gamepad_id(gilrs_event.id);
match gilrs_event.event {
EventType::Connected => {
let pad = gilrs.gamepad(gilrs_event.id);
@ -32,31 +47,39 @@ pub fn gilrs_event_system(mut gilrs: NonSendMut<Gilrs>, mut events: EventWriter<
name: pad.name().into(),
};
events.send(GamepadEventRaw::new(
convert_gamepad_id(gilrs_event.id),
GamepadEventType::Connected(info),
));
events.send(
GamepadConnectionEvent::new(gamepad, GamepadConnection::Connected(info)).into(),
);
}
EventType::Disconnected => {
events.send(GamepadEventRaw::new(
convert_gamepad_id(gilrs_event.id),
GamepadEventType::Disconnected,
));
}
EventType::ButtonChanged(gilrs_button, value, _) => {
EventType::Disconnected => events
.send(GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected).into()),
EventType::ButtonChanged(gilrs_button, raw_value, _) => {
if let Some(button_type) = convert_button(gilrs_button) {
events.send(GamepadEventRaw::new(
convert_gamepad_id(gilrs_event.id),
GamepadEventType::ButtonChanged(button_type, value),
));
let button = GamepadButton::new(gamepad, button_type);
let old_value = gamepad_buttons.get(button);
let button_settings = gamepad_settings.get_button_axis_settings(button);
// Only send events that pass the user-defined change threshold
if let Some(filtered_value) = button_settings.filter(raw_value, old_value) {
events.send(
GamepadButtonChangedEvent::new(gamepad, button_type, filtered_value)
.into(),
);
}
}
}
EventType::AxisChanged(gilrs_axis, value, _) => {
EventType::AxisChanged(gilrs_axis, raw_value, _) => {
if let Some(axis_type) = convert_axis(gilrs_axis) {
events.send(GamepadEventRaw::new(
convert_gamepad_id(gilrs_event.id),
GamepadEventType::AxisChanged(axis_type, value),
));
let axis = GamepadAxis::new(gamepad, axis_type);
let old_value = gamepad_axis.get(axis);
let axis_settings = gamepad_settings.get_axis_settings(axis);
// Only send events that pass the user-defined change threshold
if let Some(filtered_value) = axis_settings.filter(raw_value, old_value) {
events.send(
GamepadAxisChangedEvent::new(gamepad, axis_type, filtered_value).into(),
);
}
}
}
_ => (),

View file

@ -65,8 +65,8 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// ## Usage
///
/// The primary way to access the individual connected gamepads is done through the [`Gamepads`]
/// `bevy` resource. It is also used inside of [`GamepadEvent`]s and [`GamepadEventRaw`]s to distinguish
/// which gamepad an event corresponds to.
/// `bevy` resource. It is also used inside of [`GamepadConnectionEvent`]s to correspond a gamepad
/// with a connection event.
///
/// ## Note
///
@ -111,8 +111,7 @@ pub struct GamepadInfo {
/// ## Updating
///
/// The [`Gamepad`]s are registered and deregistered in the [`gamepad_connection_system`]
/// whenever a [`GamepadEventType::Connected`] or [`GamepadEventType::Disconnected`]
/// event is received.
/// whenever a [`GamepadConnectionEvent`] is received.
#[derive(Resource, Default, Debug)]
pub struct Gamepads {
/// The collection of the connected [`Gamepad`]s.
@ -145,184 +144,12 @@ impl Gamepads {
}
}
/// The data contained in a [`GamepadEvent`] or [`GamepadEventRaw`].
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum GamepadEventType {
/// A [`Gamepad`] has been connected.
Connected(GamepadInfo),
/// A [`Gamepad`] has been disconnected.
Disconnected,
/// The value of a [`Gamepad`] button has changed.
ButtonChanged(GamepadButtonType, f32),
/// The value of a [`Gamepad`] axis has changed.
AxisChanged(GamepadAxisType, f32),
}
/// An event of a [`Gamepad`].
///
/// This event is the translated version of the [`GamepadEventRaw`]. It is available to
/// the end user and can be used for game logic.
///
/// ## Differences
///
/// The difference between the [`GamepadEventRaw`] and the [`GamepadEvent`] is that the
/// former respects user defined [`GamepadSettings`] for the gamepad inputs when translating it
/// to the latter. The former also updates the [`Input<GamepadButton>`], [`Axis<GamepadAxis>`],
/// and [`Axis<GamepadButton>`] resources accordingly.
///
/// ## Gamepad input mocking
///
/// When mocking gamepad input, use [`GamepadEventRaw`]s instead of [`GamepadEvent`]s.
/// Otherwise [`GamepadSettings`] won't be respected and the [`Input<GamepadButton>`],
/// [`Axis<GamepadAxis>`], and [`Axis<GamepadButton>`] resources won't be updated correctly.
///
/// An example for gamepad input mocking can be seen in the documentation of the [`GamepadEventRaw`].
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct GamepadEvent {
/// The gamepad this event corresponds to.
pub gamepad: Gamepad,
/// The type of the event.
pub event_type: GamepadEventType,
}
impl GamepadEvent {
/// Creates a new [`GamepadEvent`].
pub fn new(gamepad: Gamepad, event_type: GamepadEventType) -> Self {
Self {
gamepad,
event_type,
}
}
}
/// A raw event of a [`Gamepad`].
///
/// This event is the translated version of the `EventType` from the `GilRs` crate.
/// It is available to the end user and can be used for game logic.
///
/// ## Differences
///
/// The difference between the `EventType` from the `GilRs` crate and the [`GamepadEventRaw`]
/// is that the latter has less events, because the button pressing logic is handled through the generic
/// [`Input<T>`] instead of through events.
///
/// The difference between the [`GamepadEventRaw`] and the [`GamepadEvent`] can be seen in the documentation
/// of the [`GamepadEvent`].
///
/// ## Gamepad input mocking
///
/// The following example showcases how to mock gamepad input by manually sending [`GamepadEventRaw`]s.
///
/// ```
/// # use bevy_input::prelude::*;
/// # use bevy_input::InputPlugin;
/// # use bevy_input::gamepad::{GamepadEventRaw, GamepadInfo};
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #[derive(Resource)]
/// struct MyResource(bool);
///
/// // This system sets the bool inside `MyResource` to `true` if the `South` button of the first gamepad is pressed.
/// fn change_resource_on_gamepad_button_press(
/// mut my_resource: ResMut<MyResource>,
/// gamepads: Res<Gamepads>,
/// button_inputs: ResMut<Input<GamepadButton>>,
/// ) {
/// let gamepad = gamepads.iter().next().unwrap();
/// let gamepad_button = GamepadButton::new(gamepad, GamepadButtonType::South);
///
/// my_resource.0 = button_inputs.pressed(gamepad_button);
/// }
///
/// // Create our app.
/// let mut app = App::new();
///
/// // Add the input plugin and the system/resource we want to test.
/// app.add_plugin(InputPlugin)
/// .insert_resource(MyResource(false))
/// .add_system(change_resource_on_gamepad_button_press);
///
/// // Define our dummy gamepad input data.
/// let gamepad = Gamepad::new(0);
/// let button_type = GamepadButtonType::South;
///
/// // Send the gamepad connected event to mark our gamepad as connected.
/// // This updates the `Gamepads` resource accordingly.
/// let info = GamepadInfo { name: "Mock Gamepad".into() };
/// app.world.send_event(GamepadEventRaw::new(gamepad, GamepadEventType::Connected(info)));
///
/// // Send the gamepad input event to mark the `South` gamepad button as pressed.
/// // This updates the `Input<GamepadButton>` resource accordingly.
/// app.world.send_event(GamepadEventRaw::new(
/// gamepad,
/// GamepadEventType::ButtonChanged(button_type, 1.0)
/// ));
///
/// // Advance the execution of the schedule by a single cycle.
/// app.update();
///
/// // At this point you can check if your game logic corresponded correctly to the gamepad input.
/// // In this example we are checking if the bool in `MyResource` was updated from `false` to `true`.
/// assert!(app.world.resource::<MyResource>().0);
///
/// // Send the gamepad input event to mark the `South` gamepad button as released.
/// // This updates the `Input<GamepadButton>` resource accordingly.
/// app.world.send_event(GamepadEventRaw::new(
/// gamepad,
/// GamepadEventType::ButtonChanged(button_type, 0.0)
/// ));
///
/// // Advance the execution of the schedule by another cycle.
/// app.update();
///
/// // Check if the bool in `MyResource` was updated from `true` to `false`.
/// assert!(!app.world.resource::<MyResource>().0);
/// #
/// # bevy_ecs::system::assert_is_system(change_resource_on_gamepad_button_press);
/// ```
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct GamepadEventRaw {
/// The gamepad this event corresponds to.
pub gamepad: Gamepad,
/// The type of the event.
pub event_type: GamepadEventType,
}
impl GamepadEventRaw {
/// Creates a new [`GamepadEventRaw`].
pub fn new(gamepad: Gamepad, event_type: GamepadEventType) -> Self {
Self {
gamepad,
event_type,
}
}
}
/// A type of a [`GamepadButton`].
///
/// ## Usage
///
/// This is used to determine which button has changed its value when receiving a
/// [`GamepadEventType::ButtonChanged`]. It is also used in the [`GamepadButton`]
/// [`GamepadButtonChangedEvent`]. It is also used in the [`GamepadButton`]
/// which in turn is used to create the [`Input<GamepadButton>`] or
/// [`Axis<GamepadButton>`] `bevy` resources.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)]
@ -385,11 +212,11 @@ pub enum GamepadButtonType {
/// ## Usage
///
/// It is used as the generic `T` value of an [`Input`] and [`Axis`] to create `bevy` resources. These
/// resources store the data of the buttons and axes of a gamepad and can be accessed inside of a system.
/// resources store the data of the buttons of a gamepad and can be accessed inside of a system.
///
/// ## Updating
///
/// The resources are updated inside of the [`gamepad_event_system`].
/// The gamepad button resources are updated inside of the [`gamepad_button_event_system`].
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)]
#[reflect(Debug, Hash, PartialEq)]
#[cfg_attr(
@ -430,7 +257,7 @@ impl GamepadButton {
/// ## Usage
///
/// This is used to determine which axis has changed its value when receiving a
/// [`GamepadEventType::AxisChanged`]. It is also used in the [`GamepadAxis`]
/// [`GamepadAxisChangedEvent`]. It is also used in the [`GamepadAxis`]
/// which in turn is used to create the [`Axis<GamepadAxis>`] `bevy` resource.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)]
#[reflect(Debug, Hash, PartialEq)]
@ -462,12 +289,12 @@ pub enum GamepadAxisType {
///
/// ## Usage
///
/// It is used as the generic `T` value of an [`Axis`] to create a `bevy` resource. This resource
/// stores the data of the axes of a gamepad and can be accessed inside of a system.
/// It is used as the generic `T` value of an [`Axis`] to create `bevy` resources. These
/// resources store the data of the axes of a gamepad and can be accessed inside of a system.
///
/// ## Updating
///
/// The resource is updated inside of the [`gamepad_event_system`].
/// The gamepad axes resources are updated inside of the [`gamepad_axis_event_system`].
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect, FromReflect)]
#[reflect(Debug, Hash, PartialEq)]
#[cfg_attr(
@ -510,8 +337,9 @@ impl GamepadAxis {
///
/// ## Note
///
/// The [`GamepadSettings`] are used inside of the [`gamepad_event_system`], but are never written to
/// inside of `bevy`. To modify these settings, mutate the corresponding resource.
/// The [`GamepadSettings`] are used inside of `bevy_gilrs` to determine when raw gamepad events from `girls`,
/// should register as a [`GamepadEvent`]. Events that don't meet the change thresholds defined in [`GamepadSettings`]
/// will not register. To modify these settings, mutate the corresponding resource.
#[derive(Resource, Default, Debug, Reflect, FromReflect)]
#[reflect(Debug, Default)]
pub struct GamepadSettings {
@ -1032,25 +860,40 @@ impl AxisSettings {
self.threshold
}
fn filter(&self, new_value: f32, old_value: Option<f32>) -> Option<f32> {
let new_value =
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
};
/// 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
}
}
if let Some(old_value) = old_value {
if (new_value - old_value).abs() <= self.threshold {
return None;
}
/// Determines whether the change from `old_value` to `new_value` should
/// be registered as a change, according to the `AxisSettings`.
fn should_register_change(&self, new_value: f32, old_value: Option<f32>) -> bool {
if old_value.is_none() {
return true;
}
Some(new_value)
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
}
}
@ -1069,7 +912,7 @@ impl AxisSettings {
///
/// ## Updating
///
/// The current value of a button is received through the [`GamepadEvent`]s or [`GamepadEventRaw`]s.
/// The current value of a button is received through the [`GamepadButtonChangedEvent`].
#[derive(Debug, Clone, Reflect, FromReflect)]
#[reflect(Debug, Default)]
pub struct ButtonAxisSettings {
@ -1092,137 +935,275 @@ impl Default for ButtonAxisSettings {
}
impl ButtonAxisSettings {
/// Filters the `new_value` according to the specified settings.
/// Clamps the `raw_value` according to the specified settings.
///
/// If the `new_value` is:
/// If the `raw_value` is:
/// - 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.
///
/// If the difference between the calculated value and the `old_value` is lower or
/// equal to the `threshold`, [`None`] will be returned.
fn filter(&self, new_value: f32, old_value: Option<f32>) -> Option<f32> {
let new_value = if new_value <= self.low {
0.0
} else if new_value >= self.high {
1.0
} else {
new_value
};
if let Some(old_value) = old_value {
if (new_value - old_value).abs() <= self.threshold {
return None;
}
fn clamp(&self, raw_value: f32) -> f32 {
if raw_value <= self.low {
return 0.0;
}
if raw_value >= self.high {
return 1.0;
}
Some(new_value)
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;
}
f32::abs(new_value - old_value.unwrap()) > self.threshold
}
/// Filters the `new_value` based on the `old_value`, according to the `ButtonAxisSettings`.
///
/// 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
}
}
/// Monitors gamepad connection and disconnection events and updates the [`Gamepads`] resource accordingly.
/// Handles [`GamepadConnectionEvent`]s and updates gamepad resources.
///
/// Updates the [`Gamepads`] resource and resets and/or initializes
/// the [`Axis<GamepadButton>`] and [`Input<GamepadButton>`] resources.
///
/// ## Note
///
/// Whenever a [`Gamepad`] connects or disconnects, an information gets printed to the console using the [`info!`] macro.
pub fn gamepad_connection_system(
mut gamepads: ResMut<Gamepads>,
mut gamepad_event: EventReader<GamepadEvent>,
mut connection_events: EventReader<GamepadConnectionEvent>,
mut axis: ResMut<Axis<GamepadAxis>>,
mut button_axis: ResMut<Axis<GamepadButton>>,
mut button_input: ResMut<Input<GamepadButton>>,
) {
for event in gamepad_event.iter() {
match &event.event_type {
GamepadEventType::Connected(info) => {
gamepads.register(event.gamepad, info.clone());
info!("{:?} Connected", event.gamepad);
}
button_input.bypass_change_detection().clear();
for connection_event in connection_events.iter() {
let gamepad = connection_event.gamepad;
GamepadEventType::Disconnected => {
gamepads.deregister(event.gamepad);
info!("{:?} Disconnected", event.gamepad);
if let GamepadConnection::Connected(info) = &connection_event.connection {
gamepads.register(gamepad, info.clone());
info!("{:?} Connected", gamepad);
for button_type in &ALL_BUTTON_TYPES {
let gamepad_button = GamepadButton::new(gamepad, *button_type);
button_input.reset(gamepad_button);
button_axis.set(gamepad_button, 0.0);
}
for axis_type in &ALL_AXIS_TYPES {
axis.set(GamepadAxis::new(gamepad, *axis_type), 0.0);
}
} else {
gamepads.deregister(gamepad);
info!("{:?} Disconnected", gamepad);
for button_type in &ALL_BUTTON_TYPES {
let gamepad_button = GamepadButton::new(gamepad, *button_type);
button_input.reset(gamepad_button);
button_axis.remove(gamepad_button);
}
for axis_type in &ALL_AXIS_TYPES {
axis.remove(GamepadAxis::new(gamepad, *axis_type));
}
_ => (),
}
}
}
/// Modifies the gamepad resources and sends out gamepad events.
///
/// The resources [`Input<GamepadButton>`], [`Axis<GamepadAxis>`], and [`Axis<GamepadButton>`] are updated
/// and the [`GamepadEvent`]s are sent according to the received [`GamepadEventRaw`]s respecting the [`GamepadSettings`].
///
/// ## Differences
///
/// The main difference between the events and the resources is that the latter allows you to check specific
/// buttons or axes, rather than reading the events one at a time. This is done through convenient functions
/// like [`Input::pressed`], [`Input::just_pressed`], [`Input::just_released`], and [`Axis::get`].
pub fn gamepad_event_system(
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum GamepadConnection {
Connected(GamepadInfo),
Disconnected,
}
/// A Gamepad connection event. Created when a connection to a gamepad
/// is established and when a gamepad is disconnected.
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)]
#[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: Gamepad,
/// The change in the gamepads connection.
pub connection: GamepadConnection,
}
impl GamepadConnectionEvent {
pub fn new(gamepad: Gamepad, connection: GamepadConnection) -> Self {
Self {
gamepad,
connection,
}
}
pub fn connected(&self) -> bool {
matches!(self.connection, GamepadConnection::Connected(_))
}
pub fn disconnected(&self) -> bool {
!self.connected()
}
}
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct GamepadAxisChangedEvent {
pub gamepad: Gamepad,
pub axis_type: GamepadAxisType,
pub value: f32,
}
impl GamepadAxisChangedEvent {
pub fn new(gamepad: Gamepad, axis_type: GamepadAxisType, value: f32) -> Self {
Self {
gamepad,
axis_type,
value,
}
}
}
/// Gamepad event for when the "value" (amount of pressure) on the button
/// changes by an amount larger than the threshold defined in [`GamepadSettings`].
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct GamepadButtonChangedEvent {
pub gamepad: Gamepad,
pub button_type: GamepadButtonType,
pub value: f32,
}
impl GamepadButtonChangedEvent {
pub fn new(gamepad: Gamepad, button_type: GamepadButtonType, value: f32) -> Self {
Self {
gamepad,
button_type,
value,
}
}
}
/// Uses [`GamepadAxisChangedEvent`]s to update update the relevant `Input` and `Axis` values.
pub fn gamepad_axis_event_system(
mut button_input: ResMut<Input<GamepadButton>>,
mut gamepad_axis: ResMut<Axis<GamepadAxis>>,
mut axis_events: EventReader<GamepadAxisChangedEvent>,
) {
button_input.bypass_change_detection().clear();
for axis_event in axis_events.iter() {
let axis = GamepadAxis::new(axis_event.gamepad, axis_event.axis_type);
gamepad_axis.set(axis, axis_event.value);
}
}
/// Uses [`GamepadButtonChangedEvent`]s to update update the relevant `Input` and `Axis` values.
pub fn gamepad_button_event_system(
mut button_events: EventReader<GamepadButtonChangedEvent>,
mut button_input: ResMut<Input<GamepadButton>>,
mut axis: ResMut<Axis<GamepadAxis>>,
mut button_axis: ResMut<Axis<GamepadButton>>,
mut raw_events: EventReader<GamepadEventRaw>,
mut events: EventWriter<GamepadEvent>,
settings: Res<GamepadSettings>,
) {
button_input.bypass_change_detection().clear();
for event in raw_events.iter() {
match &event.event_type {
GamepadEventType::Connected(_) => {
events.send(GamepadEvent::new(event.gamepad, event.event_type.clone()));
for button_type in &ALL_BUTTON_TYPES {
let gamepad_button = GamepadButton::new(event.gamepad, *button_type);
button_input.reset(gamepad_button);
button_axis.set(gamepad_button, 0.0);
}
for axis_type in &ALL_AXIS_TYPES {
axis.set(GamepadAxis::new(event.gamepad, *axis_type), 0.0);
}
}
GamepadEventType::Disconnected => {
events.send(GamepadEvent::new(event.gamepad, event.event_type.clone()));
for button_type in &ALL_BUTTON_TYPES {
let gamepad_button = GamepadButton::new(event.gamepad, *button_type);
button_input.reset(gamepad_button);
button_axis.remove(gamepad_button);
}
for axis_type in &ALL_AXIS_TYPES {
axis.remove(GamepadAxis::new(event.gamepad, *axis_type));
}
}
GamepadEventType::AxisChanged(axis_type, value) => {
let gamepad_axis = GamepadAxis::new(event.gamepad, *axis_type);
if let Some(filtered_value) = settings
.get_axis_settings(gamepad_axis)
.filter(*value, axis.get(gamepad_axis))
{
axis.set(gamepad_axis, filtered_value);
events.send(GamepadEvent::new(
event.gamepad,
GamepadEventType::AxisChanged(*axis_type, filtered_value),
));
}
}
GamepadEventType::ButtonChanged(button_type, value) => {
let gamepad_button = GamepadButton::new(event.gamepad, *button_type);
if let Some(filtered_value) = settings
.get_button_axis_settings(gamepad_button)
.filter(*value, button_axis.get(gamepad_button))
{
button_axis.set(gamepad_button, filtered_value);
events.send(GamepadEvent::new(
event.gamepad,
GamepadEventType::ButtonChanged(*button_type, filtered_value),
));
}
for button_event in button_events.iter() {
let button = GamepadButton::new(button_event.gamepad, button_event.button_type);
let value = button_event.value;
let button_property = settings.get_button_settings(button);
let button_property = settings.get_button_settings(gamepad_button);
if button_input.pressed(gamepad_button) {
if button_property.is_released(*value) {
button_input.release(gamepad_button);
}
} else if button_property.is_pressed(*value) {
button_input.press(gamepad_button);
}
if button_property.is_released(value) {
// We don't have to check if the button was previously pressed
// because that check is performed within Input<T>::release()
button_input.release(button);
} else if button_property.is_pressed(value) {
button_input.press(button);
};
button_axis.set(button, value);
}
}
/// A gamepad event.
///
/// This event type is used over the [`GamepadConnectionEvent`],
/// [`GamepadButtonChangedEvent`] and [`GamepadAxisChangedEvent`] when
/// the in-frame relative ordering of events is important.
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum GamepadEvent {
Connection(GamepadConnectionEvent),
Button(GamepadButtonChangedEvent),
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)
}
}
/// Splits the [`GamepadEvent`] event stream into it's component events.
pub fn gamepad_event_system(
mut gamepad_events: EventReader<GamepadEvent>,
mut connection_events: EventWriter<GamepadConnectionEvent>,
mut button_events: EventWriter<GamepadButtonChangedEvent>,
mut axis_events: EventWriter<GamepadAxisChangedEvent>,
) {
for gamepad_event in gamepad_events.iter() {
match gamepad_event {
GamepadEvent::Connection(connection_event) => {
connection_events.send(connection_event.clone());
}
GamepadEvent::Button(button_event) => button_events.send(button_event.clone()),
GamepadEvent::Axis(axis_event) => axis_events.send(axis_event.clone()),
}
}
}

View file

@ -12,8 +12,7 @@ pub mod prelude {
#[doc(hidden)]
pub use crate::{
gamepad::{
Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, GamepadEvent,
GamepadEventType, Gamepads,
Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, Gamepads,
},
keyboard::{KeyCode, ScanCode},
mouse::MouseButton,
@ -23,7 +22,7 @@ pub mod prelude {
}
use bevy_app::prelude::*;
use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel};
use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel, SystemSet};
use bevy_reflect::{FromReflect, Reflect};
use keyboard::{keyboard_input_system, KeyCode, KeyboardInput, ScanCode};
use mouse::{
@ -33,9 +32,11 @@ use mouse::{
use touch::{touch_screen_input_system, ForceTouch, TouchInput, TouchPhase, Touches};
use gamepad::{
gamepad_connection_system, gamepad_event_system, AxisSettings, ButtonAxisSettings,
ButtonSettings, Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType,
GamepadEvent, GamepadEventRaw, GamepadEventType, GamepadSettings, Gamepads,
gamepad_axis_event_system, gamepad_button_event_system, gamepad_connection_system,
gamepad_event_system, AxisSettings, ButtonAxisSettings, ButtonSettings, Gamepad, GamepadAxis,
GamepadAxisChangedEvent, GamepadAxisType, GamepadButton, GamepadButtonChangedEvent,
GamepadButtonType, GamepadConnection, GamepadConnectionEvent, GamepadEvent, GamepadSettings,
Gamepads,
};
#[cfg(feature = "serialize")]
@ -69,20 +70,23 @@ impl Plugin for InputPlugin {
mouse_button_input_system.label(InputSystem),
)
// gamepad
.add_event::<GamepadConnectionEvent>()
.add_event::<GamepadButtonChangedEvent>()
.add_event::<GamepadAxisChangedEvent>()
.add_event::<GamepadEvent>()
.add_event::<GamepadEventRaw>()
.init_resource::<GamepadSettings>()
.init_resource::<Gamepads>()
.init_resource::<Input<GamepadButton>>()
.init_resource::<Axis<GamepadAxis>>()
.init_resource::<Axis<GamepadButton>>()
.add_system_to_stage(
.add_system_set_to_stage(
CoreStage::PreUpdate,
gamepad_event_system.label(InputSystem),
)
.add_system_to_stage(
CoreStage::PreUpdate,
gamepad_connection_system.after(InputSystem),
SystemSet::new()
.with_system(gamepad_event_system)
.with_system(gamepad_button_event_system.after(gamepad_event_system))
.with_system(gamepad_axis_event_system.after(gamepad_event_system))
.with_system(gamepad_connection_system.after(gamepad_event_system))
.label(InputSystem),
)
// touch
.add_event::<TouchInput>()
@ -114,9 +118,7 @@ impl Plugin for InputPlugin {
// Register gamepad types
app.register_type::<Gamepad>()
.register_type::<GamepadEventType>()
.register_type::<GamepadEvent>()
.register_type::<GamepadEventRaw>()
.register_type::<GamepadConnection>()
.register_type::<GamepadButtonType>()
.register_type::<GamepadButton>()
.register_type::<GamepadAxisType>()

View file

@ -1,7 +1,9 @@
//! Iterates and prints gamepad input and connection events.
use bevy::{
input::gamepad::{GamepadEvent, GamepadEventType},
input::gamepad::{
GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnectionEvent, GamepadEvent,
},
prelude::*,
};
@ -9,30 +11,41 @@ fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_system(gamepad_events)
.add_system(gamepad_ordered_events)
.run();
}
fn gamepad_events(mut gamepad_event: EventReader<GamepadEvent>) {
for event in gamepad_event.iter() {
match event.event_type {
GamepadEventType::Connected(_) => {
info!("{:?} Connected", event.gamepad);
}
GamepadEventType::Disconnected => {
info!("{:?} Disconnected", event.gamepad);
}
GamepadEventType::ButtonChanged(button_type, value) => {
info!(
"{:?} of {:?} is changed to {}",
button_type, event.gamepad, value
);
}
GamepadEventType::AxisChanged(axis_type, value) => {
info!(
"{:?} of {:?} is changed to {}",
axis_type, event.gamepad, value
);
}
fn gamepad_events(
mut gamepad_connection_events: EventReader<GamepadConnectionEvent>,
mut gamepad_axis_events: EventReader<GamepadAxisChangedEvent>,
mut gamepad_button_events: EventReader<GamepadButtonChangedEvent>,
) {
for connection_event in gamepad_connection_events.iter() {
info!("{:?}", connection_event);
}
for axis_event in gamepad_axis_events.iter() {
info!(
"{:?} of {:?} is changed to {}",
axis_event.axis_type, axis_event.gamepad, axis_event.value
);
}
for button_event in gamepad_button_events.iter() {
info!(
"{:?} of {:?} is changed to {}",
button_event.button_type, button_event.gamepad, button_event.value
);
}
}
// If you require in-frame relative event ordering, you can also read the `Gamepad` event
// stream directly. For standard use-cases, reading the events individually or using the
// `Input<T>` or `Axis<T>` resources is preferable.
fn gamepad_ordered_events(mut gamepad_events: EventReader<GamepadEvent>) {
for gamepad_event in gamepad_events.iter() {
match gamepad_event {
GamepadEvent::Connection(connection_event) => info!("{:?}", connection_event),
GamepadEvent::Button(button_event) => info!("{:?}", button_event),
GamepadEvent::Axis(axis_event) => info!("{:?}", axis_event),
}
}
}

View file

@ -3,7 +3,9 @@
use std::f32::consts::PI;
use bevy::{
input::gamepad::{GamepadButton, GamepadSettings},
input::gamepad::{
GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, GamepadSettings,
},
prelude::*,
sprite::{MaterialMesh2dBundle, Mesh2dHandle},
};
@ -468,42 +470,40 @@ fn update_buttons(
}
fn update_button_values(
mut events: EventReader<GamepadEvent>,
mut events: EventReader<GamepadButtonChangedEvent>,
mut query: Query<(&mut Text, &TextWithButtonValue)>,
) {
for event in events.iter() {
if let GamepadEventType::ButtonChanged(button_type, value) = event.event_type {
for (mut text, text_with_button_value) in query.iter_mut() {
if button_type == **text_with_button_value {
text.sections[0].value = format!("{value:.3}");
}
for button_event in events.iter() {
for (mut text, text_with_button_value) in query.iter_mut() {
if button_event.button_type == **text_with_button_value {
text.sections[0].value = format!("{:.3}", button_event.value);
}
}
}
}
fn update_axes(
mut events: EventReader<GamepadEvent>,
mut axis_events: EventReader<GamepadAxisChangedEvent>,
mut query: Query<(&mut Transform, &MoveWithAxes)>,
mut text_query: Query<(&mut Text, &TextWithAxes)>,
) {
for event in events.iter() {
if let GamepadEventType::AxisChanged(axis_type, value) = event.event_type {
for (mut transform, move_with) in query.iter_mut() {
if axis_type == move_with.x_axis {
transform.translation.x = value * move_with.scale;
}
if axis_type == move_with.y_axis {
transform.translation.y = value * move_with.scale;
}
for axis_event in axis_events.iter() {
let axis_type = axis_event.axis_type;
let value = axis_event.value;
for (mut transform, move_with) in query.iter_mut() {
if axis_type == move_with.x_axis {
transform.translation.x = value * move_with.scale;
}
for (mut text, text_with_axes) in text_query.iter_mut() {
if axis_type == text_with_axes.x_axis {
text.sections[0].value = format!("{value:.3}");
}
if axis_type == text_with_axes.y_axis {
text.sections[2].value = format!("{value:.3}");
}
if axis_type == move_with.y_axis {
transform.translation.y = value * move_with.scale;
}
}
for (mut text, text_with_axes) in text_query.iter_mut() {
if axis_type == text_with_axes.x_axis {
text.sections[0].value = format!("{value:.3}");
}
if axis_type == text_with_axes.y_axis {
text.sections[2].value = format!("{value:.3}");
}
}
}