feat: rebinding example
This commit is contained in:
+4601
File diff suppressed because it is too large
Load Diff
+30
@@ -0,0 +1,30 @@
|
||||
[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.16.1", default-features = false, features = [
|
||||
"bevy_asset",
|
||||
"bevy_text",
|
||||
"bevy_state",
|
||||
"bevy_ui",
|
||||
"bevy_window",
|
||||
"bevy_winit",
|
||||
"default_font",
|
||||
] }
|
||||
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.
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
use bevy::{
|
||||
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: 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: EventReader<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,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user