Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d80bd8b03a | |||
| a75fb650e3 | |||
| a156490d4d | |||
| f4268d7bef | |||
| b4e480954f | |||
| 22333c0c8c | |||
| 9a32598eb5 | |||
| 11c5ff2aff | |||
| 98d7b52546 | |||
| f063db3568 | |||
| e6c9e65417 | |||
| 815891ed33 | |||
| 1defbfc1c9 | |||
| 366d11453b | |||
| 9cd99b898b | |||
| b332347f85 | |||
| 58573dbc1c | |||
| 0f094a0f8c | |||
| 8aae841930 | |||
| 7e00ce3fda | |||
| 1812be6ae3 | |||
| 234e086a77 | |||
| e1e762395f | |||
| c651f37759 | |||
| 6246ec0dd8 | |||
| 4888e60ccf | |||
| 55feb6e67f | |||
| 43be3da536 | |||
| 25b3580384 | |||
| 05b9c0b9cd | |||
| e833546669 |
+1899
-1730
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
|||||||
|
# v0.1.0
|
||||||
|
- [ ] debug log for trigered actions
|
||||||
|
- [ ] core
|
||||||
|
- [x] single keys
|
||||||
|
- [ ] chords
|
||||||
|
- [x] contracts
|
||||||
|
- [x] loby
|
||||||
|
- [x] actions
|
||||||
|
- [x] game state
|
||||||
|
- [ ] serialization & deserialization with ignoring imutable values
|
||||||
|
- [x] way to define default controlls
|
||||||
|
- [ ] layers for controlls: if on (shift + space) action enable, ignore on (space) action
|
||||||
|
- [ ] warnings on actions collision
|
||||||
|
- [ ] examples
|
||||||
|
- [ ] basic example
|
||||||
|
- [ ] serialization example
|
||||||
|
- [ ] action options work
|
||||||
|
- [ ] deley
|
||||||
|
- [ ] tap / hold
|
||||||
|
|
||||||
|
# v0.2.0
|
||||||
|
- [ ] layers for controlls: user posibility give priority to controls
|
||||||
|
- [ ] controller support
|
||||||
|
- [ ] touch pad support
|
||||||
|
- [ ] posibility support more that one game state
|
||||||
Regular → Executable
+11
-9
@@ -5,22 +5,24 @@ edition = "2021"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
logical-keyboard = []
|
||||||
serialize = ["bevy_input/serialize", "serde"]
|
serialize = ["bevy_input/serialize", "serde"]
|
||||||
reflect = ["dep:bevy_reflect"]
|
reflect = ["dep:bevy_reflect"]
|
||||||
inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
|
inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy-inspector-egui = { version = "0.22.1", optional = true }
|
bevy-inspector-egui = { version = "0.37", optional = true, default-features = false }
|
||||||
bevy_app = "0.12.1"
|
bevy_app = "0.19.1"
|
||||||
bevy_derive = "0.12.1"
|
bevy_derive = "0.19.1"
|
||||||
bevy_ecs = "0.12.1"
|
bevy_ecs = "0.19.1"
|
||||||
bevy_input = "0.12.1"
|
bevy_input = { version = "0.19.1", features = ["keyboard", "mouse"] }
|
||||||
bevy_reflect = { version = "0.12.1", optional = true }
|
bevy_state = "0.19.1"
|
||||||
bevy_utils = "0.12.1"
|
bevy_platform = "0.19.1"
|
||||||
|
bevy_reflect = { version = "0.19.1", features = ["auto_register_static"], optional = true }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
serde = { version = "1.0.194", optional = true }
|
serde = { version = "1.0.194", optional = true }
|
||||||
strum = "0.25.0"
|
strum = "0.26.2"
|
||||||
strum_macros = "0.25.3"
|
strum_macros = "0.26.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.10.1"
|
env_logger = "0.10.1"
|
||||||
|
|||||||
+3145
-1429
File diff suppressed because it is too large
Load Diff
Regular → Executable
+18
-7
@@ -7,19 +7,30 @@ edition = "2021"
|
|||||||
target-dir = "../../../../target"
|
target-dir = "../../../../target"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
x11 = ["bevy/x11", "bevy/bevy_winit"]
|
x11 = ["bevy/x11"]
|
||||||
wayland = ["bevy/wayland", "bevy/bevy_winit"]
|
wayland = ["bevy/wayland"]
|
||||||
windows = ["bevy/bevy_winit"]
|
windows = ["bevy/bevy_winit"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = { version = "0.12.1", default-features = false, features = [
|
bevy = { version = "0.19.1", default-features = false, features = [
|
||||||
|
"std",
|
||||||
|
"default_app",
|
||||||
"bevy_asset",
|
"bevy_asset",
|
||||||
"bevy_text",
|
"bevy_text",
|
||||||
|
"bevy_state",
|
||||||
"bevy_ui",
|
"bevy_ui",
|
||||||
"dynamic_linking",
|
"bevy_ui_render",
|
||||||
|
"bevy_window",
|
||||||
|
"bevy_winit",
|
||||||
"default_font",
|
"default_font",
|
||||||
]}
|
]}
|
||||||
bevy_controls = { path = "./../../" }
|
bevy_reflect = { version = "0.19.1", features = ["auto_register_static"] }
|
||||||
|
bevy_controls = { path = "./../../", features = [
|
||||||
|
"logical-keyboard",
|
||||||
|
]}
|
||||||
bevy_controls_derive = { path = "./../../../bevy_controls_derive" }
|
bevy_controls_derive = { path = "./../../../bevy_controls_derive" }
|
||||||
strum = "0.25.0"
|
strum_macros = "0.26.2"
|
||||||
strum_macros = "0.25.3"
|
strum = "0.26.2"
|
||||||
|
|
||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
linker = "rust-lld.exe"
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Basic example
|
||||||
|
|
||||||
|
to run
|
||||||
|
```bash
|
||||||
|
cargo run --features '<x11|wayland|windows>'
|
||||||
|
```
|
||||||
+9
-12
@@ -5,11 +5,11 @@
|
|||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1705309234,
|
"lastModified": 1710146030,
|
||||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1707092692,
|
"lastModified": 1788316716,
|
||||||
"narHash": "sha256-ZbHsm+mGk/izkWtT4xwwqz38fdlwu7nUUKXTOmm4SyE=",
|
"narHash": "sha256-bc7rSpXIdn9QWGNqfWcPZWOhEVF8NoeAZkWq0XWnf/k=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "faf912b086576fd1a15fca610166c98d47bc667e",
|
"rev": "3ed67ec0a4d3c7ab4ae1f04f8ee8df07bfa506a2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -43,19 +43,16 @@
|
|||||||
},
|
},
|
||||||
"rust-overlay": {
|
"rust-overlay": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": [
|
|
||||||
"flake-utils"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1707358215,
|
"lastModified": 1788456718,
|
||||||
"narHash": "sha256-Nuhi8KEJ2e+2nTimSyEIPqN5eh7ECVWd+AnPXG6L+SY=",
|
"narHash": "sha256-TxHC0sBjV2pmsBLKAX02JTXlye/EOLt6Do+WNtGofGw=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "dd917bb1b67fc049fd56fe6de70266a9ab74a4aa",
|
"rev": "0b20a18e84d9164464739189c2b2a1b00f6f0e4b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
url = "github:oxalica/rust-overlay";
|
url = "github:oxalica/rust-overlay";
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.follows = "nixpkgs";
|
nixpkgs.follows = "nixpkgs";
|
||||||
flake-utils.follows = "flake-utils";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -27,10 +26,10 @@
|
|||||||
|
|
||||||
vulkan-loader
|
vulkan-loader
|
||||||
|
|
||||||
xorg.libX11
|
libx11
|
||||||
xorg.libXrandr
|
libxrandr
|
||||||
xorg.libXcursor
|
libxcursor
|
||||||
xorg.libXi
|
libxi
|
||||||
|
|
||||||
wayland
|
wayland
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
|
|||||||
Regular → Executable
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = '1.72.0'
|
channel = '1.95.0'
|
||||||
|
|||||||
Regular → Executable
+186
-158
@@ -2,55 +2,60 @@
|
|||||||
//!
|
//!
|
||||||
//! It displays
|
//! It displays
|
||||||
//!
|
//!
|
||||||
//! Yeah I know, now it's a bit ugly
|
//! Yeah I know, that's kind of weird that it displays
|
||||||
|
//! But it does.........
|
||||||
|
|
||||||
use bevy::{ecs::schedule::States, prelude::*};
|
// TODO strum redefine in bevy_controls
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::input::keyboard::Key;
|
||||||
use bevy_controls::{
|
use bevy_controls::{
|
||||||
contract::{
|
contract::InputsContainer,
|
||||||
Action, ActionInner, GameState, GameStateInner, InputsContainer, InputsContainerInner,
|
|
||||||
},
|
|
||||||
plugin::ControlsPlugin,
|
plugin::ControlsPlugin,
|
||||||
resource::{Binding, ButtonCombination, Controls, InputType, InputValue, Keyboard, PlayerInputs},
|
resource::{
|
||||||
|
ActivationMode, ActivationOptions, AxisName, Binding, BindingCondition, BindingConfig,
|
||||||
|
Bindings, ButtonCombination, Controls, InputType, InputValue, Keyboard, MouseInput, OptionsMode,
|
||||||
|
PlayerActions,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
use bevy_controls_derive::{Action, GameState};
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy, Debug, bevy_controls_derive::Action)]
|
#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy, Debug, Action)]
|
||||||
enum MyAction {
|
enum MyAction {
|
||||||
Forward,
|
Forward,
|
||||||
Back,
|
Back,
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Up,
|
Up,
|
||||||
|
Down,
|
||||||
|
MouseHorizontal,
|
||||||
|
MouseVertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default)]
|
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default, GameState)]
|
||||||
enum MyGameState {
|
enum MyGameState {
|
||||||
#[default]
|
#[default]
|
||||||
InGame,
|
InGame,
|
||||||
|
Menu,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameState for MyGameState {}
|
|
||||||
|
|
||||||
impl GameStateInner for MyGameState {}
|
|
||||||
|
|
||||||
#[derive(Resource, Default, Clone, Debug)]
|
#[derive(Resource, Default, Clone, Debug)]
|
||||||
struct MyInputsContainer {
|
struct MyInputsContainer {
|
||||||
// When the game does not provide multiplayer, one field is enough
|
// When the game does not provide multiplayer, one field is enough
|
||||||
player_inputs: PlayerInputs<MyAction>,
|
player_inputs: PlayerActions<MyAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputsContainer<MyAction> for MyInputsContainer {}
|
impl InputsContainer<MyAction> for MyInputsContainer {
|
||||||
|
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerActions<MyAction>> + 'a> {
|
||||||
impl InputsContainerInner<MyAction> for MyInputsContainer {
|
|
||||||
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerInputs<MyAction>> + 'a> {
|
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn me<'a>(&'a self) -> Option<&'a PlayerInputs<MyAction>> {
|
fn me<'a>(&'a self) -> Option<&'a PlayerActions<MyAction>> {
|
||||||
Some(&self.player_inputs)
|
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)
|
Some(&mut self.player_inputs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,175 +66,198 @@ struct JumpsCount(i32);
|
|||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
DefaultPlugins,
|
DefaultPlugins.set(WindowPlugin {
|
||||||
ControlsPlugin::<MyAction, MyInputsContainer, MyGameState>::default(),
|
primary_window: Some(Window {
|
||||||
|
transparent: true,
|
||||||
|
decorations: false,
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
composite_alpha_mode: CompositeAlphaMode::PostMultiplied,
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
ControlsPlugin::<MyAction, MyInputsContainer, MyGameState>::new(
|
||||||
|
Controls::<MyAction, MyGameState>::new()
|
||||||
|
// the first control instance I will describe in detail
|
||||||
|
.with(
|
||||||
|
MyAction::Left,
|
||||||
|
BindingConfig::new(Bindings::new(vec![
|
||||||
|
// `KeyCode` binds physical key location, so this is the US-layout A position.
|
||||||
|
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
||||||
|
Keyboard::KeyCode(KeyCode::KeyA),
|
||||||
|
)))
|
||||||
|
.with_condition(BindingCondition::InGameState(MyGameState::InGame)),
|
||||||
|
// `Key` binds logical text, so this activates when the current layout produces h.
|
||||||
|
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
||||||
|
Keyboard::Key(Key::Character("h".into())),
|
||||||
|
)))
|
||||||
|
.with_condition(BindingCondition::InGameState(MyGameState::InGame)),
|
||||||
|
]))
|
||||||
|
.with_activation_options(
|
||||||
|
ActivationOptions::new(ActivationMode::Tap, OptionsMode::Immutable).with_delay(0.05),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// the rest will be simplified
|
||||||
|
.with(
|
||||||
|
MyAction::Back,
|
||||||
|
BindingConfig::from_vec(vec![
|
||||||
|
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyS))),
|
||||||
|
Binding::from_single(InputType::Keyboard(Keyboard::Key(Key::Character("j".into())))),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.with(
|
||||||
|
MyAction::Forward,
|
||||||
|
BindingConfig::from_vec(vec![
|
||||||
|
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyW))),
|
||||||
|
Binding::from_single(InputType::Keyboard(Keyboard::Key(Key::Character("k".into())))),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.with(
|
||||||
|
MyAction::Right,
|
||||||
|
BindingConfig::from_vec(vec![
|
||||||
|
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyD))),
|
||||||
|
Binding::from_single(InputType::Keyboard(Keyboard::Key(Key::Character("l".into())))),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.with(
|
||||||
|
MyAction::Up,
|
||||||
|
BindingConfig::from_bind(Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(
|
||||||
|
KeyCode::Space,
|
||||||
|
)))),
|
||||||
|
)
|
||||||
|
.with(
|
||||||
|
MyAction::MouseHorizontal,
|
||||||
|
BindingConfig::from_bind(Binding::from_single(InputType::Mouse(MouseInput::Axis(
|
||||||
|
AxisName::Horizontal,
|
||||||
|
)))),
|
||||||
|
)
|
||||||
|
.with(
|
||||||
|
MyAction::MouseVertical,
|
||||||
|
BindingConfig::from_bind(Binding::from_single(InputType::Mouse(MouseInput::Axis(
|
||||||
|
AxisName::Vertical,
|
||||||
|
)))),
|
||||||
|
)
|
||||||
|
.with(
|
||||||
|
MyAction::Down,
|
||||||
|
BindingConfig::from_bind(Binding::from_single(
|
||||||
|
InputType::Keyboard(Keyboard::KeyCode(KeyCode::ShiftLeft)),
|
||||||
|
//InputType::Keyboard(Keyboard::KeyCode(KeyCode::Space)),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
|
.insert_resource(ClearColor(Color::NONE))
|
||||||
.init_resource::<JumpsCount>()
|
.init_resource::<JumpsCount>()
|
||||||
.add_systems(Startup, (add_bindings, setup))
|
.add_systems(Startup, setup)
|
||||||
.add_systems(Update, text_update_system)
|
.add_systems(Update, text_update_system)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: default bingings...
|
#[derive(Component, Clone, Copy)]
|
||||||
fn add_bindings(mut controls: ResMut<Controls<MyAction, MyGameState>>) {
|
enum TextSpanKind {
|
||||||
controls.force_push(
|
MoveLeft,
|
||||||
MyAction::Left,
|
MoveBack,
|
||||||
// this way input will work only on latin layout only on `A`
|
MoveForward,
|
||||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
MoveRight,
|
||||||
Keyboard::KeyCode(KeyCode::A),
|
Jump,
|
||||||
))),
|
DeltaX,
|
||||||
);
|
DeltaY,
|
||||||
controls.force_push(
|
|
||||||
MyAction::Left,
|
|
||||||
// this way input will work only on any layout on same button, 35 is `H` for `us` layout or
|
|
||||||
// `О` for `ru` layout
|
|
||||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
|
||||||
Keyboard::ScanCode(ScanCode(35)),
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
|
|
||||||
controls.force_push(
|
|
||||||
MyAction::Back,
|
|
||||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
|
||||||
Keyboard::KeyCode(KeyCode::S),
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
controls.force_push(
|
|
||||||
MyAction::Back,
|
|
||||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
|
||||||
Keyboard::ScanCode(ScanCode(36)),
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
|
|
||||||
controls.force_push(
|
|
||||||
MyAction::Forward,
|
|
||||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
|
||||||
Keyboard::KeyCode(KeyCode::W),
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
controls.force_push(
|
|
||||||
MyAction::Forward,
|
|
||||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
|
||||||
Keyboard::ScanCode(ScanCode(37)),
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
|
|
||||||
controls.force_push(
|
|
||||||
MyAction::Right,
|
|
||||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
|
||||||
Keyboard::KeyCode(KeyCode::D),
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
controls.force_push(
|
|
||||||
MyAction::Right,
|
|
||||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
|
||||||
Keyboard::ScanCode(ScanCode(38)),
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
|
|
||||||
controls.force_push(
|
|
||||||
MyAction::Up,
|
|
||||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
|
||||||
Keyboard::KeyCode(KeyCode::Space),
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
struct TextBlock;
|
|
||||||
|
|
||||||
const TEXT_SIZE: f32 = 30.;
|
const TEXT_SIZE: f32 = 30.;
|
||||||
|
|
||||||
fn setup(mut commands: Commands) {
|
fn text_span(text: &str, kind: TextSpanKind) -> (TextSpan, TextFont, TextColor, TextSpanKind) {
|
||||||
commands.spawn(Camera2dBundle::default());
|
(
|
||||||
|
TextSpan::new(text),
|
||||||
commands.spawn((
|
TextFont {
|
||||||
TextBundle::from_sections([
|
font_size: FontSize::Px(TEXT_SIZE),
|
||||||
TextSection::new(
|
|
||||||
"\nAction: ",
|
|
||||||
TextStyle {
|
|
||||||
font_size: TEXT_SIZE,
|
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
),
|
TextColor(Color::WHITE),
|
||||||
TextSection::new(
|
kind,
|
||||||
"Left (H/A) ",
|
)
|
||||||
TextStyle {
|
|
||||||
font_size: TEXT_SIZE,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextSection::new(
|
|
||||||
"Back (J/S) ",
|
|
||||||
TextStyle {
|
|
||||||
font_size: TEXT_SIZE,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextSection::new(
|
|
||||||
"Forward (K/W) ",
|
|
||||||
TextStyle {
|
|
||||||
font_size: TEXT_SIZE,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextSection::new(
|
|
||||||
"Right (L/D) ",
|
|
||||||
TextStyle {
|
|
||||||
font_size: TEXT_SIZE,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TextSection::new(
|
|
||||||
"Up (Space)",
|
|
||||||
TextStyle {
|
|
||||||
font_size: TEXT_SIZE,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
TextBlock,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// text sections indexes
|
fn setup(mut commands: Commands) {
|
||||||
const MOVE_LEFT_INDEX: usize = 1;
|
commands.spawn(Camera2d);
|
||||||
const MOVE_BACK_INDEX: usize = 2;
|
|
||||||
const MOVE_FORWARD_INDEX: usize = 3;
|
commands
|
||||||
const MOVE_RIGHT_INDEX: usize = 4;
|
.spawn((
|
||||||
const JUMP_INDEX: usize = 5;
|
Text::new(
|
||||||
|
"Info: Important that WASD use physical key positions and HJKL use logical characters from current layout\nAction: ",
|
||||||
|
),
|
||||||
|
TextFont {
|
||||||
|
font_size: FontSize::Px(TEXT_SIZE),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
TextColor(Color::WHITE),
|
||||||
|
BackgroundColor(Color::BLACK),
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
parent.spawn(text_span("Left (H/A) ", TextSpanKind::MoveLeft));
|
||||||
|
parent.spawn(text_span("Back (J/S) ", TextSpanKind::MoveBack));
|
||||||
|
parent.spawn(text_span("Forward (K/W) ", TextSpanKind::MoveForward));
|
||||||
|
parent.spawn(text_span("Right (L/D) ", TextSpanKind::MoveRight));
|
||||||
|
parent.spawn(text_span("Up (Space)", TextSpanKind::Jump));
|
||||||
|
parent.spawn(text_span("(MouseDeltaX)", TextSpanKind::DeltaX));
|
||||||
|
parent.spawn(text_span("(MouseDeltaY)", TextSpanKind::DeltaY));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn text_update_system(
|
fn text_update_system(
|
||||||
mut query: Query<&mut Text, With<TextBlock>>,
|
mut query: Query<(&TextSpanKind, &mut TextSpan, &mut TextColor)>,
|
||||||
inputs_container: Res<MyInputsContainer>,
|
inputs_container: Res<MyInputsContainer>,
|
||||||
mut jumps_count: ResMut<JumpsCount>,
|
mut jumps_count: ResMut<JumpsCount>,
|
||||||
) {
|
) {
|
||||||
for text in query.iter_mut() {
|
for (text_span_kind, mut text_span, mut text_color) in &mut query {
|
||||||
let player_inputs = inputs_container.me().expect("This is bad");
|
let player_inputs = inputs_container.me().expect("This is bad");
|
||||||
let mut text = update_text(text, player_inputs.get(MyAction::Left), MOVE_LEFT_INDEX);
|
match *text_span_kind {
|
||||||
text = update_text(text, player_inputs.get(MyAction::Back), MOVE_BACK_INDEX);
|
TextSpanKind::MoveLeft => {
|
||||||
text = update_text(
|
update_text_color(&mut text_color, player_inputs.get(MyAction::Left))
|
||||||
text,
|
}
|
||||||
player_inputs.get(MyAction::Forward),
|
TextSpanKind::MoveBack => {
|
||||||
MOVE_FORWARD_INDEX,
|
update_text_color(&mut text_color, player_inputs.get(MyAction::Back))
|
||||||
);
|
}
|
||||||
text = update_text(text, player_inputs.get(MyAction::Right), MOVE_RIGHT_INDEX);
|
TextSpanKind::MoveForward => {
|
||||||
|
update_text_color(&mut text_color, player_inputs.get(MyAction::Forward));
|
||||||
|
}
|
||||||
|
TextSpanKind::MoveRight => {
|
||||||
|
update_text_color(&mut text_color, player_inputs.get(MyAction::Right));
|
||||||
|
}
|
||||||
|
TextSpanKind::Jump => {
|
||||||
if player_inputs
|
if player_inputs
|
||||||
.get_just_pressed(MyAction::Up)
|
.get_just_pressed(MyAction::Up)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
**jumps_count += 1;
|
**jumps_count += 1;
|
||||||
text.sections[JUMP_INDEX].value = format!("Up (Space) - {}", **jumps_count);
|
text_span.0 = format!("Up (Space) - {}", **jumps_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if player_inputs
|
||||||
|
.get_just_pressed(MyAction::Down)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
**jumps_count -= 1;
|
||||||
|
text_span.0 = format!("Up (Space) - {}", **jumps_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextSpanKind::DeltaX => {
|
||||||
|
let x = player_inputs.get(MyAction::MouseHorizontal).to_float();
|
||||||
|
text_span.0 = format!("(MouseDeltaX) - {}", x);
|
||||||
|
}
|
||||||
|
TextSpanKind::DeltaY => {
|
||||||
|
let y = player_inputs.get(MyAction::MouseVertical).to_float();
|
||||||
|
text_span.0 = format!("(MouseDeltaY) - {}", y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_text(mut text: Mut<Text>, input_value: InputValue, index: usize) -> Mut<Text> {
|
fn update_text_color(text_color: &mut TextColor, input_value: InputValue) {
|
||||||
if input_value.is_boolean() && input_value.to_boolean() {
|
if input_value.is_boolean() && input_value.to_boolean() {
|
||||||
text.sections[index].style.color = Color::GOLD;
|
text_color.0 = Color::linear_rgb(0.855, 0.647, 0.125);
|
||||||
} else {
|
} else {
|
||||||
text.sections[index].style.color = Color::WHITE;
|
text_color.0 = Color::WHITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
text
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5128
File diff suppressed because it is too large
Load Diff
+34
@@ -0,0 +1,34 @@
|
|||||||
|
[package]
|
||||||
|
name = "rebinding"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target-dir = "../../../../target"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
x11 = ["bevy/x11"]
|
||||||
|
wayland = ["bevy/wayland"]
|
||||||
|
windows = ["bevy/bevy_winit"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy = { version = "0.19.1", default-features = false, features = [
|
||||||
|
"std",
|
||||||
|
"default_app",
|
||||||
|
"bevy_asset",
|
||||||
|
"bevy_text",
|
||||||
|
"bevy_state",
|
||||||
|
"bevy_ui",
|
||||||
|
"bevy_ui_render",
|
||||||
|
"bevy_window",
|
||||||
|
"bevy_winit",
|
||||||
|
"default_font",
|
||||||
|
] }
|
||||||
|
bevy_reflect = { version = "0.19.1", features = ["auto_register_static"] }
|
||||||
|
bevy_controls = { path = "./../../" }
|
||||||
|
bevy_controls_derive = { path = "./../../../bevy_controls_derive" }
|
||||||
|
strum = "0.26.2"
|
||||||
|
strum_macros = "0.26.2"
|
||||||
|
|
||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
linker = "rust-lld.exe"
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
# Rebinding example
|
||||||
|
|
||||||
|
Run from this directory with one platform feature:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --features x11
|
||||||
|
cargo run --features wayland
|
||||||
|
cargo run --features windows
|
||||||
|
```
|
||||||
|
|
||||||
|
Controls:
|
||||||
|
|
||||||
|
- `1`-`4`: select Left, Back, Forward, or Right.
|
||||||
|
- `R`: capture next key and add it to selected action.
|
||||||
|
- `C`: clear selected action bindings.
|
||||||
|
- `Escape`: cancel key capture.
|
||||||
|
|
||||||
|
Default WASD bindings and captured keys use physical `KeyCode` positions.
|
||||||
+208
@@ -0,0 +1,208 @@
|
|||||||
|
use bevy::{
|
||||||
|
ecs::message::MessageReader,
|
||||||
|
input::{keyboard::KeyboardInput, ButtonState},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use bevy_controls::{
|
||||||
|
contract::InputsContainer,
|
||||||
|
plugin::ControlsPlugin,
|
||||||
|
resource::{Binding, BindingConfig, Bindings, Controls, InputType, Keyboard, PlayerActions},
|
||||||
|
};
|
||||||
|
use bevy_controls_derive::{Action, GameState};
|
||||||
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy, Debug, Action)]
|
||||||
|
enum MovementAction {
|
||||||
|
Left,
|
||||||
|
Back,
|
||||||
|
Forward,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MovementAction {
|
||||||
|
const fn name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Left => "Left",
|
||||||
|
Self::Back => "Back",
|
||||||
|
Self::Forward => "Forward",
|
||||||
|
Self::Right => "Right",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default, GameState)]
|
||||||
|
enum DemoState {
|
||||||
|
#[default]
|
||||||
|
Playing,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default, Clone, Debug)]
|
||||||
|
struct MovementInputs(PlayerActions<MovementAction>);
|
||||||
|
|
||||||
|
impl InputsContainer<MovementAction> for MovementInputs {
|
||||||
|
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerActions<MovementAction>> + 'a> {
|
||||||
|
Box::new(std::iter::once(&self.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn me<'a>(&'a self) -> Option<&'a PlayerActions<MovementAction>> {
|
||||||
|
Some(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerActions<MovementAction>> {
|
||||||
|
Some(&mut self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct RebindingUi {
|
||||||
|
selected: MovementAction,
|
||||||
|
capturing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RebindingUi {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
selected: MovementAction::Left,
|
||||||
|
capturing: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct RebindingText;
|
||||||
|
|
||||||
|
const TEXT_SIZE: f32 = 30.;
|
||||||
|
|
||||||
|
fn physical_binding(key_code: KeyCode) -> Binding<DemoState> {
|
||||||
|
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(key_code)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn controls() -> Controls<MovementAction, DemoState> {
|
||||||
|
Controls::new()
|
||||||
|
.with(
|
||||||
|
MovementAction::Left,
|
||||||
|
BindingConfig::new(Bindings::new(vec![physical_binding(KeyCode::KeyA)]).customizable()),
|
||||||
|
)
|
||||||
|
.with(
|
||||||
|
MovementAction::Back,
|
||||||
|
BindingConfig::new(Bindings::new(vec![physical_binding(KeyCode::KeyS)]).customizable()),
|
||||||
|
)
|
||||||
|
.with(
|
||||||
|
MovementAction::Forward,
|
||||||
|
BindingConfig::new(Bindings::new(vec![physical_binding(KeyCode::KeyW)]).customizable()),
|
||||||
|
)
|
||||||
|
.with(
|
||||||
|
MovementAction::Right,
|
||||||
|
BindingConfig::new(Bindings::new(vec![physical_binding(KeyCode::KeyD)]).customizable()),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins((
|
||||||
|
DefaultPlugins.set(WindowPlugin {
|
||||||
|
primary_window: Some(Window {
|
||||||
|
title: "Bevy Controls Rebinding".into(),
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
ControlsPlugin::<MovementAction, MovementInputs, DemoState>::new(controls()),
|
||||||
|
))
|
||||||
|
.init_resource::<RebindingUi>()
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, (handle_rebinding, update_text))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands) {
|
||||||
|
commands.spawn(Camera2d);
|
||||||
|
commands.spawn((
|
||||||
|
Text::new(""),
|
||||||
|
TextFont {
|
||||||
|
font_size: FontSize::Px(TEXT_SIZE),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
TextColor(Color::WHITE),
|
||||||
|
BackgroundColor(Color::BLACK),
|
||||||
|
RebindingText,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_rebinding(
|
||||||
|
mut controls: ResMut<Controls<MovementAction, DemoState>>,
|
||||||
|
mut ui: ResMut<RebindingUi>,
|
||||||
|
keys: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut keyboard_events: MessageReader<KeyboardInput>,
|
||||||
|
) {
|
||||||
|
if ui.capturing {
|
||||||
|
for event in keyboard_events.read() {
|
||||||
|
if event.state != ButtonState::Pressed {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.key_code == KeyCode::Escape {
|
||||||
|
ui.capturing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let binding = Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(event.key_code)));
|
||||||
|
controls.push_binding(ui.selected, binding);
|
||||||
|
ui.capturing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in keyboard_events.read() {}
|
||||||
|
|
||||||
|
if keys.just_pressed(KeyCode::Digit1) {
|
||||||
|
ui.selected = MovementAction::Left;
|
||||||
|
}
|
||||||
|
if keys.just_pressed(KeyCode::Digit2) {
|
||||||
|
ui.selected = MovementAction::Back;
|
||||||
|
}
|
||||||
|
if keys.just_pressed(KeyCode::Digit3) {
|
||||||
|
ui.selected = MovementAction::Forward;
|
||||||
|
}
|
||||||
|
if keys.just_pressed(KeyCode::Digit4) {
|
||||||
|
ui.selected = MovementAction::Right;
|
||||||
|
}
|
||||||
|
if keys.just_pressed(KeyCode::KeyC) {
|
||||||
|
controls.clear_bindings(ui.selected);
|
||||||
|
}
|
||||||
|
if keys.just_pressed(KeyCode::KeyR) {
|
||||||
|
ui.capturing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_text(
|
||||||
|
mut text: Single<&mut Text, With<RebindingText>>,
|
||||||
|
ui: Res<RebindingUi>,
|
||||||
|
controls: Res<Controls<MovementAction, DemoState>>,
|
||||||
|
) {
|
||||||
|
let bindings = controls.get(ui.selected).map_or_else(
|
||||||
|
|| String::from(" (none)"),
|
||||||
|
|config| {
|
||||||
|
config
|
||||||
|
.bindings
|
||||||
|
.iter()
|
||||||
|
.map(|binding| format!(" {binding:?}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let capture_status = if ui.capturing {
|
||||||
|
"Waiting for a key. Escape cancels."
|
||||||
|
} else {
|
||||||
|
"Ready. Press R to add a binding."
|
||||||
|
};
|
||||||
|
|
||||||
|
text.0 = format!(
|
||||||
|
"Keyboard rebinding demo\n\nSelected action: {}\n{}\n\nBindings:\n{}\n\n1-4 select action | R capture | C clear | Escape cancel",
|
||||||
|
ui.selected.name(),
|
||||||
|
capture_status,
|
||||||
|
bindings,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "bevy_controls_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
#bevy_controls = { path = "./../bevy_controls" }
|
||||||
|
syn = { version = "1.0", features = [ "full", "extra-traits" ] }
|
||||||
|
quote = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
env_logger = "0.10.1"
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream, Result},
|
||||||
|
parse_macro_input,
|
||||||
|
punctuated::Punctuated,
|
||||||
|
ItemTrait, LitStr, Token, TypeParamBound,
|
||||||
|
};
|
||||||
|
|
||||||
|
// #[cfg_feature_trait_bounds {
|
||||||
|
// "serialize" => Serialize + DeserializeOwned,
|
||||||
|
// "reflect" => Reflect + TypePath + FromReflect,
|
||||||
|
// "inspector-egui" => InspectorOptionsType,
|
||||||
|
// }]
|
||||||
|
|
||||||
|
type RuleKey = LitStr;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FeatureTraitBoundMap(HashMap<RuleKey, Punctuated<TypeParamBound, Token![+]>>);
|
||||||
|
|
||||||
|
impl Parse for FeatureTraitBoundMap {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let mut rules = HashMap::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let condition: RuleKey = input.parse()?;
|
||||||
|
|
||||||
|
let _arrow = input.parse::<Token![=>]>()?;
|
||||||
|
|
||||||
|
let supertraits = {
|
||||||
|
let mut supertraits = Punctuated::new();
|
||||||
|
loop {
|
||||||
|
supertraits.push_value(input.parse()?);
|
||||||
|
if !input.peek(Token![+]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
supertraits.push_punct(input.parse()?);
|
||||||
|
|
||||||
|
if input.peek(Token![,]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
supertraits
|
||||||
|
};
|
||||||
|
let _comma = input.parse::<Token![,]>();
|
||||||
|
|
||||||
|
rules.insert(condition, supertraits);
|
||||||
|
|
||||||
|
if input.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FeatureTraitBoundMap(rules))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn cfg_feature_trait_bounds(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let _rules = parse_macro_input!(attr as FeatureTraitBoundMap);
|
||||||
|
let _input = parse_macro_input!(input as ItemTrait);
|
||||||
|
|
||||||
|
dbg!(std::env::vars().map(|(key, _)| key).collect::<Vec<_>>());
|
||||||
|
|
||||||
|
// let code = quote! {
|
||||||
|
// trait #name: #rules {}
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// dbg!(code);
|
||||||
|
|
||||||
|
TokenStream::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn supertraits(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as ItemTrait);
|
||||||
|
|
||||||
|
let mut bounds = Vec::new();
|
||||||
|
if cfg!(feature = "serialize") {
|
||||||
|
bounds.push(quote! { serde::Serialize });
|
||||||
|
bounds.push(quote! { serde::de::DeserializeOwned });
|
||||||
|
};
|
||||||
|
if cfg!(feature = "reflect") {
|
||||||
|
bounds.push(quote! { bevy_reflect::Reflect });
|
||||||
|
bounds.push(quote! { bevy_reflect::TypePath });
|
||||||
|
bounds.push(quote! { bevy_reflect::FromReflect });
|
||||||
|
};
|
||||||
|
if cfg!(feature = "inspector-egui") {
|
||||||
|
bounds.push(quote! { bevy_inspector_egui::inspector_options::InspectorOptionsType });
|
||||||
|
};
|
||||||
|
|
||||||
|
let ItemTrait {
|
||||||
|
ident,
|
||||||
|
vis,
|
||||||
|
items,
|
||||||
|
generics,
|
||||||
|
supertraits: bounds_old,
|
||||||
|
..
|
||||||
|
} = input;
|
||||||
|
|
||||||
|
let bounds_old = bounds_old.iter().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let out = quote! {
|
||||||
|
#vis trait #ident #generics: #(#bounds_old)+* + #(#bounds)+* { #(#items)* }
|
||||||
|
};
|
||||||
|
|
||||||
|
// dbg!(&out);
|
||||||
|
// dbg!(out.to_string());
|
||||||
|
|
||||||
|
TokenStream::from(out)
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ log "enable logging"
|
|||||||
|
|
||||||
while [ "$#" -gt 0 ]; do
|
while [ "$#" -gt 0 ]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
-h|-?|--help|help)
|
-h|--help|help)
|
||||||
HELP=1
|
HELP=1
|
||||||
;;
|
;;
|
||||||
-*)
|
-*)
|
||||||
|
|||||||
Regular → Executable
+40
-206
@@ -1,19 +1,20 @@
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use bevy_ecs::{schedule::States, system::Resource};
|
use bevy_ecs::{component::Mutable, prelude::Resource};
|
||||||
#[cfg(feature = "inspector-egui")]
|
#[cfg(feature = "inspector-egui")]
|
||||||
use bevy_inspector_egui::inspector_options::InspectorOptionsType;
|
use bevy_inspector_egui::inspector_options::InspectorOptionsType;
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
// #[cfg(all(feature = "reflect", feature = "serialize"))]
|
|
||||||
// use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
|
||||||
#[cfg(feature = "reflect")]
|
#[cfg(feature = "reflect")]
|
||||||
use bevy_reflect::{FromReflect, Reflect, TypePath};
|
use bevy_reflect::{FromReflect, Reflect, TypePath};
|
||||||
|
use bevy_state::state::{FreelyMutableState, States};
|
||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use crate::resource::PlayerInputs;
|
use crate::resource::PlayerActions;
|
||||||
|
|
||||||
pub trait ActionInner:
|
macro_rules! define_contracts {
|
||||||
|
($($feature_bounds:tt)*) => {
|
||||||
|
pub trait Action:
|
||||||
'static
|
'static
|
||||||
+ std::marker::Sync
|
+ std::marker::Sync
|
||||||
+ std::marker::Send
|
+ std::marker::Send
|
||||||
@@ -24,225 +25,58 @@ pub trait ActionInner:
|
|||||||
+ Clone
|
+ Clone
|
||||||
+ Copy
|
+ Copy
|
||||||
+ std::fmt::Debug
|
+ std::fmt::Debug
|
||||||
|
$($feature_bounds)*
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait GameStateInner:
|
pub trait GameState:
|
||||||
'static + std::marker::Sync + std::marker::Send + States + Default + std::fmt::Debug
|
'static
|
||||||
|
+ std::marker::Sync
|
||||||
|
+ std::marker::Send
|
||||||
|
+ States
|
||||||
|
+ Default
|
||||||
|
+ std::fmt::Debug
|
||||||
|
+ FreelyMutableState
|
||||||
|
$($feature_bounds)*
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InputsContainerInner<A: Action>:
|
pub trait InputsContainer<A: Action>:
|
||||||
'static + std::marker::Sync + std::marker::Send + Resource + Default + std::fmt::Debug
|
'static
|
||||||
|
+ std::marker::Sync
|
||||||
|
+ std::marker::Send
|
||||||
|
+ Resource<Mutability = Mutable>
|
||||||
|
+ Default
|
||||||
|
+ std::fmt::Debug
|
||||||
|
$($feature_bounds)*
|
||||||
{
|
{
|
||||||
/// The method returning an iterator over references to the PlayerInputs.
|
/// 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 PlayerActions<A>> + 'a>;
|
||||||
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerInputs<A>> + 'a>;
|
|
||||||
/// The method returning an player inputs of this client.
|
/// 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.
|
/// 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(feature = "serialize")]
|
#[cfg(all(not(feature = "serialize"), not(feature = "reflect")))]
|
||||||
pub trait SerializeImpl: Serialize + DeserializeOwned {}
|
define_contracts!();
|
||||||
|
#[cfg(all(feature = "serialize", not(feature = "reflect")))]
|
||||||
#[cfg(feature = "reflect")]
|
define_contracts!(+ Serialize + DeserializeOwned);
|
||||||
pub trait ReflectImpl: Reflect + TypePath + FromReflect {}
|
|
||||||
|
|
||||||
#[cfg(feature = "inspector-egui")]
|
|
||||||
pub trait InspectorEguiImpl: InspectorOptionsType {}
|
|
||||||
|
|
||||||
// ===== Action =====
|
|
||||||
// SAFETY: inspector-egui includes reflect
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
not(feature = "serialize"),
|
|
||||||
not(feature = "reflect"),
|
|
||||||
not(feature = "inspector-egui")
|
|
||||||
))]
|
|
||||||
pub trait Action: ActionInner {}
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
feature = "serialize",
|
|
||||||
not(feature = "reflect"),
|
|
||||||
not(feature = "inspector-egui")
|
|
||||||
))]
|
|
||||||
pub trait Action: ActionInner + SerializeImpl {}
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
not(feature = "serialize"),
|
|
||||||
feature = "reflect",
|
feature = "reflect",
|
||||||
not(feature = "inspector-egui")
|
|
||||||
))]
|
|
||||||
pub trait Action: ActionInner + ReflectImpl {}
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
feature = "serialize",
|
|
||||||
feature = "reflect",
|
|
||||||
not(feature = "inspector-egui")
|
|
||||||
))]
|
|
||||||
pub trait Action: ActionInner + SerializeImpl + ReflectImpl {}
|
|
||||||
|
|
||||||
#[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))]
|
|
||||||
pub trait Action: ActionInner + ReflectImpl + InspectorEguiImpl {}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "serialize", feature = "inspector-egui"))]
|
|
||||||
pub trait Action: ActionInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {}
|
|
||||||
|
|
||||||
// ===== GameState =====
|
|
||||||
// SAFETY: inspector-egui includes reflect
|
|
||||||
#[cfg(all(
|
|
||||||
not(feature = "serialize"),
|
not(feature = "serialize"),
|
||||||
not(feature = "reflect"),
|
|
||||||
not(feature = "inspector-egui")
|
not(feature = "inspector-egui")
|
||||||
))]
|
))]
|
||||||
pub trait GameState: GameStateInner {}
|
define_contracts!(+ Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed);
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "serialize",
|
|
||||||
not(feature = "reflect"),
|
|
||||||
not(feature = "inspector-egui")
|
|
||||||
))]
|
|
||||||
pub trait GameState: GameStateInner + SerializeImpl {}
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
not(feature = "serialize"),
|
|
||||||
feature = "reflect",
|
feature = "reflect",
|
||||||
not(feature = "inspector-egui")
|
|
||||||
))]
|
|
||||||
pub trait GameState: GameStateInner + ReflectImpl {}
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
feature = "serialize",
|
feature = "serialize",
|
||||||
feature = "reflect",
|
|
||||||
not(feature = "inspector-egui")
|
not(feature = "inspector-egui")
|
||||||
))]
|
))]
|
||||||
pub trait GameState: GameStateInner + SerializeImpl + ReflectImpl {}
|
define_contracts!(+ Serialize + DeserializeOwned + Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed);
|
||||||
|
#[cfg(all(feature = "inspector-egui", not(feature = "serialize")))]
|
||||||
#[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))]
|
define_contracts!(+ Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed + InspectorOptionsType);
|
||||||
pub trait GameState: GameStateInner + ReflectImpl + InspectorEguiImpl {}
|
#[cfg(all(feature = "inspector-egui", feature = "serialize"))]
|
||||||
|
define_contracts!(+ Serialize + DeserializeOwned + Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed + InspectorOptionsType);
|
||||||
#[cfg(all(feature = "serialize", feature = "inspector-egui"))]
|
|
||||||
pub trait GameState: GameStateInner + SerializeImpl + ReflectImpl + InspectorEguiImpl {}
|
|
||||||
|
|
||||||
// ===== InputsContainer =====
|
|
||||||
// SAFETY: inspector-egui includes reflect
|
|
||||||
#[cfg(all(
|
|
||||||
not(feature = "serialize"),
|
|
||||||
not(feature = "reflect"),
|
|
||||||
not(feature = "inspector-egui")
|
|
||||||
))]
|
|
||||||
pub trait InputsContainer<A: Action>: InputsContainerInner<A> {}
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
feature = "serialize",
|
|
||||||
not(feature = "reflect"),
|
|
||||||
not(feature = "inspector-egui")
|
|
||||||
))]
|
|
||||||
pub trait InputsContainer<A: Action>: InputsContainerInner<A> + SerializeImpl {}
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
not(feature = "serialize"),
|
|
||||||
feature = "reflect",
|
|
||||||
not(feature = "inspector-egui")
|
|
||||||
))]
|
|
||||||
pub trait InputsContainer<A: Action>: InputsContainerInner<A> + ReflectImpl {}
|
|
||||||
|
|
||||||
#[cfg(all(
|
|
||||||
feature = "serialize",
|
|
||||||
feature = "reflect",
|
|
||||||
not(feature = "inspector-egui")
|
|
||||||
))]
|
|
||||||
pub trait InputsContainer<A: Action>:
|
|
||||||
InputsContainerInner<A> + SerializeImpl + ReflectImpl
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))]
|
|
||||||
pub trait InputsContainer<A: Action>:
|
|
||||||
InputsContainerInner<A> + ReflectImpl + InspectorEguiImpl
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "serialize", feature = "inspector-egui"))]
|
|
||||||
pub trait InputsContainer<A: Action>:
|
|
||||||
InputsContainerInner<A> + SerializeImpl + ReflectImpl + InspectorEguiImpl
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[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 {}
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -2,5 +2,3 @@ pub mod contract;
|
|||||||
pub mod plugin;
|
pub mod plugin;
|
||||||
pub mod resource;
|
pub mod resource;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub use util::*;
|
|
||||||
|
|||||||
Regular → Executable
+196
-210
@@ -1,38 +1,108 @@
|
|||||||
use bevy_app::{App, Plugin, Update};
|
use bevy_app::{App, Plugin, PreUpdate};
|
||||||
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
schedule::State,
|
message::MessageReader,
|
||||||
system::{Res, ResMut},
|
prelude::Resource,
|
||||||
|
system::{In, IntoSystem, Res, ResMut},
|
||||||
};
|
};
|
||||||
use bevy_input::{keyboard::{KeyCode, ScanCode}, mouse::MouseButton, Input};
|
use bevy_input::{
|
||||||
|
keyboard::KeyCode,
|
||||||
|
mouse::{MouseButton, MouseMotion, MouseScrollUnit, MouseWheel},
|
||||||
|
ButtonInput,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "logical-keyboard")]
|
||||||
|
use bevy_input::{
|
||||||
|
keyboard::{Key, KeyboardInput},
|
||||||
|
ButtonState,
|
||||||
|
};
|
||||||
|
use bevy_state::app::AppExtStates;
|
||||||
|
use bevy_state::state::State;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
#[cfg(feature = "logical-keyboard")]
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
contract::{Action, GameState, InputsContainer},
|
contract::{Action, GameState, InputsContainer},
|
||||||
resource::*,
|
resource::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ControlsPlugin<A, Ic, Gs> {
|
pub struct ControlsPlugin<A: Action, Ic: InputsContainer<A>, Gs: GameState> {
|
||||||
_action: std::marker::PhantomData<A>,
|
_action: std::marker::PhantomData<A>,
|
||||||
_inputs_container: std::marker::PhantomData<Ic>,
|
_inputs_container: std::marker::PhantomData<Ic>,
|
||||||
_game_state: std::marker::PhantomData<Gs>,
|
_game_state: std::marker::PhantomData<Gs>,
|
||||||
|
controls: Controls<A, Gs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, Ic, Gs> Default for ControlsPlugin<A, Ic, Gs> {
|
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Default for ControlsPlugin<A, Ic, Gs> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
_action: std::marker::PhantomData,
|
_action: std::marker::PhantomData,
|
||||||
_inputs_container: std::marker::PhantomData,
|
_inputs_container: std::marker::PhantomData,
|
||||||
_game_state: std::marker::PhantomData,
|
_game_state: std::marker::PhantomData,
|
||||||
|
controls: Controls::<A, Gs>::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs> {
|
||||||
|
pub fn new(controls: Controls<A, Gs>) -> Self {
|
||||||
|
Self {
|
||||||
|
_action: std::marker::PhantomData,
|
||||||
|
_inputs_container: std::marker::PhantomData,
|
||||||
|
_game_state: std::marker::PhantomData,
|
||||||
|
controls,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default, Debug, Deref, DerefMut)]
|
||||||
|
struct TrigeredInputs(HashMap<InputType, InputValue>);
|
||||||
|
|
||||||
|
#[cfg(feature = "logical-keyboard")]
|
||||||
|
#[derive(Resource, Default, Debug, Deref, DerefMut)]
|
||||||
|
struct PressedLogicalKeys(HashSet<Key>);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "reflect"))]
|
||||||
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin<A, Ic, Gs> {
|
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin<A, Ic, Gs> {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app
|
app
|
||||||
.init_resource::<Controls<A, Gs>>()
|
.insert_resource(self.controls.clone())
|
||||||
.init_resource::<Ic>()
|
.init_resource::<Ic>()
|
||||||
.add_state::<Gs>()
|
.init_resource::<TrigeredInputs>()
|
||||||
.add_systems(Update, Self::save_input);
|
.insert_state::<Gs>(Gs::default());
|
||||||
|
|
||||||
|
#[cfg(feature = "logical-keyboard")]
|
||||||
|
app.init_resource::<PressedLogicalKeys>();
|
||||||
|
|
||||||
|
app.add_systems(
|
||||||
|
PreUpdate,
|
||||||
|
Self::collect_inputs.pipe(Self::fill_players_inputs),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "reflect")]
|
||||||
|
impl<A, Ic, Gs> Plugin for ControlsPlugin<A, Ic, Gs>
|
||||||
|
where
|
||||||
|
A: Action + bevy_reflect::FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed,
|
||||||
|
Ic: InputsContainer<A>,
|
||||||
|
Gs:
|
||||||
|
GameState + bevy_reflect::FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed,
|
||||||
|
{
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
.insert_resource(self.controls.clone())
|
||||||
|
.init_resource::<Ic>()
|
||||||
|
.init_resource::<TrigeredInputs>()
|
||||||
|
.insert_state::<Gs>(Gs::default());
|
||||||
|
|
||||||
|
#[cfg(feature = "logical-keyboard")]
|
||||||
|
app.init_resource::<PressedLogicalKeys>();
|
||||||
|
|
||||||
|
app.add_systems(
|
||||||
|
PreUpdate,
|
||||||
|
Self::collect_inputs.pipe(Self::fill_players_inputs),
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(feature = "reflect")]
|
#[cfg(feature = "reflect")]
|
||||||
app.register_type::<Controls<A, Gs>>();
|
app.register_type::<Controls<A, Gs>>();
|
||||||
@@ -40,223 +110,139 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Plugin for ControlsPlugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs> {
|
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs> {
|
||||||
/// Process all hard inputs and bindings to update [`PlayerInputs`]
|
/// Collects input events from various input devices
|
||||||
fn save_input(
|
/// (keyboard, mouse buttons, mouse motion, and scroll)
|
||||||
keyboard_input_keycode: Res<Input<KeyCode>>,
|
/// and stores them in a `TrigeredInputs` structure.
|
||||||
keyboard_input_scancode: Res<Input<ScanCode>>,
|
///
|
||||||
mouse_input: Res<Input<MouseButton>>,
|
/// This function is separated from filling inputs to allow for more complex input handling
|
||||||
|
/// in the future, particularly in scenarios where the order of input events becomes significant.
|
||||||
|
///
|
||||||
|
fn collect_inputs(
|
||||||
|
keyboard_button: Res<ButtonInput<KeyCode>>,
|
||||||
|
#[cfg(feature = "logical-keyboard")] mut keyboard_evr: MessageReader<KeyboardInput>,
|
||||||
|
#[cfg(feature = "logical-keyboard")] mut pressed_logical_keys: ResMut<PressedLogicalKeys>,
|
||||||
|
mouse_buttons: Res<ButtonInput<MouseButton>>,
|
||||||
|
mut scroll_evr: MessageReader<MouseWheel>,
|
||||||
|
mut motion_evr: MessageReader<MouseMotion>,
|
||||||
|
// mut collected_inputs: ResMut<TrigeredInputs>,
|
||||||
|
) -> TrigeredInputs {
|
||||||
|
let mut collected_inputs = TrigeredInputs(HashMap::new());
|
||||||
|
|
||||||
|
#[cfg(feature = "logical-keyboard")]
|
||||||
|
for event in keyboard_evr.read() {
|
||||||
|
match event.state {
|
||||||
|
ButtonState::Pressed => {
|
||||||
|
pressed_logical_keys.insert(event.logical_key.clone());
|
||||||
|
}
|
||||||
|
ButtonState::Released => {
|
||||||
|
pressed_logical_keys.remove(&event.logical_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect pressed keyboard buttons
|
||||||
|
for key in keyboard_button.get_pressed() {
|
||||||
|
collected_inputs.insert(
|
||||||
|
InputType::Keyboard(Keyboard::KeyCode(*key)),
|
||||||
|
InputValue::Boolean(true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "logical-keyboard")]
|
||||||
|
for key in pressed_logical_keys.iter() {
|
||||||
|
collected_inputs.insert(
|
||||||
|
InputType::Keyboard(Keyboard::Key(key.clone())),
|
||||||
|
InputValue::Boolean(true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// buttons.get_just_released()
|
||||||
|
|
||||||
|
// Collect pressed mouse buttons
|
||||||
|
for button in mouse_buttons.get_pressed() {
|
||||||
|
collected_inputs.insert(
|
||||||
|
InputType::Mouse(MouseInput::Button(*button)),
|
||||||
|
InputValue::Boolean(true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect mouse motion events
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect mouse scroll events
|
||||||
|
for ev in scroll_evr.read() {
|
||||||
|
match ev.unit {
|
||||||
|
// Handle scroll in line units
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Handle scroll in pixel units (not accepted in this game)
|
||||||
|
MouseScrollUnit::Pixel => {
|
||||||
|
panic!("I'm do not accept you to use touch pad in my game");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collected_inputs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills the player's inputs based on the collected inputs and the current control bindings.
|
||||||
|
///
|
||||||
|
/// This function takes in the current inputs, updates them according to the specified control
|
||||||
|
/// bindings, and then stores the updated inputs in the input container. It only updates the
|
||||||
|
/// inputs if they satisfy the binding conditions, such as being in a specific game state.
|
||||||
|
///
|
||||||
|
fn fill_players_inputs(
|
||||||
|
In(collected_inputs): In<TrigeredInputs>,
|
||||||
mut inputs_container: ResMut<Ic>,
|
mut inputs_container: ResMut<Ic>,
|
||||||
controls: Res<Controls<A, Gs>>,
|
controls: Res<Controls<A, Gs>>,
|
||||||
game_state: Res<State<Gs>>,
|
game_state: Res<State<Gs>>,
|
||||||
) {
|
) {
|
||||||
|
// Check if the input container is accessible
|
||||||
if let Some(inputs) = inputs_container.me_mut() {
|
if let Some(inputs) = inputs_container.me_mut() {
|
||||||
for (action, config) in controls.iter() {
|
for (action, config) in controls.iter() {
|
||||||
'bindings_loop: for binding in config.bindings.iter() {
|
let mut action_value = InputValue::Empty;
|
||||||
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);
|
// Iterate over the control bindings and collect all action-binding pairs
|
||||||
|
for binding in config.bindings.iter() {
|
||||||
|
// Skip any binding with mismatched condition
|
||||||
|
if binding
|
||||||
|
.conditions
|
||||||
|
.iter()
|
||||||
|
.any(|condition| matches!(condition, BindingCondition::InGameState(state) if *state != *game_state.get()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
match &binding.input {
|
match &binding.input {
|
||||||
ButtonCombination::Single(button) => match button {
|
ButtonCombination::Single(input_type) => {
|
||||||
InputType::Keyboard(Keyboard::KeyCode(key)) => {
|
if let Some(&value) = collected_inputs.get(&*input_type) {
|
||||||
log::trace!("presed?: {:?}", keyboard_input_keycode.pressed(*key));
|
action_value = action_value.merge(value);
|
||||||
log::trace!("active");
|
|
||||||
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 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 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!();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ButtonCombination::Chord(buttons) => {
|
|
||||||
// If any button in chord is not pressed we skip this `binding`
|
|
||||||
for button in buttons {
|
|
||||||
match button {
|
|
||||||
InputType::Keyboard(Keyboard::KeyCode(key)) => {
|
|
||||||
if !keyboard_input_keycode.pressed(*key) {
|
|
||||||
continue 'bindings_loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InputType::Keyboard(Keyboard::ScanCode(code)) => {
|
|
||||||
if !keyboard_input_scancode.pressed(*code) {
|
|
||||||
continue 'bindings_loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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!();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::trace!("active");
|
inputs.forced_set(*action, action_value);
|
||||||
// TODO: should be [`Chord`](ButtonCombination::Chord) only [`Boolean`](InputValue::Boolean) type
|
|
||||||
inputs.forced_set(*action, true);
|
|
||||||
break; // when on binding trigered no sence check another
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::error!("cannot find me in inputs container")
|
log::warn!("cannot find me in inputs container");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[cfg(test)]
|
|
||||||
// mod performance_test {
|
|
||||||
// use crate::{ util::test::{enable_loggings, measure_time, Times}, resource::PlayerInputs};
|
|
||||||
|
|
||||||
// use super::Controls;
|
|
||||||
// use std::time::Duration;
|
|
||||||
|
|
||||||
// /// Test for execution time for [`Controls`] get
|
|
||||||
// ///
|
|
||||||
// /// Example:
|
|
||||||
// /// ```
|
|
||||||
// /// cargo test --package pih-pah-app --lib --features "dev, ui_egui" -- controls::test::controls_get --exact --nocapture
|
|
||||||
// /// ```
|
|
||||||
// #[test]
|
|
||||||
// fn controls_get() {
|
|
||||||
// enable_loggings();
|
|
||||||
|
|
||||||
// let controls = Controls::default();
|
|
||||||
|
|
||||||
// let duration = measure_time(
|
|
||||||
// || {
|
|
||||||
// controls.get(Action::LeverEditorForward);
|
|
||||||
// controls.get(Action::LevelEditorBackward);
|
|
||||||
// controls.get(Action::LevelEditorLeft);
|
|
||||||
// controls.get(Action::LevelEditorRight);
|
|
||||||
// },
|
|
||||||
// Times::default(),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// log::info!("time: {:?}", duration);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Test for execution speed for [`PlayerInputs`] get
|
|
||||||
// fn player_inputs_get() -> Duration {
|
|
||||||
// enable_loggings();
|
|
||||||
|
|
||||||
// let inputs = PlayerInputs::default();
|
|
||||||
|
|
||||||
// let duration = measure_time(
|
|
||||||
// || {
|
|
||||||
// inputs.get(Action::LeverEditorForward);
|
|
||||||
// inputs.get(Action::LevelEditorBackward);
|
|
||||||
// inputs.get(Action::LevelEditorLeft);
|
|
||||||
// inputs.get(Action::LevelEditorRight);
|
|
||||||
// },
|
|
||||||
// Times::default(),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// duration
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Test for execution speed for [`PlayerInputs`] get_many
|
|
||||||
// fn player_inputs_get_many() -> Duration {
|
|
||||||
// enable_loggings();
|
|
||||||
|
|
||||||
// let inputs = PlayerInputs::default();
|
|
||||||
|
|
||||||
// let duration = measure_time(
|
|
||||||
// || {
|
|
||||||
// inputs.get_many(vec![
|
|
||||||
// Action::LeverEditorForward,
|
|
||||||
// Action::LevelEditorBackward,
|
|
||||||
// Action::LevelEditorLeft,
|
|
||||||
// Action::LevelEditorRight,
|
|
||||||
// ]);
|
|
||||||
// },
|
|
||||||
// Times::default(),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// duration
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Test for execution speed for [`PlayerInputs`] get and get_many
|
|
||||||
// ///
|
|
||||||
// /// Example:
|
|
||||||
// /// ```
|
|
||||||
// /// cargo test --package pih-pah-app --lib --features "dev, ui_egui" -- controls::test::compare_player_inputs_get_and_get_many --exact --nocapture
|
|
||||||
// /// ```
|
|
||||||
// #[test]
|
|
||||||
// fn compare_player_inputs_get_and_get_many() {
|
|
||||||
// let get = player_inputs_get();
|
|
||||||
// let get_many = player_inputs_get_many();
|
|
||||||
|
|
||||||
// log::info!("get: {:?}", get);
|
|
||||||
// log::info!("get_many: {:?}", get_many);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// Test for execution speed for [`InputValue`] casting
|
|
||||||
// #[test]
|
|
||||||
// fn action_casting() {
|
|
||||||
// enable_loggings();
|
|
||||||
|
|
||||||
// let inputs = PlayerInputs::default();
|
|
||||||
|
|
||||||
// let duration = measure_time(
|
|
||||||
// || {
|
|
||||||
// let _ = (inputs.get(Action::LeverEditorForward).as_boolean() as i8
|
|
||||||
// - inputs.get(Action::LevelEditorBackward).as_boolean() as i8)
|
|
||||||
// as f32;
|
|
||||||
// },
|
|
||||||
// 10000000.into(),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// log::info!("with casting: {:?}", duration);
|
|
||||||
|
|
||||||
// let inputs = PlayerInputs::default();
|
|
||||||
|
|
||||||
// let duration = measure_time(
|
|
||||||
// || {
|
|
||||||
// let _ = inputs.get(Action::LeverEditorForward);
|
|
||||||
// let _ = inputs.get(Action::LevelEditorBackward);
|
|
||||||
// },
|
|
||||||
// 10000000.into(),
|
|
||||||
// );
|
|
||||||
|
|
||||||
// log::info!("without casting: {:?}", duration);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
Regular → Executable
+237
-53
@@ -1,13 +1,15 @@
|
|||||||
use std::error;
|
use std::error;
|
||||||
|
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::system::Resource;
|
use bevy_ecs::prelude::Resource;
|
||||||
use bevy_input::{keyboard::{KeyCode, ScanCode}, mouse::MouseButton};
|
#[cfg(feature = "logical-keyboard")]
|
||||||
|
use bevy_input::keyboard::Key;
|
||||||
|
use bevy_input::{keyboard::KeyCode, mouse::MouseButton};
|
||||||
#[cfg(feature = "inspector-egui")]
|
#[cfg(feature = "inspector-egui")]
|
||||||
use bevy_inspector_egui::prelude::*;
|
use bevy_inspector_egui::prelude::*;
|
||||||
|
use bevy_platform::collections::HashMap;
|
||||||
#[cfg(all(feature = "reflect", feature = "serialize"))]
|
#[cfg(all(feature = "reflect", feature = "serialize"))]
|
||||||
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||||
use bevy_utils::HashMap;
|
|
||||||
use log::warn;
|
use log::warn;
|
||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -44,27 +46,27 @@ where
|
|||||||
common_traits_conditions! {
|
common_traits_conditions! {
|
||||||
/// Struct that contains user's inputs corresponding to actions
|
/// Struct that contains user's inputs corresponding to actions
|
||||||
#[derive(Debug, PartialEq, Clone, Deref, DerefMut)]
|
#[derive(Debug, PartialEq, Clone, Deref, DerefMut)]
|
||||||
pub struct Inputs<A: Action>(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap<A, InputValue>);
|
pub struct Actions<A: Action>(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap<A, InputValue>);
|
||||||
|
|
||||||
/// Resource that contains current user inputs
|
/// Resource that contains current user inputs
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct PlayerInputs<A: Action> {
|
pub struct PlayerActions<A: Action> {
|
||||||
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
|
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
|
||||||
current: Inputs<A>,
|
current: Actions<A>,
|
||||||
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
|
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
|
||||||
previous: Inputs<A>,
|
previous: Actions<A>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Action> Default for PlayerInputs<A> {
|
impl<A: Action> Default for PlayerActions<A> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PlayerInputs {
|
PlayerActions {
|
||||||
current: Inputs(
|
current: Actions(
|
||||||
A::iter()
|
A::iter()
|
||||||
.map(|action| (action, InputValue::Empty))
|
.map(|action| (action, InputValue::Empty))
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
previous: Inputs(
|
previous: Actions(
|
||||||
A::iter()
|
A::iter()
|
||||||
.map(|action| (action, InputValue::Empty))
|
.map(|action| (action, InputValue::Empty))
|
||||||
.collect(),
|
.collect(),
|
||||||
@@ -73,7 +75,7 @@ impl<A: Action> Default for PlayerInputs<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Action> PlayerInputs<A> {
|
impl<A: Action> PlayerActions<A> {
|
||||||
/// Returns current input value for action
|
/// Returns current input value for action
|
||||||
///
|
///
|
||||||
/// in any case faster that `get_many` method
|
/// in any case faster that `get_many` method
|
||||||
@@ -119,15 +121,11 @@ impl<A: Action> PlayerInputs<A> {
|
|||||||
/// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame
|
/// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame
|
||||||
/// If input has not same type as `InputValue` on previous frame return `Error()`
|
/// If input has not same type as `InputValue` on previous frame return `Error()`
|
||||||
pub fn get_just_pressed(&self, action: A) -> Result<bool, Box<dyn error::Error>> {
|
pub fn get_just_pressed(&self, action: A) -> Result<bool, Box<dyn error::Error>> {
|
||||||
// SAFETY: action is always valid
|
return Ok(
|
||||||
// because we iterate over all actions in `default` method
|
self.current.get(&action).unwrap().to_boolean()
|
||||||
if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() {
|
&& !self.previous.get(&action).unwrap().to_boolean(),
|
||||||
if let InputValue::Boolean(previous) = *self.previous.get(&action).unwrap() {
|
);
|
||||||
return Ok(current && !previous);
|
//Err("This input is not boolean".into())
|
||||||
}
|
|
||||||
return Err("Previous input is not boolean".into());
|
|
||||||
}
|
|
||||||
Err("This input is not boolean".into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set input value to new
|
/// Set input value to new
|
||||||
@@ -168,7 +166,7 @@ impl<A: Action> PlayerInputs<A> {
|
|||||||
/// Update current inputs
|
/// Update current inputs
|
||||||
///
|
///
|
||||||
/// notice: not recomended to use if you do not know what do you do
|
/// notice: not recomended to use if you do not know what do you do
|
||||||
pub fn forced_update(&mut self, inputs: Inputs<A>) {
|
pub fn forced_update(&mut self, inputs: Actions<A>) {
|
||||||
*self.previous = self.current.clone().0;
|
*self.previous = self.current.clone().0;
|
||||||
*self.current = inputs.0;
|
*self.current = inputs.0;
|
||||||
}
|
}
|
||||||
@@ -178,7 +176,7 @@ impl<A: Action> PlayerInputs<A> {
|
|||||||
/// ! remember that it work safely but slow
|
/// ! remember that it work safely but slow
|
||||||
///
|
///
|
||||||
/// notice: not recomended to use if you do not know what do you do
|
/// notice: not recomended to use if you do not know what do you do
|
||||||
pub fn update(&mut self, inputs: Inputs<A>) -> Result<(), Box<dyn error::Error>> {
|
pub fn update(&mut self, inputs: Actions<A>) -> Result<(), Box<dyn error::Error>> {
|
||||||
// SAFETY: action is always valid
|
// SAFETY: action is always valid
|
||||||
// because we iterate over all actions in `default` method
|
// because we iterate over all actions in `default` method
|
||||||
for (action, value) in inputs.0.iter() {
|
for (action, value) in inputs.0.iter() {
|
||||||
@@ -196,48 +194,56 @@ impl<A: Action> PlayerInputs<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add option on exlude or not exclude if used
|
||||||
common_traits_conditions! {
|
common_traits_conditions! {
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
/// Keyboard input identified by physical location or logical meaning.
|
||||||
|
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||||
|
pub enum Keyboard {
|
||||||
|
KeyCode(KeyCode),
|
||||||
|
#[cfg(feature = "logical-keyboard")]
|
||||||
|
Key(Key),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||||
pub enum InputType {
|
pub enum InputType {
|
||||||
Keyboard(Keyboard),
|
Keyboard(Keyboard),
|
||||||
Mouse(MouseInput),
|
Mouse(MouseInput),
|
||||||
// TODO: Gamepad(GamepadButtonType),
|
// TODO: Gamepad(GamepadButtonType),
|
||||||
// TODO: Touch screen
|
// TODO: Touch screen
|
||||||
|
// TODO: TouchPad inputs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
common_traits_conditions! {
|
||||||
pub enum Keyboard {
|
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||||
KeyCode(KeyCode),
|
|
||||||
ScanCode(ScanCode),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
pub enum MouseInput {
|
pub enum MouseInput {
|
||||||
Button(MouseButton),
|
Button(MouseButton),
|
||||||
Axis(AxisName),
|
Axis(AxisName),
|
||||||
Wheel(AxisName)
|
Wheel(AxisName)
|
||||||
// TODO: CursorPosition
|
// TODO: CursorPosition
|
||||||
// TODO: TouchPad inputs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Name of axis
|
/// Name of axis
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||||
pub enum AxisName {
|
pub enum AxisName {
|
||||||
Horizontal,
|
Horizontal,
|
||||||
Vertical,
|
Vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a binding that can be changed by the player
|
/// Represents a binding that can be changed by the player
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
/// default: [`Immutable`](OptionsMode::Immutable)
|
||||||
|
#[derive(Debug, PartialEq, Clone, Default)]
|
||||||
pub enum OptionsMode {
|
pub enum OptionsMode {
|
||||||
/// Represents a binding that cannot be changed as it is crucial to the game logic
|
/// Represents a binding that cannot be changed as it is crucial to the game logic
|
||||||
|
#[default] // I think this is more safe
|
||||||
Immutable,
|
Immutable,
|
||||||
/// Represents a binding that the player can customize
|
/// Represents a binding that the player can customize
|
||||||
Customizable,
|
Customizable,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the manner in which input is registered
|
/// Represents the manner in which input is registered
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
/// default: [`Tap`](ActivationMode::Tap)
|
||||||
|
#[derive(Debug, PartialEq, Clone, Default)]
|
||||||
pub enum ActivationMode {
|
pub enum ActivationMode {
|
||||||
/// for<'a> Action<'a> is triggered when the button is pressed
|
/// for<'a> Action<'a> is triggered when the button is pressed
|
||||||
Hold,
|
Hold,
|
||||||
@@ -245,6 +251,7 @@ common_traits_conditions! {
|
|||||||
/// and cannot be triggered again until the button is released
|
/// and cannot be triggered again until the button is released
|
||||||
/// you don't need to release all buttons in chord
|
/// you don't need to release all buttons in chord
|
||||||
/// you can release only last pressed button
|
/// you can release only last pressed button
|
||||||
|
#[default] // I think this is more safe
|
||||||
Tap,
|
Tap,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,8 +269,8 @@ common_traits_conditions! {
|
|||||||
impl Default for ActivationOptions {
|
impl Default for ActivationOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
mode: ActivationMode::Tap, // Because it is more safe
|
mode: ActivationMode::default(),
|
||||||
option: OptionsMode::Immutable,
|
option: OptionsMode::default(),
|
||||||
delay: 0.05, // 20 times per second; 14 - world record?
|
delay: 0.05, // 20 times per second; 14 - world record?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,10 +297,21 @@ common_traits_conditions! {
|
|||||||
pub enum ButtonCombination {
|
pub enum ButtonCombination {
|
||||||
/// Single button press
|
/// Single button press
|
||||||
Single(InputType),
|
Single(InputType),
|
||||||
/// Chord is a combination of buttons that must be pressed at the same time
|
// /// Chord is a combination of buttons that must be pressed at the same time
|
||||||
Chord(Vec<InputType>),
|
// 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)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum BindingCondition<Gs: GameState> {
|
pub enum BindingCondition<Gs: GameState> {
|
||||||
/// During specific game state
|
/// During specific game state
|
||||||
@@ -328,6 +346,20 @@ impl<Gs: GameState> Binding<Gs> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_single(input: InputType) -> Self {
|
||||||
|
Self {
|
||||||
|
input: ButtonCombination::Single(input),
|
||||||
|
conditions: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn from_chord(input: Vec<InputType>) -> Self {
|
||||||
|
// Self {
|
||||||
|
// input: ButtonCombination::Chord(input),
|
||||||
|
// conditions: Vec::new(),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn with_condition(mut self, condition: BindingCondition<Gs>) -> Self {
|
pub fn with_condition(mut self, condition: BindingCondition<Gs>) -> Self {
|
||||||
self.conditions.push(condition);
|
self.conditions.push(condition);
|
||||||
self
|
self
|
||||||
@@ -395,6 +427,50 @@ impl InputValue {
|
|||||||
InputValue::Empty => false,
|
InputValue::Empty => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_float(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
InputValue::Boolean(value) => {
|
||||||
|
if *value {
|
||||||
|
1.
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputValue::Float(value) => *value,
|
||||||
|
InputValue::Empty => 0.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges another input value into this one.
|
||||||
|
///
|
||||||
|
/// `Empty` acts as an identity value. Boolean inputs are combined using
|
||||||
|
/// logical OR. For other non-empty values, the existing value is preserved.
|
||||||
|
///
|
||||||
|
/// This is used when multiple control bindings contribute to the same action.
|
||||||
|
pub(crate) fn merge(self, other: Self) -> Self {
|
||||||
|
match (self, other) {
|
||||||
|
// Empty doesn't affect an existing value
|
||||||
|
(Self::Empty, rhs) => rhs,
|
||||||
|
(lhs, Self::Empty) => lhs,
|
||||||
|
|
||||||
|
// Multiple boolean bindings combine with OR
|
||||||
|
(Self::Boolean(lhs), Self::Boolean(rhs)) => Self::Boolean(lhs || rhs),
|
||||||
|
|
||||||
|
// For floats choose max
|
||||||
|
// TODO: maybe user want to chose multiply same inputs of chose max
|
||||||
|
(Self::Float(lhs), Self::Float(rhs)) => {
|
||||||
|
if rhs.abs() > lhs.abs() {
|
||||||
|
Self::Float(rhs)
|
||||||
|
} else {
|
||||||
|
Self::Float(lhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(lhs, _) if !lhs.is_empty() => lhs,
|
||||||
|
(_, rhs) => rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
common_traits_conditions! {
|
common_traits_conditions! {
|
||||||
@@ -412,20 +488,44 @@ impl<Gs: GameState> Default for Bindings<Gs> {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
list: Vec::new(),
|
list: Vec::new(),
|
||||||
options: OptionsMode::Immutable,
|
options: OptionsMode::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Gs: GameState> Bindings<Gs> {
|
impl<Gs: GameState> Bindings<Gs> {
|
||||||
pub fn new(bindings: Vec<Binding<Gs>>, options: OptionsMode) -> Self {
|
pub fn new(bindings: Vec<Binding<Gs>>) -> Self {
|
||||||
Self {
|
let mut result = Self {
|
||||||
list: bindings,
|
list: Vec::with_capacity(bindings.len()),
|
||||||
options,
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// To avoid duplicates
|
||||||
|
for binding in bindings {
|
||||||
|
result.force_push(binding);
|
||||||
}
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn customizable(mut self) -> Self {
|
||||||
|
self.options = OptionsMode::Customizable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn immutable(mut self) -> Self {
|
||||||
|
self.options = OptionsMode::Immutable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_option_mode(mut self, options: OptionsMode) -> Self {
|
||||||
|
self.options = options;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn force_push(&mut self, binding: Binding<Gs>) {
|
pub fn force_push(&mut self, binding: Binding<Gs>) {
|
||||||
|
if self.list.contains(&binding) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.list.push(binding);
|
self.list.push(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,7 +534,7 @@ impl<Gs: GameState> Bindings<Gs> {
|
|||||||
warn!("You try to push binding to immutable bindings");
|
warn!("You try to push binding to immutable bindings");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.list.push(binding);
|
self.force_push(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
@@ -492,22 +592,60 @@ common_traits_conditions! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Gs: GameState> BindingConfig<Gs> {
|
impl<Gs: GameState> BindingConfig<Gs> {
|
||||||
pub fn new(bindings: Bindings<Gs>, activation: ActivationOptions) -> Self {
|
pub fn new(bindings: Bindings<Gs>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bindings,
|
bindings,
|
||||||
activation,
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_vec(bindings: Vec<Binding<Gs>>) -> Self {
|
||||||
|
Self {
|
||||||
|
bindings: Bindings::new(bindings),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bind(binding: Binding<Gs>) -> Self {
|
||||||
|
Self {
|
||||||
|
bindings: Bindings::new(vec![binding]),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_activation_options(mut self, activation: ActivationOptions) -> Self {
|
||||||
|
self.activation = activation;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deref, DerefMut)]
|
||||||
|
pub struct ControlsBuilder<A: Action, Gs: GameState>(Controls<A, Gs>);
|
||||||
|
|
||||||
|
impl<A: Action, Gs: GameState> ControlsBuilder<A, Gs> {
|
||||||
|
///
|
||||||
|
pub fn with(mut self, action: A, config: BindingConfig<Gs>) -> Self {
|
||||||
|
self.insert(action, config);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn build(mut self) -> Controls<A, Gs> {
|
||||||
|
// If you see this error, you may add new action in menu_actions
|
||||||
|
// 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) // TODO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
common_traits_conditions! {
|
common_traits_conditions! {
|
||||||
/// Contains all bindings for actions
|
/// Contains all bindings for actions
|
||||||
#[derive(Resource, Clone)]
|
#[derive(Resource, Clone, Deref, DerefMut)]
|
||||||
#[cfg_attr(feature = "reflect", reflect(Resource))]
|
#[cfg_attr(feature = "reflect", reflect(Resource))]
|
||||||
// TODO: Add delay for input
|
|
||||||
pub struct Controls<A: Action, Gs: GameState>(
|
pub struct Controls<A: Action, Gs: GameState>(
|
||||||
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
|
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
|
||||||
// TODO: change HashMap to Vec for faster iteration
|
// FIXME: change HashMap to Vec for faster iteration
|
||||||
HashMap<A, BindingConfig<Gs>>
|
HashMap<A, BindingConfig<Gs>>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -530,6 +668,11 @@ impl<A: Action, Gs: GameState> Default for Controls<A, Gs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
||||||
|
/// New [`Controls`] instance
|
||||||
|
pub fn new() -> ControlsBuilder<A, Gs> {
|
||||||
|
ControlsBuilder(Controls(HashMap::new()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns bindings for action
|
/// Returns bindings for action
|
||||||
pub fn get(&self, action: A) -> Option<&BindingConfig<Gs>> {
|
pub fn get(&self, action: A) -> Option<&BindingConfig<Gs>> {
|
||||||
self.0.get(&action)
|
self.0.get(&action)
|
||||||
@@ -548,7 +691,7 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push new binding for action
|
/// Push new binding for action
|
||||||
pub fn push(&mut self, action: A, binding: Binding<Gs>) {
|
pub fn push_binding(&mut self, action: A, binding: Binding<Gs>) {
|
||||||
// TODO: warning if config is not exist yet
|
// TODO: warning if config is not exist yet
|
||||||
// TODO: interface for [`ActivationOptions`]
|
// TODO: interface for [`ActivationOptions`]
|
||||||
self
|
self
|
||||||
@@ -560,7 +703,7 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove all bindings for action
|
/// Remove all bindings for action
|
||||||
pub fn remove(&mut self, action: A) {
|
pub fn clear_bindings(&mut self, action: A) {
|
||||||
// TODO: warning if config is not exist yet
|
// TODO: warning if config is not exist yet
|
||||||
self
|
self
|
||||||
.0
|
.0
|
||||||
@@ -570,9 +713,10 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
|||||||
.clear();
|
.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove binding for action
|
||||||
pub fn remove_binding(&mut self, action: A, binding: Binding<Gs>) {
|
pub fn remove_binding(&mut self, action: A, binding: Binding<Gs>) {
|
||||||
if let Some(config) = self.0.get_mut(&action) {
|
if let Some(config) = self.0.get_mut(&action) {
|
||||||
config.bindings.retain(|b| *b == binding);
|
config.bindings.retain(|b| *b != binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -580,3 +724,43 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
|||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
test,
|
||||||
|
feature = "logical-keyboard",
|
||||||
|
not(feature = "serialize"),
|
||||||
|
not(feature = "reflect"),
|
||||||
|
not(feature = "inspector-egui")
|
||||||
|
))]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use bevy_state::state::States;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keyboard_variants_are_distinct() {
|
||||||
|
let physical = InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyH));
|
||||||
|
let logical = InputType::Keyboard(Keyboard::Key(Key::Character("h".into())));
|
||||||
|
|
||||||
|
assert_ne!(physical, logical);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default)]
|
||||||
|
enum TestState {
|
||||||
|
#[default]
|
||||||
|
Playing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::contract::GameState for TestState {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bindings_reject_duplicates() {
|
||||||
|
let binding: Binding<TestState> =
|
||||||
|
Binding::from_single(InputType::Keyboard(Keyboard::KeyCode(KeyCode::KeyH)));
|
||||||
|
let mut bindings = Bindings::new(vec![binding.clone(), binding.clone()]).customizable();
|
||||||
|
|
||||||
|
bindings.push(binding.clone());
|
||||||
|
bindings.force_push(binding);
|
||||||
|
|
||||||
|
assert_eq!(bindings.iter().count(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/// Creates a [`HashMap`](bevy_utils::HashMap) from a list of key-value pairs.
|
/// Creates a [`HashMap`](std::collections::HashMap) from a list of key-value pairs.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! hashmap {
|
macro_rules! hashmap {
|
||||||
($( $key: expr => $val: expr ),*) => {{
|
($( $key: expr => $val: expr ),*) => {{
|
||||||
let mut map = HashMap::new();
|
let mut map = ::std::collections::HashMap::new();
|
||||||
$(
|
$(
|
||||||
map.insert($key, $val);
|
map.insert($key, $val);
|
||||||
)*
|
)*
|
||||||
|
|||||||
@@ -6,24 +6,7 @@ edition = "2021"
|
|||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
# [features]
|
|
||||||
# default = []
|
|
||||||
# serialize = ["bevy_input/serialize", "serde"]
|
|
||||||
# reflect = ["dep:bevy_reflect"]
|
|
||||||
# inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
|
|
||||||
|
|
||||||
[dependencies]
|
[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_reflect = { version = "0.12.1", optional = true }
|
|
||||||
# bevy_utils = "0.12.1"
|
|
||||||
# log = "0.4.20"
|
|
||||||
# serde = { version = "1.0.194", optional = true }
|
|
||||||
# strum = "0.25.0"
|
|
||||||
# strum_macros = "0.25.3"
|
|
||||||
bevy_controls = { path = "./../bevy_controls" }
|
bevy_controls = { path = "./../bevy_controls" }
|
||||||
syn = "1.0"
|
syn = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
use bevy_controls::contract::{Action, ActionInner};
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn;
|
use syn;
|
||||||
|
|
||||||
#[proc_macro_derive(Action)]
|
#[proc_macro_derive(Action)]
|
||||||
pub fn action(input: TokenStream) -> TokenStream {
|
pub fn action(input: TokenStream) -> TokenStream {
|
||||||
// Construct a representation of Rust code as a syntax tree
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
// that we can manipulate
|
|
||||||
let ast = syn::parse(input).unwrap();
|
|
||||||
|
|
||||||
// Build the trait implementation
|
|
||||||
impl_hello_macro(&ast)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|
||||||
let name = &ast.ident;
|
let name = &ast.ident;
|
||||||
let gen = quote! {
|
let gen = quote! {
|
||||||
impl Action for #name {}
|
impl bevy_controls::contract::Action for #name {}
|
||||||
impl ActionInner for #name {}
|
};
|
||||||
|
gen.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(GameState)]
|
||||||
|
pub fn game_state(input: TokenStream) -> TokenStream {
|
||||||
|
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||||
|
|
||||||
|
let name = &ast.ident;
|
||||||
|
let gen = quote! {
|
||||||
|
impl bevy_controls::contract::GameState for #name {}
|
||||||
};
|
};
|
||||||
gen.into()
|
gen.into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
{ inputs, flake, self, pkgs }:
|
||||||
|
let
|
||||||
|
rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ../rust-toolchain.toml;
|
||||||
|
runtimeLibraries = with pkgs; [
|
||||||
|
alsa-lib
|
||||||
|
vulkan-loader
|
||||||
|
udev
|
||||||
|
libx11
|
||||||
|
libxcursor
|
||||||
|
libxi
|
||||||
|
libxrandr
|
||||||
|
libxkbcommon
|
||||||
|
wayland
|
||||||
|
];
|
||||||
|
in {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
rustToolchain
|
||||||
|
stdenv.cc
|
||||||
|
git
|
||||||
|
pkg-config
|
||||||
|
shellcheck
|
||||||
|
file
|
||||||
|
openssl
|
||||||
|
] ++ runtimeLibraries;
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export RUST_SRC_PATH="${rustToolchain}/lib/rustlib/src/rust/library"
|
||||||
|
|
||||||
|
if [ -d /run/opengl-driver/lib ]; then
|
||||||
|
export LD_LIBRARY_PATH="/run/opengl-driver/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath runtimeLibraries}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||||
|
|
||||||
|
if git_root=$(${pkgs.git}/bin/git rev-parse --show-toplevel 2>/dev/null); then
|
||||||
|
export LOCAL_DIR="$git_root"
|
||||||
|
else
|
||||||
|
export LOCAL_DIR="$PWD"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "bevy_controls dev shell"
|
||||||
|
printf '%s\n' "Library: cargo check && cargo test"
|
||||||
|
printf '%s\n' "Example: cd crate/bevy_controls/example/basic && cargo run --features x11"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
Generated
+1238
-20
File diff suppressed because it is too large
Load Diff
@@ -1,38 +1,28 @@
|
|||||||
{
|
{
|
||||||
|
description = "Bevy controls library and examples";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
hearth = {
|
||||||
|
url = "git+https://gitea.hectic-lab.com/hinterland/hearth.git";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
rust-overlay = {
|
rust-overlay = {
|
||||||
url = "github:oxalica/rust-overlay";
|
url = "github:oxalica/rust-overlay";
|
||||||
inputs = {
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
nixpkgs.follows = "nixpkgs";
|
|
||||||
flake-utils.follows = "flake-utils";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
|
outputs = { self, hearth, ... }@inputs:
|
||||||
flake-utils.lib.eachDefaultSystem
|
|
||||||
(system:
|
|
||||||
let
|
let
|
||||||
overlays = [ (import rust-overlay) ];
|
flake = builtins.warn "Using the `flake` variable may produce a large source closure." ./.;
|
||||||
pkgs = import nixpkgs {
|
lib = import ./lib { inherit inputs flake self; };
|
||||||
inherit system overlays;
|
|
||||||
};
|
|
||||||
rustToolchain = (pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml);
|
|
||||||
nativeBuildInputs = with pkgs; [ rustToolchain pkg-config ];
|
|
||||||
buildInputs = with pkgs; [
|
|
||||||
openssl
|
|
||||||
shellcheck
|
|
||||||
file
|
|
||||||
];
|
|
||||||
in
|
in
|
||||||
with pkgs;
|
hearth.lib.forAllSystemsWithPkgs lib.composedOverlays ({ system, pkgs }: {
|
||||||
{
|
legacyPackages.${system} = pkgs;
|
||||||
devShells.default = mkShell {
|
packages.${system} = import ./package { inherit self inputs flake pkgs; };
|
||||||
inherit buildInputs nativeBuildInputs;
|
devShells.${system} = import ./devshell { inherit self inputs flake pkgs; };
|
||||||
|
}) // {
|
||||||
|
inherit lib;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
<h1 id="v0.1.0">v0.1.0</h1>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li><label><input type="checkbox" checked="" />contracts</label>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li><label><input type="checkbox" checked="" />loby<br />
|
||||||
|
</label></li>
|
||||||
|
<li><label><input type="checkbox" checked="" />actions<br />
|
||||||
|
</label></li>
|
||||||
|
<li><label><input type="checkbox" checked="" />game state<br />
|
||||||
|
</label></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><label><input type="checkbox" checked="" />way to define default
|
||||||
|
controlls</label></li>
|
||||||
|
<li><label><input type="checkbox" />layers for controlls: if on (shift +
|
||||||
|
space) action enable, ignore on (space) action</label></li>
|
||||||
|
<li><label><input type="checkbox" />warnings on actions
|
||||||
|
collision</label></li>
|
||||||
|
<li><label><input type="checkbox" />basic example</label></li>
|
||||||
|
</ul>
|
||||||
|
<h1 id="v0.2.0">v0.2.0</h1>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li><label><input type="checkbox" />layers for controlls: user
|
||||||
|
posibility give priority to controls</label></li>
|
||||||
|
<li><label><input type="checkbox" />controller support</label></li>
|
||||||
|
<li><label><input type="checkbox" />touch pad support</label></li>
|
||||||
|
<li><label><input type="checkbox" />posibility support more that one
|
||||||
|
game state</label></li>
|
||||||
|
</ul>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{ self, flake, inputs }:
|
||||||
|
let
|
||||||
|
overlays = with inputs; [
|
||||||
|
hearth.overlays.default
|
||||||
|
(import rust-overlay)
|
||||||
|
];
|
||||||
|
in {
|
||||||
|
nixpkgs-lib = inputs.nixpkgs.lib;
|
||||||
|
composedOverlays = overlays;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{ pkgs }:
|
||||||
|
let
|
||||||
|
rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ../../rust-toolchain.toml;
|
||||||
|
rustPlatform = pkgs.makeRustPlatform {
|
||||||
|
rustc = rustToolchain;
|
||||||
|
cargo = rustToolchain;
|
||||||
|
};
|
||||||
|
in rustPlatform.buildRustPackage {
|
||||||
|
pname = "bevy-controls";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = ../..;
|
||||||
|
|
||||||
|
cargoLock.lockFile = ../../Cargo.lock;
|
||||||
|
cargoBuildFlags = [ "--workspace" ];
|
||||||
|
cargoTestFlags = [ "--workspace" ];
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
openssl
|
||||||
|
alsa-lib
|
||||||
|
vulkan-loader
|
||||||
|
udev
|
||||||
|
libx11
|
||||||
|
libxcursor
|
||||||
|
libxi
|
||||||
|
libxrandr
|
||||||
|
libxkbcommon
|
||||||
|
wayland
|
||||||
|
];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p "$out"
|
||||||
|
touch "$out/workspace-built"
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
bevy-controls = import ./bevy-controls { inherit pkgs; };
|
||||||
|
default = import ./bevy-controls { inherit pkgs; };
|
||||||
|
}
|
||||||
Regular → Executable
+2
-1
@@ -1,2 +1,3 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = '1.72.0'
|
channel = '1.95.0'
|
||||||
|
components = ["rustfmt", "clippy", "rust-src"]
|
||||||
|
|||||||
Reference in New Issue
Block a user