Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 036ef889e8 |
+1801
-1970
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
# 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
|
||||
Executable → Regular
+9
-11
@@ -5,24 +5,22 @@ edition = "2021"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
logical-keyboard = []
|
||||
serialize = ["bevy_input/serialize", "serde"]
|
||||
reflect = ["dep:bevy_reflect"]
|
||||
inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
|
||||
|
||||
[dependencies]
|
||||
bevy-inspector-egui = { version = "0.37", optional = true, default-features = false }
|
||||
bevy_app = "0.19.1"
|
||||
bevy_derive = "0.19.1"
|
||||
bevy_ecs = "0.19.1"
|
||||
bevy_input = { version = "0.19.1", features = ["keyboard", "mouse"] }
|
||||
bevy_state = "0.19.1"
|
||||
bevy_platform = "0.19.1"
|
||||
bevy_reflect = { version = "0.19.1", features = ["auto_register_static"], optional = true }
|
||||
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.26.2"
|
||||
strum_macros = "0.26.2"
|
||||
strum = "0.25.0"
|
||||
strum_macros = "0.25.3"
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.10.1"
|
||||
|
||||
+1370
-3086
File diff suppressed because it is too large
Load Diff
Executable → Regular
+7
-18
@@ -7,30 +7,19 @@ edition = "2021"
|
||||
target-dir = "../../../../target"
|
||||
|
||||
[features]
|
||||
x11 = ["bevy/x11"]
|
||||
wayland = ["bevy/wayland"]
|
||||
x11 = ["bevy/x11", "bevy/bevy_winit"]
|
||||
wayland = ["bevy/wayland", "bevy/bevy_winit"]
|
||||
windows = ["bevy/bevy_winit"]
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.19.1", default-features = false, features = [
|
||||
"std",
|
||||
"default_app",
|
||||
bevy = { version = "0.12.1", default-features = false, features = [
|
||||
"bevy_asset",
|
||||
"bevy_text",
|
||||
"bevy_state",
|
||||
"bevy_ui",
|
||||
"bevy_ui_render",
|
||||
"bevy_window",
|
||||
"bevy_winit",
|
||||
"dynamic_linking",
|
||||
"default_font",
|
||||
]}
|
||||
bevy_reflect = { version = "0.19.1", features = ["auto_register_static"] }
|
||||
bevy_controls = { path = "./../../", features = [
|
||||
"logical-keyboard",
|
||||
]}
|
||||
bevy_controls = { path = "./../../" }
|
||||
bevy_controls_derive = { path = "./../../../bevy_controls_derive" }
|
||||
strum_macros = "0.26.2"
|
||||
strum = "0.26.2"
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld.exe"
|
||||
strum = "0.25.0"
|
||||
strum_macros = "0.25.3"
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
Basic example
|
||||
|
||||
to run
|
||||
```bash
|
||||
cargo run --features '<x11|wayland|windows>'
|
||||
```
|
||||
+12
-9
@@ -5,11 +5,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"lastModified": 1705309234,
|
||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1788316716,
|
||||
"narHash": "sha256-bc7rSpXIdn9QWGNqfWcPZWOhEVF8NoeAZkWq0XWnf/k=",
|
||||
"lastModified": 1707092692,
|
||||
"narHash": "sha256-ZbHsm+mGk/izkWtT4xwwqz38fdlwu7nUUKXTOmm4SyE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3ed67ec0a4d3c7ab4ae1f04f8ee8df07bfa506a2",
|
||||
"rev": "faf912b086576fd1a15fca610166c98d47bc667e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -43,16 +43,19 @@
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788456718,
|
||||
"narHash": "sha256-TxHC0sBjV2pmsBLKAX02JTXlye/EOLt6Do+WNtGofGw=",
|
||||
"lastModified": 1707358215,
|
||||
"narHash": "sha256-Nuhi8KEJ2e+2nTimSyEIPqN5eh7ECVWd+AnPXG6L+SY=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "0b20a18e84d9164464739189c2b2a1b00f6f0e4b",
|
||||
"rev": "dd917bb1b67fc049fd56fe6de70266a9ab74a4aa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs = {
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -26,10 +27,10 @@
|
||||
|
||||
vulkan-loader
|
||||
|
||||
libx11
|
||||
libxrandr
|
||||
libxcursor
|
||||
libxi
|
||||
xorg.libX11
|
||||
xorg.libXrandr
|
||||
xorg.libXcursor
|
||||
xorg.libXi
|
||||
|
||||
wayland
|
||||
libxkbcommon
|
||||
|
||||
Executable → Regular
+1
-1
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = '1.95.0'
|
||||
channel = '1.72.0'
|
||||
|
||||
Executable → Regular
+163
-191
@@ -2,60 +2,55 @@
|
||||
//!
|
||||
//! It displays
|
||||
//!
|
||||
//! Yeah I know, that's kind of weird that it displays
|
||||
//! But it does.........
|
||||
//! Yeah I know, now it's a bit ugly
|
||||
|
||||
// TODO strum redefine in bevy_controls
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::input::keyboard::Key;
|
||||
use bevy::{ecs::schedule::States, prelude::*};
|
||||
use bevy_controls::{
|
||||
contract::InputsContainer,
|
||||
plugin::ControlsPlugin,
|
||||
resource::{
|
||||
ActivationMode, ActivationOptions, AxisName, Binding, BindingCondition, BindingConfig,
|
||||
Bindings, ButtonCombination, Controls, InputType, InputValue, Keyboard, MouseInput, OptionsMode,
|
||||
PlayerActions,
|
||||
contract::{
|
||||
Action, ActionInner, GameState, GameStateInner, InputsContainer, InputsContainerInner,
|
||||
},
|
||||
plugin::ControlsPlugin,
|
||||
resource::{Binding, ButtonCombination, Controls, InputType, InputValue, Keyboard, PlayerInputs},
|
||||
};
|
||||
use bevy_controls_derive::{Action, GameState};
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy, Debug, Action)]
|
||||
#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy, Debug, bevy_controls_derive::Action)]
|
||||
enum MyAction {
|
||||
Forward,
|
||||
Back,
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
MouseHorizontal,
|
||||
MouseVertical,
|
||||
}
|
||||
|
||||
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default, GameState)]
|
||||
#[derive(States, PartialEq, Eq, Clone, Hash, Debug, Default)]
|
||||
enum MyGameState {
|
||||
#[default]
|
||||
InGame,
|
||||
Menu,
|
||||
}
|
||||
|
||||
impl GameState for MyGameState {}
|
||||
|
||||
impl GameStateInner for MyGameState {}
|
||||
|
||||
#[derive(Resource, Default, Clone, Debug)]
|
||||
struct MyInputsContainer {
|
||||
// When the game does not provide multiplayer, one field is enough
|
||||
player_inputs: PlayerActions<MyAction>,
|
||||
player_inputs: PlayerInputs<MyAction>,
|
||||
}
|
||||
|
||||
impl InputsContainer<MyAction> for MyInputsContainer {
|
||||
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerActions<MyAction>> + 'a> {
|
||||
impl InputsContainer<MyAction> for MyInputsContainer {}
|
||||
|
||||
impl InputsContainerInner<MyAction> for MyInputsContainer {
|
||||
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerInputs<MyAction>> + 'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn me<'a>(&'a self) -> Option<&'a PlayerActions<MyAction>> {
|
||||
fn me<'a>(&'a self) -> Option<&'a PlayerInputs<MyAction>> {
|
||||
Some(&self.player_inputs)
|
||||
}
|
||||
|
||||
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerActions<MyAction>> {
|
||||
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerInputs<MyAction>> {
|
||||
Some(&mut self.player_inputs)
|
||||
}
|
||||
}
|
||||
@@ -66,198 +61,175 @@ struct JumpsCount(i32);
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
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(),
|
||||
),
|
||||
DefaultPlugins,
|
||||
ControlsPlugin::<MyAction, MyInputsContainer, MyGameState>::default(),
|
||||
))
|
||||
.insert_resource(ClearColor(Color::NONE))
|
||||
.init_resource::<JumpsCount>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Startup, (add_bindings, setup))
|
||||
.add_systems(Update, text_update_system)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
enum TextSpanKind {
|
||||
MoveLeft,
|
||||
MoveBack,
|
||||
MoveForward,
|
||||
MoveRight,
|
||||
Jump,
|
||||
DeltaX,
|
||||
DeltaY,
|
||||
// TODO: default bingings...
|
||||
fn add_bindings(mut controls: ResMut<Controls<MyAction, MyGameState>>) {
|
||||
controls.force_push(
|
||||
MyAction::Left,
|
||||
// this way input will work only on latin layout only on `A`
|
||||
Binding::new(ButtonCombination::Single(InputType::Keyboard(
|
||||
Keyboard::KeyCode(KeyCode::A),
|
||||
))),
|
||||
);
|
||||
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.;
|
||||
|
||||
fn text_span(text: &str, kind: TextSpanKind) -> (TextSpan, TextFont, TextColor, TextSpanKind) {
|
||||
(
|
||||
TextSpan::new(text),
|
||||
TextFont {
|
||||
font_size: FontSize::Px(TEXT_SIZE),
|
||||
..default()
|
||||
},
|
||||
TextColor(Color::WHITE),
|
||||
kind,
|
||||
)
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn(Camera2d);
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
Text::new(
|
||||
"Info: Important that WASD use physical key positions and HJKL use logical characters from current layout\nAction: ",
|
||||
commands.spawn((
|
||||
TextBundle::from_sections([
|
||||
TextSection::new(
|
||||
"\nAction: ",
|
||||
TextStyle {
|
||||
font_size: TEXT_SIZE,
|
||||
..default()
|
||||
},
|
||||
),
|
||||
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));
|
||||
});
|
||||
TextSection::new(
|
||||
"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
|
||||
const MOVE_LEFT_INDEX: usize = 1;
|
||||
const MOVE_BACK_INDEX: usize = 2;
|
||||
const MOVE_FORWARD_INDEX: usize = 3;
|
||||
const MOVE_RIGHT_INDEX: usize = 4;
|
||||
const JUMP_INDEX: usize = 5;
|
||||
|
||||
fn text_update_system(
|
||||
mut query: Query<(&TextSpanKind, &mut TextSpan, &mut TextColor)>,
|
||||
mut query: Query<&mut Text, With<TextBlock>>,
|
||||
inputs_container: Res<MyInputsContainer>,
|
||||
mut jumps_count: ResMut<JumpsCount>,
|
||||
) {
|
||||
for (text_span_kind, mut text_span, mut text_color) in &mut query {
|
||||
for text in query.iter_mut() {
|
||||
let player_inputs = inputs_container.me().expect("This is bad");
|
||||
match *text_span_kind {
|
||||
TextSpanKind::MoveLeft => {
|
||||
update_text_color(&mut text_color, player_inputs.get(MyAction::Left))
|
||||
}
|
||||
TextSpanKind::MoveBack => {
|
||||
update_text_color(&mut text_color, player_inputs.get(MyAction::Back))
|
||||
}
|
||||
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
|
||||
.get_just_pressed(MyAction::Up)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
**jumps_count += 1;
|
||||
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);
|
||||
}
|
||||
let mut text = update_text(text, player_inputs.get(MyAction::Left), MOVE_LEFT_INDEX);
|
||||
text = update_text(text, player_inputs.get(MyAction::Back), MOVE_BACK_INDEX);
|
||||
text = update_text(
|
||||
text,
|
||||
player_inputs.get(MyAction::Forward),
|
||||
MOVE_FORWARD_INDEX,
|
||||
);
|
||||
text = update_text(text, player_inputs.get(MyAction::Right), MOVE_RIGHT_INDEX);
|
||||
if player_inputs
|
||||
.get_just_pressed(MyAction::Up)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
**jumps_count += 1;
|
||||
text.sections[JUMP_INDEX].value = format!("Up (Space) - {}", **jumps_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_text_color(text_color: &mut TextColor, input_value: InputValue) {
|
||||
fn update_text(mut text: Mut<Text>, input_value: InputValue, index: usize) -> Mut<Text> {
|
||||
if input_value.is_boolean() && input_value.to_boolean() {
|
||||
text_color.0 = Color::linear_rgb(0.855, 0.647, 0.125);
|
||||
text.sections[index].style.color = Color::GOLD;
|
||||
} else {
|
||||
text_color.0 = Color::WHITE;
|
||||
text.sections[index].style.color = Color::WHITE;
|
||||
}
|
||||
|
||||
text
|
||||
}
|
||||
|
||||
-5128
File diff suppressed because it is too large
Load Diff
@@ -1,34 +0,0 @@
|
||||
[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"
|
||||
@@ -1,18 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,208 +0,0 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
[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"
|
||||
@@ -1,114 +0,0 @@
|
||||
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
|
||||
case $1 in
|
||||
-h|--help|help)
|
||||
-h|-?|--help|help)
|
||||
HELP=1
|
||||
;;
|
||||
-*)
|
||||
|
||||
Executable → Regular
+228
-62
@@ -1,82 +1,248 @@
|
||||
use std::hash::Hash;
|
||||
|
||||
use bevy_ecs::{component::Mutable, prelude::Resource};
|
||||
use bevy_ecs::{schedule::States, system::Resource};
|
||||
#[cfg(feature = "inspector-egui")]
|
||||
use bevy_inspector_egui::inspector_options::InspectorOptionsType;
|
||||
use strum::IntoEnumIterator;
|
||||
// #[cfg(all(feature = "reflect", feature = "serialize"))]
|
||||
// use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
#[cfg(feature = "reflect")]
|
||||
use bevy_reflect::{FromReflect, Reflect, TypePath};
|
||||
use bevy_state::state::{FreelyMutableState, States};
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::resource::PlayerActions;
|
||||
use crate::resource::PlayerInputs;
|
||||
|
||||
macro_rules! define_contracts {
|
||||
($($feature_bounds:tt)*) => {
|
||||
pub trait Action:
|
||||
'static
|
||||
+ std::marker::Sync
|
||||
+ std::marker::Send
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Hash
|
||||
+ IntoEnumIterator
|
||||
+ Clone
|
||||
+ Copy
|
||||
+ std::fmt::Debug
|
||||
$($feature_bounds)*
|
||||
{
|
||||
}
|
||||
|
||||
pub trait GameState:
|
||||
'static
|
||||
+ std::marker::Sync
|
||||
+ std::marker::Send
|
||||
+ States
|
||||
+ Default
|
||||
+ std::fmt::Debug
|
||||
+ FreelyMutableState
|
||||
$($feature_bounds)*
|
||||
{
|
||||
}
|
||||
|
||||
pub trait InputsContainer<A: Action>:
|
||||
'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.
|
||||
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerActions<A>> + 'a>;
|
||||
/// The method returning an player inputs of this client.
|
||||
fn me<'a>(&'a self) -> Option<&'a PlayerActions<A>>;
|
||||
/// The method returning an mutable player inputs of this client.
|
||||
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerActions<A>>;
|
||||
}
|
||||
};
|
||||
pub trait ActionInner:
|
||||
'static
|
||||
+ std::marker::Sync
|
||||
+ std::marker::Send
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ Hash
|
||||
+ IntoEnumIterator
|
||||
+ Clone
|
||||
+ Copy
|
||||
+ std::fmt::Debug
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "serialize"), not(feature = "reflect")))]
|
||||
define_contracts!();
|
||||
#[cfg(all(feature = "serialize", not(feature = "reflect")))]
|
||||
define_contracts!(+ Serialize + DeserializeOwned);
|
||||
pub trait GameStateInner:
|
||||
'static + std::marker::Sync + std::marker::Send + States + Default + std::fmt::Debug
|
||||
{
|
||||
}
|
||||
|
||||
pub trait InputsContainerInner<A: Action>:
|
||||
'static + std::marker::Sync + std::marker::Send + Resource + Default + std::fmt::Debug
|
||||
{
|
||||
/// The method returning an iterator over references to the PlayerInputs.
|
||||
/// Note: the use of 'a for both the method's lifetime and the returned Iterator's lifetime.
|
||||
fn iter_inputs<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PlayerInputs<A>> + 'a>;
|
||||
/// The method returning an player inputs of this client.
|
||||
fn me<'a>(&'a self) -> Option<&'a PlayerInputs<A>>;
|
||||
/// The method returning an mutable player inputs of this client.
|
||||
fn me_mut<'a>(&'a mut self) -> Option<&'a mut PlayerInputs<A>>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
pub trait SerializeImpl: Serialize + DeserializeOwned {}
|
||||
|
||||
#[cfg(feature = "reflect")]
|
||||
pub trait ReflectImpl: Reflect + TypePath + FromReflect {}
|
||||
|
||||
#[cfg(feature = "inspector-egui")]
|
||||
pub trait InspectorEguiImpl: InspectorOptionsType {}
|
||||
|
||||
// ===== Action =====
|
||||
// SAFETY: inspector-egui includes reflect
|
||||
#[cfg(all(
|
||||
feature = "reflect",
|
||||
not(feature = "serialize"),
|
||||
not(feature = "reflect"),
|
||||
not(feature = "inspector-egui")
|
||||
))]
|
||||
define_contracts!(+ Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed);
|
||||
pub trait Action: ActionInner {}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "reflect",
|
||||
feature = "serialize",
|
||||
not(feature = "reflect"),
|
||||
not(feature = "inspector-egui")
|
||||
))]
|
||||
define_contracts!(+ Serialize + DeserializeOwned + Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed);
|
||||
#[cfg(all(feature = "inspector-egui", not(feature = "serialize")))]
|
||||
define_contracts!(+ Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed + InspectorOptionsType);
|
||||
#[cfg(all(feature = "inspector-egui", feature = "serialize"))]
|
||||
define_contracts!(+ Serialize + DeserializeOwned + Reflect + TypePath + FromReflect + bevy_reflect::GetTypeRegistration + bevy_reflect::Typed + InspectorOptionsType);
|
||||
pub trait Action: ActionInner + SerializeImpl {}
|
||||
|
||||
#[cfg(all(
|
||||
not(feature = "serialize"),
|
||||
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 = "reflect"),
|
||||
not(feature = "inspector-egui")
|
||||
))]
|
||||
pub trait GameState: GameStateInner {}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "serialize",
|
||||
not(feature = "reflect"),
|
||||
not(feature = "inspector-egui")
|
||||
))]
|
||||
pub trait GameState: GameStateInner + SerializeImpl {}
|
||||
|
||||
#[cfg(all(
|
||||
not(feature = "serialize"),
|
||||
feature = "reflect",
|
||||
not(feature = "inspector-egui")
|
||||
))]
|
||||
pub trait GameState: GameStateInner + ReflectImpl {}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "serialize",
|
||||
feature = "reflect",
|
||||
not(feature = "inspector-egui")
|
||||
))]
|
||||
pub trait GameState: GameStateInner + SerializeImpl + ReflectImpl {}
|
||||
|
||||
#[cfg(all(not(feature = "serialize"), feature = "inspector-egui"))]
|
||||
pub trait GameState: GameStateInner + ReflectImpl + InspectorEguiImpl {}
|
||||
|
||||
#[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,3 +2,5 @@ pub mod contract;
|
||||
pub mod plugin;
|
||||
pub mod resource;
|
||||
mod util;
|
||||
|
||||
pub use util::*;
|
||||
|
||||
Executable → Regular
+214
-200
@@ -1,108 +1,38 @@
|
||||
use bevy_app::{App, Plugin, PreUpdate};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_app::{App, Plugin, Update};
|
||||
use bevy_ecs::{
|
||||
message::MessageReader,
|
||||
prelude::Resource,
|
||||
system::{In, IntoSystem, Res, ResMut},
|
||||
schedule::State,
|
||||
system::{Res, ResMut},
|
||||
};
|
||||
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 bevy_input::{keyboard::{KeyCode, ScanCode}, mouse::MouseButton, Input};
|
||||
|
||||
use crate::{
|
||||
contract::{Action, GameState, InputsContainer},
|
||||
resource::*,
|
||||
};
|
||||
|
||||
pub struct ControlsPlugin<A: Action, Ic: InputsContainer<A>, Gs: GameState> {
|
||||
pub struct ControlsPlugin<A, Ic, Gs> {
|
||||
_action: std::marker::PhantomData<A>,
|
||||
_inputs_container: std::marker::PhantomData<Ic>,
|
||||
_game_state: std::marker::PhantomData<Gs>,
|
||||
controls: Controls<A, Gs>,
|
||||
}
|
||||
|
||||
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> Default for ControlsPlugin<A, Ic, Gs> {
|
||||
impl<A, Ic, Gs> Default for ControlsPlugin<A, Ic, Gs> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
_action: std::marker::PhantomData,
|
||||
_inputs_container: 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> {
|
||||
fn build(&self, app: &mut App) {
|
||||
app
|
||||
.insert_resource(self.controls.clone())
|
||||
.init_resource::<Controls<A, Gs>>()
|
||||
.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")]
|
||||
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),
|
||||
);
|
||||
.add_state::<Gs>()
|
||||
.add_systems(Update, Self::save_input);
|
||||
|
||||
#[cfg(feature = "reflect")]
|
||||
app.register_type::<Controls<A, Gs>>();
|
||||
@@ -110,139 +40,223 @@ where
|
||||
}
|
||||
|
||||
impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs> {
|
||||
/// Collects input events from various input devices
|
||||
/// (keyboard, mouse buttons, mouse motion, and scroll)
|
||||
/// and stores them in a `TrigeredInputs` structure.
|
||||
///
|
||||
/// 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>,
|
||||
/// Process all hard inputs and bindings to update [`PlayerInputs`]
|
||||
fn save_input(
|
||||
keyboard_input_keycode: Res<Input<KeyCode>>,
|
||||
keyboard_input_scancode: Res<Input<ScanCode>>,
|
||||
mouse_input: Res<Input<MouseButton>>,
|
||||
mut inputs_container: ResMut<Ic>,
|
||||
controls: Res<Controls<A, Gs>>,
|
||||
game_state: Res<State<Gs>>,
|
||||
) {
|
||||
// Check if the input container is accessible
|
||||
if let Some(inputs) = inputs_container.me_mut() {
|
||||
for (action, config) in controls.iter() {
|
||||
let mut action_value = InputValue::Empty;
|
||||
|
||||
// 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 {
|
||||
ButtonCombination::Single(input_type) => {
|
||||
if let Some(&value) = collected_inputs.get(&*input_type) {
|
||||
action_value = action_value.merge(value);
|
||||
'bindings_loop: for binding in config.bindings.iter() {
|
||||
for condition in &binding.conditions {
|
||||
match condition {
|
||||
BindingCondition::InGameState(state) => {
|
||||
if *state != *game_state.get() {
|
||||
continue 'bindings_loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inputs.forced_set(*action, action_value);
|
||||
log::trace!("action binding {:?} {:?} in condition", action, binding);
|
||||
|
||||
match &binding.input {
|
||||
ButtonCombination::Single(button) => match button {
|
||||
InputType::Keyboard(Keyboard::KeyCode(key)) => {
|
||||
log::trace!("presed?: {:?}", keyboard_input_keycode.pressed(*key));
|
||||
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");
|
||||
// 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 {
|
||||
log::warn!("cannot find me in inputs container");
|
||||
log::error!("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);
|
||||
// }
|
||||
// }
|
||||
|
||||
Executable → Regular
+56
-240
@@ -1,15 +1,13 @@
|
||||
use std::error;
|
||||
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::prelude::Resource;
|
||||
#[cfg(feature = "logical-keyboard")]
|
||||
use bevy_input::keyboard::Key;
|
||||
use bevy_input::{keyboard::KeyCode, mouse::MouseButton};
|
||||
use bevy_ecs::system::Resource;
|
||||
use bevy_input::{keyboard::{KeyCode, ScanCode}, mouse::MouseButton};
|
||||
#[cfg(feature = "inspector-egui")]
|
||||
use bevy_inspector_egui::prelude::*;
|
||||
use bevy_platform::collections::HashMap;
|
||||
#[cfg(all(feature = "reflect", feature = "serialize"))]
|
||||
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
use bevy_utils::HashMap;
|
||||
use log::warn;
|
||||
#[cfg(feature = "serialize")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -46,27 +44,27 @@ where
|
||||
common_traits_conditions! {
|
||||
/// Struct that contains user's inputs corresponding to actions
|
||||
#[derive(Debug, PartialEq, Clone, Deref, DerefMut)]
|
||||
pub struct Actions<A: Action>(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap<A, InputValue>);
|
||||
pub struct Inputs<A: Action>(#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] HashMap<A, InputValue>);
|
||||
|
||||
/// Resource that contains current user inputs
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct PlayerActions<A: Action> {
|
||||
pub struct PlayerInputs<A: Action> {
|
||||
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
|
||||
current: Actions<A>,
|
||||
current: Inputs<A>,
|
||||
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
|
||||
previous: Actions<A>,
|
||||
previous: Inputs<A>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Action> Default for PlayerActions<A> {
|
||||
impl<A: Action> Default for PlayerInputs<A> {
|
||||
fn default() -> Self {
|
||||
PlayerActions {
|
||||
current: Actions(
|
||||
PlayerInputs {
|
||||
current: Inputs(
|
||||
A::iter()
|
||||
.map(|action| (action, InputValue::Empty))
|
||||
.collect(),
|
||||
),
|
||||
previous: Actions(
|
||||
previous: Inputs(
|
||||
A::iter()
|
||||
.map(|action| (action, InputValue::Empty))
|
||||
.collect(),
|
||||
@@ -75,7 +73,7 @@ impl<A: Action> Default for PlayerActions<A> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Action> PlayerActions<A> {
|
||||
impl<A: Action> PlayerInputs<A> {
|
||||
/// Returns current input value for action
|
||||
///
|
||||
/// in any case faster that `get_many` method
|
||||
@@ -121,11 +119,15 @@ impl<A: Action> PlayerActions<A> {
|
||||
/// 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()`
|
||||
pub fn get_just_pressed(&self, action: A) -> Result<bool, Box<dyn error::Error>> {
|
||||
return Ok(
|
||||
self.current.get(&action).unwrap().to_boolean()
|
||||
&& !self.previous.get(&action).unwrap().to_boolean(),
|
||||
);
|
||||
//Err("This input is not boolean".into())
|
||||
// SAFETY: action is always valid
|
||||
// because we iterate over all actions in `default` method
|
||||
if let InputValue::Boolean(current) = *self.current.get(&action).unwrap() {
|
||||
if let InputValue::Boolean(previous) = *self.previous.get(&action).unwrap() {
|
||||
return Ok(current && !previous);
|
||||
}
|
||||
return Err("Previous input is not boolean".into());
|
||||
}
|
||||
Err("This input is not boolean".into())
|
||||
}
|
||||
|
||||
/// Set input value to new
|
||||
@@ -166,7 +168,7 @@ impl<A: Action> PlayerActions<A> {
|
||||
/// Update current inputs
|
||||
///
|
||||
/// notice: not recomended to use if you do not know what do you do
|
||||
pub fn forced_update(&mut self, inputs: Actions<A>) {
|
||||
pub fn forced_update(&mut self, inputs: Inputs<A>) {
|
||||
*self.previous = self.current.clone().0;
|
||||
*self.current = inputs.0;
|
||||
}
|
||||
@@ -176,7 +178,7 @@ impl<A: Action> PlayerActions<A> {
|
||||
/// ! remember that it work safely but slow
|
||||
///
|
||||
/// notice: not recomended to use if you do not know what do you do
|
||||
pub fn update(&mut self, inputs: Actions<A>) -> Result<(), Box<dyn error::Error>> {
|
||||
pub fn update(&mut self, inputs: Inputs<A>) -> Result<(), Box<dyn error::Error>> {
|
||||
// SAFETY: action is always valid
|
||||
// because we iterate over all actions in `default` method
|
||||
for (action, value) in inputs.0.iter() {
|
||||
@@ -194,56 +196,48 @@ impl<A: Action> PlayerActions<A> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add option on exlude or not exclude if used
|
||||
common_traits_conditions! {
|
||||
/// 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)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum InputType {
|
||||
Keyboard(Keyboard),
|
||||
Mouse(MouseInput),
|
||||
// TODO: Gamepad(GamepadButtonType),
|
||||
// TODO: Touch screen
|
||||
// TODO: TouchPad inputs
|
||||
}
|
||||
}
|
||||
// TODO: Gamepad(GamepadButtonType),
|
||||
// TODO: Touch screen
|
||||
}
|
||||
|
||||
common_traits_conditions! {
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Keyboard {
|
||||
KeyCode(KeyCode),
|
||||
ScanCode(ScanCode),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum MouseInput {
|
||||
Button(MouseButton),
|
||||
Axis(AxisName),
|
||||
Wheel(AxisName)
|
||||
// TODO: CursorPosition
|
||||
// TODO: TouchPad inputs
|
||||
}
|
||||
|
||||
/// Name of axis
|
||||
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum AxisName {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
/// Represents a binding that can be changed by the player
|
||||
/// default: [`Immutable`](OptionsMode::Immutable)
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum OptionsMode {
|
||||
/// Represents a binding that cannot be changed as it is crucial to the game logic
|
||||
#[default] // I think this is more safe
|
||||
Immutable,
|
||||
/// Represents a binding that the player can customize
|
||||
Customizable,
|
||||
}
|
||||
|
||||
/// Represents the manner in which input is registered
|
||||
/// default: [`Tap`](ActivationMode::Tap)
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ActivationMode {
|
||||
/// for<'a> Action<'a> is triggered when the button is pressed
|
||||
Hold,
|
||||
@@ -251,7 +245,6 @@ common_traits_conditions! {
|
||||
/// and cannot be triggered again until the button is released
|
||||
/// you don't need to release all buttons in chord
|
||||
/// you can release only last pressed button
|
||||
#[default] // I think this is more safe
|
||||
Tap,
|
||||
}
|
||||
|
||||
@@ -269,8 +262,8 @@ common_traits_conditions! {
|
||||
impl Default for ActivationOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: ActivationMode::default(),
|
||||
option: OptionsMode::default(),
|
||||
mode: ActivationMode::Tap, // Because it is more safe
|
||||
option: OptionsMode::Immutable,
|
||||
delay: 0.05, // 20 times per second; 14 - world record?
|
||||
}
|
||||
}
|
||||
@@ -297,21 +290,10 @@ common_traits_conditions! {
|
||||
pub enum ButtonCombination {
|
||||
/// Single button press
|
||||
Single(InputType),
|
||||
// /// Chord is a combination of buttons that must be pressed at the same time
|
||||
// Chord(Vec<InputType>),
|
||||
/// Chord is a combination of buttons that must be pressed at the same time
|
||||
Chord(Vec<InputType>),
|
||||
}
|
||||
}
|
||||
|
||||
impl ButtonCombination {
|
||||
pub fn len(&self) -> usize {
|
||||
use crate::resource::ButtonCombination::*;
|
||||
match self {
|
||||
Single(_) => 1, // TODO: Chord
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
common_traits_conditions! {
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum BindingCondition<Gs: GameState> {
|
||||
/// During specific game state
|
||||
@@ -346,20 +328,6 @@ 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 {
|
||||
self.conditions.push(condition);
|
||||
self
|
||||
@@ -427,50 +395,6 @@ impl InputValue {
|
||||
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! {
|
||||
@@ -488,44 +412,20 @@ impl<Gs: GameState> Default for Bindings<Gs> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
list: Vec::new(),
|
||||
options: OptionsMode::default(),
|
||||
options: OptionsMode::Immutable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Gs: GameState> Bindings<Gs> {
|
||||
pub fn new(bindings: Vec<Binding<Gs>>) -> Self {
|
||||
let mut result = Self {
|
||||
list: Vec::with_capacity(bindings.len()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// To avoid duplicates
|
||||
for binding in bindings {
|
||||
result.force_push(binding);
|
||||
pub fn new(bindings: Vec<Binding<Gs>>, options: OptionsMode) -> Self {
|
||||
Self {
|
||||
list: bindings,
|
||||
options,
|
||||
}
|
||||
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>) {
|
||||
if self.list.contains(&binding) {
|
||||
return;
|
||||
}
|
||||
self.list.push(binding);
|
||||
}
|
||||
|
||||
@@ -534,7 +434,7 @@ impl<Gs: GameState> Bindings<Gs> {
|
||||
warn!("You try to push binding to immutable bindings");
|
||||
return;
|
||||
}
|
||||
self.force_push(binding);
|
||||
self.list.push(binding);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
@@ -592,60 +492,22 @@ common_traits_conditions! {
|
||||
}
|
||||
|
||||
impl<Gs: GameState> BindingConfig<Gs> {
|
||||
pub fn new(bindings: Bindings<Gs>) -> Self {
|
||||
pub fn new(bindings: Bindings<Gs>, activation: ActivationOptions) -> Self {
|
||||
Self {
|
||||
bindings,
|
||||
..Default::default()
|
||||
activation,
|
||||
}
|
||||
}
|
||||
|
||||
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! {
|
||||
/// Contains all bindings for actions
|
||||
#[derive(Resource, Clone, Deref, DerefMut)]
|
||||
#[derive(Resource, Clone)]
|
||||
#[cfg_attr(feature = "reflect", reflect(Resource))]
|
||||
// TODO: Add delay for input
|
||||
pub struct Controls<A: Action, Gs: GameState>(
|
||||
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
|
||||
// FIXME: change HashMap to Vec for faster iteration
|
||||
// TODO: change HashMap to Vec for faster iteration
|
||||
HashMap<A, BindingConfig<Gs>>
|
||||
);
|
||||
}
|
||||
@@ -668,11 +530,6 @@ impl<A: Action, Gs: GameState> Default for 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
|
||||
pub fn get(&self, action: A) -> Option<&BindingConfig<Gs>> {
|
||||
self.0.get(&action)
|
||||
@@ -691,7 +548,7 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
||||
}
|
||||
|
||||
/// Push new binding for action
|
||||
pub fn push_binding(&mut self, action: A, binding: Binding<Gs>) {
|
||||
pub fn push(&mut self, action: A, binding: Binding<Gs>) {
|
||||
// TODO: warning if config is not exist yet
|
||||
// TODO: interface for [`ActivationOptions`]
|
||||
self
|
||||
@@ -703,7 +560,7 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
||||
}
|
||||
|
||||
/// Remove all bindings for action
|
||||
pub fn clear_bindings(&mut self, action: A) {
|
||||
pub fn remove(&mut self, action: A) {
|
||||
// TODO: warning if config is not exist yet
|
||||
self
|
||||
.0
|
||||
@@ -713,10 +570,9 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
||||
.clear();
|
||||
}
|
||||
|
||||
/// Remove binding for action
|
||||
pub fn remove_binding(&mut self, action: A, binding: Binding<Gs>) {
|
||||
if let Some(config) = self.0.get_mut(&action) {
|
||||
config.bindings.retain(|b| *b != binding);
|
||||
config.bindings.retain(|b| *b == binding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,43 +580,3 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
|
||||
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`](std::collections::HashMap) from a list of key-value pairs.
|
||||
/// Creates a [`HashMap`](bevy_utils::HashMap) from a list of key-value pairs.
|
||||
///
|
||||
/// Example:
|
||||
/// ```ignore
|
||||
@@ -9,7 +9,7 @@
|
||||
#[macro_export]
|
||||
macro_rules! hashmap {
|
||||
($( $key: expr => $val: expr ),*) => {{
|
||||
let mut map = ::std::collections::HashMap::new();
|
||||
let mut map = HashMap::new();
|
||||
$(
|
||||
map.insert($key, $val);
|
||||
)*
|
||||
|
||||
@@ -6,7 +6,24 @@ edition = "2021"
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
# [features]
|
||||
# default = []
|
||||
# serialize = ["bevy_input/serialize", "serde"]
|
||||
# reflect = ["dep:bevy_reflect"]
|
||||
# inspector-egui = ["reflect", "dep:bevy-inspector-egui"]
|
||||
|
||||
[dependencies]
|
||||
# bevy-inspector-egui = { version = "0.22.1", optional = true }
|
||||
# bevy_app = "0.12.1"
|
||||
# bevy_derive = "0.12.1"
|
||||
# bevy_ecs = "0.12.1"
|
||||
# bevy_input = "0.12.1"
|
||||
# bevy_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" }
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use bevy_controls::contract::{Action, ActionInner};
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn;
|
||||
|
||||
#[proc_macro_derive(Action)]
|
||||
pub fn action(input: TokenStream) -> TokenStream {
|
||||
let ast = syn::parse::<syn::DeriveInput>(input).unwrap();
|
||||
// Construct a representation of Rust code as a syntax tree
|
||||
// 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 gen = quote! {
|
||||
impl bevy_controls::contract::Action 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 {}
|
||||
impl Action for #name {}
|
||||
impl ActionInner for #name {}
|
||||
};
|
||||
gen.into()
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
{ 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
+16
-1234
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,38 @@
|
||||
{
|
||||
description = "Bevy controls library and examples";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
hearth = {
|
||||
url = "git+https://gitea.hectic-lab.com/hinterland/hearth.git";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
rust-overlay = {
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs = {
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, hearth, ... }@inputs:
|
||||
let
|
||||
flake = builtins.warn "Using the `flake` variable may produce a large source closure." ./.;
|
||||
lib = import ./lib { inherit inputs flake self; };
|
||||
in
|
||||
hearth.lib.forAllSystemsWithPkgs lib.composedOverlays ({ system, pkgs }: {
|
||||
legacyPackages.${system} = pkgs;
|
||||
packages.${system} = import ./package { inherit self inputs flake pkgs; };
|
||||
devShells.${system} = import ./devshell { inherit self inputs flake pkgs; };
|
||||
}) // {
|
||||
inherit lib;
|
||||
};
|
||||
outputs = { self, nixpkgs, flake-utils, rust-overlay }:
|
||||
flake-utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let
|
||||
overlays = [ (import rust-overlay) ];
|
||||
pkgs = import nixpkgs {
|
||||
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
|
||||
with pkgs;
|
||||
{
|
||||
devShells.default = mkShell {
|
||||
inherit buildInputs nativeBuildInputs;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
<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>
|
||||
@@ -1,10 +0,0 @@
|
||||
{ self, flake, inputs }:
|
||||
let
|
||||
overlays = with inputs; [
|
||||
hearth.overlays.default
|
||||
(import rust-overlay)
|
||||
];
|
||||
in {
|
||||
nixpkgs-lib = inputs.nixpkgs.lib;
|
||||
composedOverlays = overlays;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{ 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"
|
||||
'';
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
bevy-controls = import ./bevy-controls { inherit pkgs; };
|
||||
default = import ./bevy-controls { inherit pkgs; };
|
||||
}
|
||||
Executable → Regular
+1
-2
@@ -1,3 +1,2 @@
|
||||
[toolchain]
|
||||
channel = '1.95.0'
|
||||
components = ["rustfmt", "clippy", "rust-src"]
|
||||
channel = '1.72.0'
|
||||
|
||||
Reference in New Issue
Block a user