feat: update to bevy 13.2; rework input collection system

This commit is contained in:
2024-04-05 12:56:32 +02:00
parent 1812be6ae3
commit 7e00ce3fda
12 changed files with 1324 additions and 919 deletions
+4 -4
View File
@@ -11,10 +11,10 @@ inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
[dependencies]
bevy-inspector-egui = { version = "0.22.1", optional = true }
bevy_app = "0.12.1"
bevy_derive = "0.12.1"
bevy_ecs = "0.12.1"
bevy_input = "0.12.1"
bevy_app = "0.13.2"
bevy_derive = "0.13.2"
bevy_ecs = "0.13.2"
bevy_input = "0.13.2"
bevy_reflect = { version = "0.12.1", optional = true }
bevy_utils = "0.12.1"
log = "0.4.20"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -12,7 +12,7 @@ wayland = ["bevy/wayland"]
windows = ["bevy/bevy_winit"]
[dependencies]
bevy = { version = "0.12.1", default-features = false, features = [
bevy = { version = "0.13.2", default-features = false, features = [
"bevy_asset",
"bevy_text",
"bevy_ui",
+9 -9
View File
@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1707092692,
"narHash": "sha256-ZbHsm+mGk/izkWtT4xwwqz38fdlwu7nUUKXTOmm4SyE=",
"lastModified": 1712163089,
"narHash": "sha256-Um+8kTIrC19vD4/lUCN9/cU9kcOsD1O1m+axJqQPyMM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "faf912b086576fd1a15fca610166c98d47bc667e",
"rev": "fd281bd6b7d3e32ddfa399853946f782553163b5",
"type": "github"
},
"original": {
@@ -51,11 +51,11 @@
]
},
"locked": {
"lastModified": 1707358215,
"narHash": "sha256-Nuhi8KEJ2e+2nTimSyEIPqN5eh7ECVWd+AnPXG6L+SY=",
"lastModified": 1712196778,
"narHash": "sha256-SOiwCr2HtmYpw8OvQQVRPtiCBWwndbIoPqtsamZK3J8=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "dd917bb1b67fc049fd56fe6de70266a9ab74a4aa",
"rev": "20e7895d1873cc64c14a9f024a8e04f5824bed28",
"type": "github"
},
"original": {
@@ -1,2 +1,2 @@
[toolchain]
channel = '1.72.0'
channel = '1.76.0'
+21 -31
View File
@@ -11,7 +11,7 @@ use bevy_controls::{
plugin::ControlsPlugin,
resource::{
ActivationMode, ActivationOptions, Binding, BindingCondition, BindingConfig, Bindings,
ButtonCombination, Controls, InputType, InputValue, Keyboard, OptionsMode, PlayerInputs,
ButtonCombination, Controls, InputType, InputValue, OptionsMode, PlayerActions,
},
};
use bevy_controls_derive::{Action, GameState};
@@ -37,19 +37,19 @@ enum MyGameState {
#[derive(Resource, Default, Clone, Debug)]
struct MyInputsContainer {
// When the game does not provide multiplayer, one field is enough
player_inputs: PlayerInputs<MyAction>,
player_inputs: PlayerActions<MyAction>,
}
impl InputsContainer<MyAction> for MyInputsContainer {
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerInputs<MyAction>> + 'a> {
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerActions<MyAction>> + 'a> {
todo!()
}
fn me<'a>(&'a self) -> Option<&'a PlayerInputs<MyAction>> {
Some(&self.player_inputs)
fn me<'a>(&'a self) -> Option<&'a PlayerActions<MyAction>> {
Some(&self.player_inputs)
}
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerInputs<MyAction>> {
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerActions<MyAction>> {
Some(&mut self.player_inputs)
}
}
@@ -78,12 +78,7 @@ fn main() {
BindingConfig::new(Bindings::new(vec![
// this way input will work only on latin layout only on `A`, because it uses `Keyboard::KeyCode`
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::KeyCode(KeyCode::A),
))),
// this way input will work only on any layout on same button, 35 is `H` for `us` layout or
// `О` for `ru` layout, because it uses `Keyboard::ScanCode`
Binding::new(ButtonCombination::Single(InputType::Keyboard(
Keyboard::ScanCode(ScanCode(35)),
KeyCode::KeyA,
)))
.with_condition(BindingCondition::InGameState(MyGameState::InGame)),
]))
@@ -94,37 +89,32 @@ fn main() {
// the rest will be simplified
.with(
MyAction::Back,
BindingConfig::from_vec(vec![
Binding::from_single(InputType::from_key_code(KeyCode::S)),
Binding::from_single(InputType::from_scan_code(ScanCode(36))),
]),
BindingConfig::from_vec(vec![Binding::from_single(InputType::Keyboard(
KeyCode::KeyS,
))]),
)
.with(
MyAction::Forward,
BindingConfig::from_vec(vec![
Binding::from_single(InputType::from_key_code(KeyCode::W)),
Binding::from_single(InputType::from_scan_code(ScanCode(37))),
]),
BindingConfig::from_vec(vec![Binding::from_single(InputType::Keyboard(
KeyCode::KeyW,
))]),
)
.with(
MyAction::Right,
BindingConfig::from_vec(vec![
Binding::from_single(InputType::from_key_code(KeyCode::D)),
Binding::from_single(InputType::from_scan_code(ScanCode(38))),
]),
BindingConfig::from_vec(vec![Binding::from_single(InputType::Keyboard(
KeyCode::KeyD,
))]),
)
.with(
MyAction::Up,
BindingConfig::from_bind(Binding::from_single(InputType::from_key_code(
KeyCode::Space,
))),
BindingConfig::from_bind(Binding::from_single(InputType::Keyboard(KeyCode::Space))),
)
.with(
MyAction::Down,
BindingConfig::from_bind(Binding::from_chord(vec![
InputType::from_key_code(KeyCode::ShiftLeft),
InputType::from_key_code(KeyCode::Space),
])),
BindingConfig::from_bind(Binding::from_single(
InputType::Keyboard(KeyCode::ShiftLeft),
//InputType::Keyboard(KeyCode::Space),
)),
)
.build(),
),
+5 -82
View File
@@ -6,13 +6,13 @@ use bevy_inspector_egui::inspector_options::InspectorOptionsType;
use strum::IntoEnumIterator;
// #[cfg(all(feature = "reflect", feature = "serialize"))]
// use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use bevy_controls_macros::{cfg_feature_trait_bounds, supertraits};
use bevy_controls_macros::supertraits;
#[cfg(feature = "reflect")]
use bevy_reflect::{FromReflect, Reflect, TypePath};
#[cfg(feature = "serialize")]
use serde::{de::DeserializeOwned, Serialize};
use crate::resource::PlayerInputs;
use crate::resource::PlayerActions;
// ===== Action =====
// SAFETY: inspector-egui includes reflect
@@ -52,86 +52,9 @@ pub trait InputsContainer<A: Action>:
{
/// The method returning an iterator over references to the PlayerInputs.
/// Note: the use of 'a for both the method's lifetime and the returned Iterator's lifetime.
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerInputs<A>> + 'a>;
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerActions<A>> + 'a>;
/// The method returning an player inputs of this client.
fn me<'a>(&'a self) -> Option<&'a PlayerInputs<A>>;
fn me<'a>(&'a self) -> Option<&'a PlayerActions<A>>;
/// The method returning an mutable player inputs of this client.
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerInputs<A>>;
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerActions<A>>;
}
// #[cfg(test)]
// mod test {
// use bevy_ecs::{entity::Entity, schedule::States};
// use bevy_utils::{HashMap, prelude::default};
// use strum_macros::EnumIter;
// #[cfg(feature = "serialize")]
// use serde::{Serialize, Deserialize};
// use crate::{hashmap, resource::PlayerInputs, common_traits_conditions};
// common_traits_conditions! {
// #[derive(Debug, Clone, PartialEq)]
// #[cfg_attr(feature = "reflect", reflect(Resource))]
// pub struct Lobby {
// pub me: PlayerId,
// pub players: HashMap<PlayerId, Player>,
// }
// }
// impl Default for Lobby {
// fn default() -> Self {
// Self {
// me: PlayerId::Host,
// players: hashmap! {
// PlayerId::Host => Player::default()
// },
// }
// }
// }
// common_traits_conditions! {
// #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
// pub enum PlayerId {
// /// Host or alone
// Host,
// /// Client
// Client(()),
// }
// #[derive(Default, Debug, PartialEq, Clone)]
// pub struct Player {
// /// Client do not need to know about other clients
// #[cfg_attr(feature = "serialize", serde(skip))]
// pub inputs: PlayerInputs<Action>,
// #[cfg_attr(feature = "serialize", serde(skip))]
// pub entity: Option<Entity>,
// }
// }
// #[derive(Debug, PartialEq, Clone, States, Default, Hash, Eq)]
// pub enum GameState {
// #[default]
// LevelEditor,
// InGame,
// }
// impl super::GameState for GameState {}
// // TODO: it is just plug, it must be a trait
// /// Actions that can be performed by player
// #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, EnumIter)]
// pub enum Action {
// /// Move forward
// LeverEditorForward,
// /// Move backward
// LevelEditorBackward,
// /// Move left
// LevelEditorLeft,
// /// Move right
// LevelEditorRight,
// LevelEditorFly,
// }
// impl super::Action for Action {}
// }
+116 -81
View File
@@ -1,13 +1,18 @@
use bevy_app::{App, Plugin, PreUpdate, Update};
use bevy_app::{App, Plugin, PreUpdate};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::schedule::IntoSystemConfigs;
use bevy_ecs::system::{In, IntoSystem};
use bevy_ecs::{
event::EventReader,
schedule::State,
system::{Res, ResMut},
system::{Res, ResMut, Resource},
};
use bevy_input::{
keyboard::{KeyCode, ScanCode},
mouse::MouseButton,
Input,
keyboard::KeyCode,
mouse::{MouseButton, MouseMotion, MouseScrollUnit, MouseWheel},
ButtonInput,
};
use bevy_utils::HashMap;
use crate::{
contract::{Action, GameState, InputsContainer},
@@ -43,13 +48,20 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
}
}
#[derive(Resource, Default, Debug, Deref, DerefMut)]
struct TrigeredInputs(HashMap<InputType, InputValue>);
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin<A, Ic, Gs> {
fn build(&self, app: &mut App) {
app
.insert_resource(self.controls.clone())
.init_resource::<Ic>()
.add_state::<Gs>()
.add_systems(PreUpdate, Self::collect_inputs);
.init_resource::<TrigeredInputs>()
.insert_state::<Gs>(Gs::default())
.add_systems(
PreUpdate,
Self::collect_inputs.pipe(Self::fill_players_inputs),
);
#[cfg(feature = "reflect")]
app.register_type::<Controls<A, Gs>>();
@@ -57,83 +69,106 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin
}
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs> {
// Separate collecting and filling is necessary becouse order of complicate imputs in future
fn collect_inputs(
keyboard_input_keycode: Res<Input<KeyCode>>,
keyboard_input_scancode: Res<Input<ScanCode>>,
) {
let a: Vec<(InputType, InputValue)> = vec![];
for key in keyboard_input_keycode.get_pressed() {
keyboard_button: Res<ButtonInput<KeyCode>>,
mouse_buttons: Res<ButtonInput<MouseButton>>,
mut scroll_evr: EventReader<MouseWheel>,
mut motion_evr: EventReader<MouseMotion>,
// mut collected_inputs: ResMut<TrigeredInputs>,
) -> TrigeredInputs {
let mut collected_inputs = TrigeredInputs(HashMap::new());
for key in keyboard_button.get_pressed() {
collected_inputs.insert(InputType::Keyboard(*key), InputValue::Boolean(true));
}
// buttons.get_just_released()
for button in mouse_buttons.get_pressed() {
collected_inputs.insert(
InputType::Mouse(MouseInput::Button(*button)),
InputValue::Boolean(true),
);
}
for ev in motion_evr.read() {
collected_inputs.insert(
InputType::Mouse(MouseInput::Axis(AxisName::Horizontal)),
InputValue::Float(ev.delta.x),
);
collected_inputs.insert(
InputType::Mouse(MouseInput::Axis(AxisName::Vertical)),
InputValue::Float(ev.delta.y),
);
}
for ev in scroll_evr.read() {
match ev.unit {
MouseScrollUnit::Line => {
collected_inputs.insert(
InputType::Mouse(MouseInput::Wheel(AxisName::Horizontal)),
InputValue::Float(ev.x),
);
collected_inputs.insert(
InputType::Mouse(MouseInput::Wheel(AxisName::Vertical)),
InputValue::Float(ev.y),
);
}
MouseScrollUnit::Pixel => {
panic!("I'm do not accept you to use touch pad in my game");
}
}
}
collected_inputs
}
fn fill_players_inputs() {}
// TODO: ! Zero out values that did not arrive
fn fill_players_inputs(
In(collected_inputs): In<TrigeredInputs>,
mut inputs_container: ResMut<Ic>,
controls: Res<Controls<A, Gs>>,
game_state: Res<State<Gs>>,
) {
if let Some(inputs) = inputs_container.me_mut() {
// TODO: only on controls change
let mut action_binding_pairs: Vec<(&A, &BindingConfig<Gs>, &Binding<Gs>)> = Vec::new();
for (action, config) in controls.iter() {
for binding in config.bindings.iter() {
action_binding_pairs.push((action, config, binding));
}
}
// /// Process all hard inputs and bindings to update [`PlayerInputs`]
// fn save_input(
// keyboard_input_keycode: Res<Input<KeyCode>>,
// keyboard_input_scancode: Res<Input<ScanCode>>,
// mouse_input: Res<Input<MouseButton>>,
// mut inputs_container: ResMut<Ic>,
// controls: Res<Controls<A, Gs>>,
// game_state: Res<State<Gs>>,
// ) {
// // get current client inputs
// if let Some(inputs) = inputs_container.me_mut() {
// for (action, config) in controls.iter() {
// 'bindings_loop: for binding in config.bindings.iter() {
// // check binding conditions
// for condition in &binding.conditions {
// match condition {
// BindingCondition::InGameState(state) => {
// if *state != *game_state.get() {
// continue 'bindings_loop;
// }
// }
// }
// }
//
// log::trace!("action binding {:?} {:?} in condition", action, binding);
//
// // check inputs & mark actions
// match &binding.input {
// ButtonCombination::Single(button) => match button {
// InputType::Keyboard(Keyboard::KeyCode(key)) => {
// if keyboard_input_keycode.pressed(*key) {
// inputs.forced_set(*action, true);
// break; // when on binding trigered no sence check another
// }
// inputs.forced_set(*action, false); // FIXME: this is happening on every pass so maybe too frequently
// }
// InputType::Keyboard(Keyboard::ScanCode(code)) => {
// log::trace!("presed?: {:?}", keyboard_input_scancode.pressed(*code));
// log::trace!("active");
// if keyboard_input_scancode.pressed(*code) {
// inputs.forced_set(*action, true);
// break; // when on binding trigered no sence check another
// }
// inputs.forced_set(*action, false); // FIXME: this is happening on every pass so maybe too frequently
// }
// InputType::Mouse(input) => match input {
// MouseInput::Axis(_axis) => {
// todo!();
// }
// MouseInput::Button(button) => {
// if !mouse_input.get_pressed().any(|b| b == button) {
// continue 'bindings_loop;
// }
// }
// MouseInput::Wheel(_axis) => {
// todo!();
// }
// },
// },
// }
// }
// }
// } else {
// log::error!("cannot find me in inputs container")
// }
// }
action_binding_pairs.sort_by(|a, b| b.2.input.len().cmp(&a.2.input.len()));
'bindings_loop: for (action, config, binding) in action_binding_pairs.iter() {
// check binding conditions
for condition in &binding.conditions {
match condition {
BindingCondition::InGameState(state) => {
if *state != *game_state.get() {
continue 'bindings_loop;
}
}
}
}
//log::info!("{:#?}", collected_inputs);
//log::info!("{:#?}", action_binding_pairs[0]);
match &binding.input {
ButtonCombination::Single(input_type) => {
if collected_inputs.contains_key(&*input_type) {
inputs.forced_set(**action, true);
} else {
inputs.forced_set(**action, false);
}
}
}
}
} else {
// TODO: print onece
log::warn!("cannot find me in inputs container");
}
}
}
+19 -22
View File
@@ -3,7 +3,7 @@ use std::error;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::system::Resource;
use bevy_input::{
keyboard::{KeyCode, ScanCode},
keyboard::KeyCode,
mouse::MouseButton,
};
#[cfg(feature = "inspector-egui")]
@@ -200,42 +200,27 @@ impl<A: Action> PlayerActions<A> {
}
common_traits_conditions! {
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum InputType {
Keyboard(Keyboard),
Keyboard(KeyCode),
Mouse(MouseInput),
// TODO: Gamepad(GamepadButtonType),
// TODO: Touch screen
// TODO: TouchPad inputs
}
}
impl InputType {
pub fn from_key_code(key_code: KeyCode) -> Self {
InputType::Keyboard(Keyboard::KeyCode(key_code))
}
pub fn from_scan_code(scan_code: ScanCode) -> Self {
InputType::Keyboard(Keyboard::ScanCode(scan_code))
}
}
common_traits_conditions! {
#[derive(Debug, PartialEq, Clone)]
pub enum Keyboard {
KeyCode(KeyCode),
ScanCode(ScanCode),
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum MouseInput {
Button(MouseButton),
Axis(AxisName),
Wheel(AxisName)
// TODO: CursorPosition
// TODO: TouchPad inputs
}
/// Name of axis
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub enum AxisName {
Horizontal,
Vertical,
@@ -311,7 +296,19 @@ common_traits_conditions! {
// /// Chord is a combination of buttons that must be pressed at the same time
// Chord(Vec<InputType>),
}
}
impl ButtonCombination {
pub fn len(&self) -> usize {
use crate::resource::ButtonCombination::*;
match self {
Single(_) => 1
// TODO: Chord
}
}
}
common_traits_conditions! {
#[derive(Debug, PartialEq, Clone)]
pub enum BindingCondition<Gs: GameState> {
/// During specific game state
@@ -572,7 +569,7 @@ impl<A: Action, Gs: GameState> ControlsBuilder<A, Gs> {
// or make sure that you have only one Menufor<'a> Action<'a> with the same name in the Menufor<'a> Action<'a>s
assert!(validate_hash_map(&**self));
std::mem::take(&mut self)
std::mem::take(&mut self) // TODO
}
}