feat: rebinding example

This commit is contained in:
2026-09-04 19:37:52 +00:00
parent f4268d7bef
commit a156490d4d
4 changed files with 4856 additions and 0 deletions
+4601
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -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
View File
@@ -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
View File
@@ -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,
);
}