docs: simplest example

This commit is contained in:
2024-02-11 13:54:49 +01:00
parent 608781faf6
commit 2ecced8496
4 changed files with 106 additions and 68 deletions
+79 -63
View File
@@ -14,22 +14,19 @@ use bevy_controls::{
Action, ActionInner, GameState, GameStateInner, InputsContainer, InputsContainerInner, Action, ActionInner, GameState, GameStateInner, InputsContainer, InputsContainerInner,
}, },
plugin::ControlsPlugin, plugin::ControlsPlugin,
resource::{Binding, ButtonCombination, Controls, InputType, Inputs, PlayerInputs}, resource::{Binding, ButtonCombination, Controls, InputType, InputValue, Inputs, PlayerInputs},
}; };
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use strum_macros::EnumIter; use strum_macros::EnumIter;
const FIRA_CODE_PATH: &str = "font/FiraCode/FiraCode-VariableFont_wght.ttf"; const FIRA_CODE_PATH: &str = "font/FiraCode/FiraCode-VariableFont_wght.ttf";
#[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy)] #[derive(PartialEq, Eq, Hash, EnumIter, Clone, Copy, Debug)]
enum MyAction { enum MyAction {
MoveForward, MoveForward,
MoveBack, MoveBack,
MoveLeft, MoveLeft,
MoveRight, MoveRight,
Jump, Jump,
Interact,
Shoot,
Scope,
} }
impl Action for MyAction {} impl Action for MyAction {}
@@ -46,7 +43,7 @@ impl GameState for MyGameState {}
impl GameStateInner for MyGameState {} impl GameStateInner for MyGameState {}
#[derive(Resource, Default, Clone)] #[derive(Resource, Default, Clone, Debug)]
struct MyInputsContainer { struct MyInputsContainer {
// When the game does not provide multiplayer, one field is enough // When the game does not provide multiplayer, one field is enough
player_inputs: PlayerInputs<MyAction>, player_inputs: PlayerInputs<MyAction>,
@@ -78,58 +75,49 @@ fn main() {
ControlsPlugin::<MyAction, MyInputsContainer, MyGameState>::default(), ControlsPlugin::<MyAction, MyInputsContainer, MyGameState>::default(),
)) ))
.add_systems(Startup, (add_bindings, setup)) .add_systems(Startup, (add_bindings, setup))
.add_systems(Update, text_update_system)
.run(); .run();
} }
// TODO: default bingings... // TODO: default bingings...
fn add_bindings(mut controls: ResMut<Controls<MyAction, MyGameState>>) { fn add_bindings(mut controls: ResMut<Controls<MyAction, MyGameState>>) {
controls.push( controls.force_push(
MyAction::MoveLeft,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::A))),
);
controls.force_push(
MyAction::MoveBack,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::S))),
);
controls.force_push(
MyAction::MoveForward, MyAction::MoveForward,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::W))), Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::W))),
); );
controls.force_push(
MyAction::MoveRight,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::D))),
);
controls.force_push(
MyAction::Jump,
Binding::new(ButtonCombination::Single(InputType::Keyboard(KeyCode::Space))),
);
} }
#[derive(Component)] #[derive(Component)]
struct TextBlock; struct TextBlock;
const TEXT_SIZE: f32 = 30.;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default()); commands.spawn(Camera2dBundle::default());
commands.spawn(( commands.spawn((
TextBundle::from_sections([ TextBundle::from_sections([
TextSection::new( TextSection::new(
"Input: ", "Inputs: A S W D Space",
TextStyle { TextStyle {
font: asset_server.load(FIRA_CODE_PATH), font: asset_server.load(FIRA_CODE_PATH),
font_size: 60.0, font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"A ",
TextStyle {
font_size: 60.0,
..default()
},
),
TextSection::new(
"S ",
TextStyle {
font_size: 60.0,
..default()
},
),
TextSection::new(
"W ",
TextStyle {
font_size: 60.0,
..default()
},
),
TextSection::new(
"D ",
TextStyle {
font_size: 60.0,
..default() ..default()
}, },
), ),
@@ -137,35 +125,42 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
"\nAction: ", "\nAction: ",
TextStyle { TextStyle {
font: asset_server.load(FIRA_CODE_PATH), font: asset_server.load(FIRA_CODE_PATH),
font_size: 60.0, font_size: TEXT_SIZE,
..default() ..default()
}, },
), ),
TextSection::new( TextSection::new(
"MoveLeft", "MoveLeft ",
TextStyle { TextStyle {
font_size: 60.0, font_size: TEXT_SIZE,
..default() ..default()
}, },
), ),
TextSection::new( TextSection::new(
"MoveBack", "MoveBack ",
TextStyle { TextStyle {
font_size: 60.0, font_size: TEXT_SIZE,
..default() ..default()
}, },
), ),
TextSection::new( TextSection::new(
"MoveForward", "MoveForward ",
TextStyle { TextStyle {
font_size: 60.0, font_size: TEXT_SIZE,
..default() ..default()
}, },
), ),
TextSection::new( TextSection::new(
"MoveRight", "MoveRight ",
TextStyle { TextStyle {
font_size: 60.0, font_size: TEXT_SIZE,
..default()
},
),
TextSection::new(
"Jump",
TextStyle {
font_size: TEXT_SIZE,
..default() ..default()
}, },
), ),
@@ -175,31 +170,52 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
} }
// text sections indexes // text sections indexes
const A_INDEX: usize = 2; const MOVE_LEFT_INDEX: usize = 2;
const S_INDEX: usize = 3; const MOVE_BACK_INDEX: usize = 3;
const W_INDEX: usize = 4; const MOVE_FORWARD_INDEX: usize = 4;
const D_INDEX: usize = 5; const MOVE_RIGHT_INDEX: usize = 5;
const JUMP_INDEX: usize = 6;
const MOVE_LEFT_INDEX: usize = 6;
const MOVE_BACK_INDEX: usize = 7;
const MOVE_FORWARD_INDEX: usize = 8;
const MOVE_RIGHT_INDEX: usize = 9;
fn text_update_system( fn text_update_system(
mut query: Query<&mut Text, With<TextBlock>>, mut query: Query<&mut Text, With<TextBlock>>,
inputs_container: MyInputsContainer, inputs_container: Res<MyInputsContainer>,
) { ) {
for mut text in &mut query { for text in query.iter_mut() {
let input_value = inputs_container.me().expect("This is bad").get(MyAction::MoveLeft); let player_inputs = inputs_container.me().expect("This is bad");
if input_value.is_boolean() && input_value.to_boolean() { let mut text = update_text(
text.sections[MOVE_LEFT_INDEX].style.color = Color::GOLD; text,
} else { player_inputs.get(MyAction::MoveLeft),
text.sections[MOVE_LEFT_INDEX].style.color = Color::GOLD; MOVE_LEFT_INDEX);
} text = update_text(
text,
player_inputs.get(MyAction::MoveBack),
MOVE_BACK_INDEX);
text = update_text(
text,
player_inputs.get(MyAction::MoveForward),
MOVE_FORWARD_INDEX);
text = update_text(
text,
player_inputs.get(MyAction::MoveRight),
MOVE_RIGHT_INDEX);
_ = update_text(
text,
InputValue::Boolean(player_inputs.get_just_pressed(MyAction::Jump).unwrap_or(false)),
JUMP_INDEX);
} }
} }
fn update_text(mut text: Mut<Text>, input_value: InputValue, index: usize) -> Mut<Text>{
if input_value.is_boolean() && input_value.to_boolean() {
text.sections[index].style.color = Color::GOLD;
} else {
text.sections[index].style.color = Color::WHITE;
}
text
}
// fn text_update_system( // fn text_update_system(
// diagnostics: Res<DiagnosticsStore>, // diagnostics: Res<DiagnosticsStore>,
// mut query: Query<&mut Text, With<FpsText>>, // mut query: Query<&mut Text, With<FpsText>>,
+3 -2
View File
@@ -23,13 +23,14 @@ pub trait ActionInner:
+ IntoEnumIterator + IntoEnumIterator
+ Clone + Clone
+ Copy + Copy
+ std::fmt::Debug
{ {
} }
pub trait GameStateInner: 'static + std::marker::Sync + std::marker::Send + States + Default {} pub trait GameStateInner: 'static + std::marker::Sync + std::marker::Send + States + Default + std::fmt::Debug {}
pub trait InputsContainerInner<A: Action>: pub trait InputsContainerInner<A: Action>:
'static + std::marker::Sync + std::marker::Send + Resource + Default 'static + std::marker::Sync + std::marker::Send + Resource + Default + std::fmt::Debug
{ {
/// The method returning an iterator over references to the PlayerInputs. /// The method returning an iterator over references to the PlayerInputs.
/// Note: the use of 'a for both the method's lifetime and the returned Iterator's lifetime. /// Note: the use of 'a for both the method's lifetime and the returned Iterator's lifetime.
+5
View File
@@ -63,9 +63,13 @@ impl<
} }
} }
log::trace!("action {:?} in condition", action);
match &binding.input { match &binding.input {
ButtonCombination::Single(button) => match button { ButtonCombination::Single(button) => match button {
InputType::Keyboard(key) => { InputType::Keyboard(key) => {
log::trace!("presed?: {:?}", keyboard_input.pressed(*key));
log::trace!("active");
inputs.forced_set(*action, keyboard_input.pressed(*key)); inputs.forced_set(*action, keyboard_input.pressed(*key));
} }
InputType::Mouse(input) => { InputType::Mouse(input) => {
@@ -117,6 +121,7 @@ impl<
} }
} }
log::trace!("active");
// TODO: should be [`Chord`](ButtonCombination::Chord) only [`Boolean`](InputValue::Boolean) type // TODO: should be [`Chord`](ButtonCombination::Chord) only [`Boolean`](InputValue::Boolean) type
inputs.forced_set(*action, true); inputs.forced_set(*action, true);
} }
+19 -3
View File
@@ -112,6 +112,7 @@ impl<A: Action> PlayerInputs<A> {
panic!("This input is not boolean") panic!("This input is not boolean")
} }
// TODO this is must be binding option for non axis inputs
/// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame /// Returns `true` if current `InputValue` has become `InputValue::Boolean(true)` in this frame
/// If input has not same type as `InputValue` on previous frame return `Error()` /// If input has not same type as `InputValue` on previous frame return `Error()`
pub fn get_just_pressed(&self, action: A) -> Result<bool, Box<dyn error::Error>> { pub fn get_just_pressed(&self, action: A) -> Result<bool, Box<dyn error::Error>> {
@@ -238,7 +239,7 @@ common_traits_conditions! {
Tap, Tap,
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct ActivationOptions { pub struct ActivationOptions {
/// Manner in which input is registered /// Manner in which input is registered
pub mode: ActivationMode, pub mode: ActivationMode,
@@ -388,7 +389,7 @@ impl InputValue {
} }
common_traits_conditions! { common_traits_conditions! {
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Bindings<Gs: GameState> { pub struct Bindings<Gs: GameState> {
/// List of bindings for action /// List of bindings for action
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] #[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
@@ -415,6 +416,10 @@ impl<Gs: GameState> Bindings<Gs> {
} }
} }
pub fn force_push(&mut self, binding: Binding<Gs>) {
self.list.push(binding);
}
pub fn push(&mut self, binding: Binding<Gs>) { pub fn push(&mut self, binding: Binding<Gs>) {
if self.options == OptionsMode::Immutable { if self.options == OptionsMode::Immutable {
warn!("You try to push binding to immutable bindings"); warn!("You try to push binding to immutable bindings");
@@ -468,7 +473,7 @@ impl<Gs: GameState> Bindings<Gs> {
common_traits_conditions! { common_traits_conditions! {
/// Contains all bindings for action /// Contains all bindings for action
/// and different activation options /// and different activation options
#[derive(Default, Clone)] #[derive(Default, Clone, Debug)]
pub struct BindingConfig<Gs: GameState> { pub struct BindingConfig<Gs: GameState> {
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))] #[cfg_attr(feature = "serialize", serde(bound(deserialize = "")))]
pub bindings: Bindings<Gs>, pub bindings: Bindings<Gs>,
@@ -517,6 +522,17 @@ impl<A: Action, Gs: GameState> Controls<A, Gs> {
self.0.get(&action) self.0.get(&action)
} }
/// Forced push new binding for action
pub fn force_push(&mut self, action: A, binding: Binding<Gs>) {
// TODO: warning if config is not exist yet
// TODO: interface for [`ActivationOptions`]
self.0
.entry(action)
.or_insert_with(BindingConfig::default)
.bindings
.force_push(binding);
}
/// Push new binding for action /// Push new binding for action
pub fn push(&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: warning if config is not exist yet