From 8d6e675c82d9c8749c4827e10e12ccb2a7e6d26f Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 5 Nov 2023 22:50:57 -0500 Subject: [PATCH] Implement a decent first pass of the input action system This still needs some work, mostly just names of things and finding a better way to add the InputActionPLugin and ActionHandler --- examples/testbed/src/main.rs | 56 +++++++--- src/input/action.rs | 198 +++++++++++++++++++++++++++++++++-- src/input/buttons.rs | 2 + 3 files changed, 228 insertions(+), 28 deletions(-) diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index fd8aeea..710d01d 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -1,8 +1,9 @@ -use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent, DeltaTime}, EventQueue, SimpleSystem, Component, Criteria, CriteriaSchedule, BatchedSystem}, math::Transform, input::{KeyCode, InputButtons, MouseMotion, ActionHandler, Layout, Action, ActionKind, LayoutId, ActionMapping, Binding, ActionSource, ActionMappingId}, game::Game, plugin::Plugin, render::window::{CursorGrabMode, WindowOptions}, change_tracker::Ct}; +use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent, DeltaTime}, EventQueue, SimpleSystem, Component, Criteria, CriteriaSchedule, BatchedSystem}, math::Transform, input::{KeyCode, InputButtons, MouseMotion, ActionHandler, Layout, Action, ActionKind, LayoutId, ActionMapping, Binding, ActionSource, ActionMappingId, InputActionPlugin, ActionState}, game::Game, plugin::Plugin, render::window::{CursorGrabMode, WindowOptions}, change_tracker::Ct}; use lyra_engine::assets::{ResourceManager, Model}; mod free_fly_camera; use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera}; +use tracing::debug; struct FixedTimestep { max_tps: u32, @@ -58,22 +59,6 @@ struct TpsAccumulator(f32); #[async_std::main] async fn main() { - let _action_handler = ActionHandler::new() - .add_layout(LayoutId::from(0)) - .add_action("forward_backward", Action::new(ActionKind::Button)) - .add_action("left_right", Action::new(ActionKind::Button)) - .add_mapping(ActionMapping::new(LayoutId::from(0), ActionMappingId::from(0)) - .bind("forward_backward", &[ - ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), - ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0) - ]) - .bind("left_right", &[ - ActionSource::Keyboard(KeyCode::A).into_binding_modifier(1.0), - ActionSource::Keyboard(KeyCode::D).into_binding_modifier(-1.0) - ]) - .finish() - ); - let setup_sys = |world: &mut World| -> anyhow::Result<()> { { let mut window_options = world.get_resource_mut::>().unwrap(); @@ -143,10 +128,47 @@ async fn main() { game.with_system("fixed", sys, &[]); fps_plugin(game); }; + + let action_handler_plugin = |game: &mut Game| { + let action_handler = ActionHandler::new() + .add_layout(LayoutId::from(0)) + .add_action("forward_backward", Action::new(ActionKind::Button)) + .add_action("left_right", Action::new(ActionKind::Button)) + .add_mapping(ActionMapping::new(LayoutId::from(0), ActionMappingId::from(0)) + .bind("forward_backward", &[ + ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0) + ]) + .bind("left_right", &[ + ActionSource::Keyboard(KeyCode::A).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::D).into_binding_modifier(-1.0) + ]) + .finish() + ); + + let test_system = |world: &mut World| -> anyhow::Result<()> { + let handler = world.get_resource::().unwrap(); + + if let Some(alpha) = handler.get_pressed_modifier("forward_backward") { + debug!("'forward_backward': {alpha}"); + } + + if let Some(alpha) = handler.get_pressed_modifier("left_right") { + debug!("'left_right': {alpha}"); + } + + Ok(()) + }; + + game.world().insert_resource(action_handler); + game.with_plugin(InputActionPlugin); + game.with_system("test_actions", test_system, &[]); + }; Game::initialize().await .with_plugin(lyra_engine::DefaultPlugins) .with_startup_system(setup_sys) + .with_plugin(action_handler_plugin) //.with_plugin(fps_plugin) .with_plugin(jiggle_plugin) .with_plugin(FreeFlyCameraPlugin) diff --git a/src/input/action.rs b/src/input/action.rs index 549a7db..187b50d 100644 --- a/src/input/action.rs +++ b/src/input/action.rs @@ -1,10 +1,10 @@ -use std::{collections::HashMap, cell::RefCell, borrow::BorrowMut}; +use std::{collections::HashMap, cell::RefCell, borrow::BorrowMut, ops::Deref}; -use edict::action; +use edict::{action, World}; -use crate::castable_any::CastableAny; +use crate::{castable_any::CastableAny, plugin::Plugin}; -use super::{Button, KeyCode}; +use super::{Button, KeyCode, InputButtons}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] enum GamepadFormat { @@ -93,6 +93,24 @@ impl Binding { } } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ActionState { + /// No input is being given by the user for the button/axis. + Idle, + /// The button is pressed + Pressed(f32), + /// The button was just pressed + JustPressed(f32), + /// The button was just released + JustReleased, + + //Released, + /// The axis moved + Axis(f32), + /// Some other state with a modifier + Other(f32), +} + #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum ActionKind { Button, @@ -101,13 +119,15 @@ pub enum ActionKind { #[derive(Clone, Debug)] pub struct Action { - kind: ActionKind, + pub kind: ActionKind, + pub state: ActionState, } impl Action { pub fn new(kind: ActionKind) -> Self { Self { kind, + state: ActionState::Idle, } } } @@ -130,12 +150,14 @@ impl Default for LayoutId { #[derive(Clone)] pub struct Layout { mappings: HashMap, + active_mapping: ActionMappingId, } impl Layout { pub fn new() -> Self { Self { mappings: HashMap::new(), + active_mapping: Default::default(), } } @@ -215,10 +237,12 @@ impl ActionMapping { } } +#[derive(Clone)] pub struct ActionHandler { - actions: HashMap, - layouts: HashMap, - current_layout: LayoutId, + pub actions: HashMap, + pub layouts: HashMap, + pub current_layout: LayoutId, + pub current_mapping: ActionMappingId, } impl ActionHandler { @@ -227,16 +251,17 @@ impl ActionHandler { actions: HashMap::new(), layouts: HashMap::new(), current_layout: LayoutId::default(), + current_mapping: ActionMappingId::default(), } } - pub fn add_layout(&mut self, id: LayoutId) -> &mut Self { + pub fn add_layout(mut self, id: LayoutId) -> Self { self.layouts.insert(id, Layout::new()); self } - pub fn add_action(&mut self, label: &str, action: Action) -> &mut Self { + pub fn add_action(mut self, label: &str, action: Action) -> Self { /* let layout = self.layouts.get_mut(&layout_id).unwrap(); layout.actions.insert(label.to_string(), action); */ self.actions.insert(label.to_string(), action); @@ -244,10 +269,161 @@ impl ActionHandler { self } - pub fn add_mapping(&mut self, mapping: ActionMapping) -> &mut Self { + pub fn add_mapping(mut self, mapping: ActionMapping) -> Self { let layout = self.layouts.get_mut(&mapping.layout).unwrap(); layout.add_mapping(mapping); self } + + /// Returns true if the action is pressed (or was just pressed). + /// + /// This will panic if the action name does not correspond to an action. + pub fn is_action_pressed(&self, action_name: &str) -> bool { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::Pressed(_) | ActionState::JustPressed(_) => true, + _ => false + } + } + + /// Returns true if the action was just pressed. + /// + /// This will panic if the action name does not correspond to an action. + pub fn was_action_just_pressed(&self, action_name: &str) -> bool { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::JustPressed(_) => true, + _ => false + } + } + + /// Returns true if the action was just released. + /// + /// This will panic if the action name does not correspond to an action. + pub fn was_action_just_released(&self, action_name: &str) -> bool { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::JustReleased => true, + _ => false + } + } + + /// Returns an action's state. + /// + /// This will panic if the action name does not correspond to an action. + pub fn get_action_state(&self, action_name: &str) -> ActionState { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + action.state + } + + /// Returns the action's modifier if it is pressed (or was just pressed). + /// Returns `None` if the action's state is not `ActionState::Pressed` or `ActionState::JustPressed`. + /// + /// This will panic if the action name does not correspond to an action. + pub fn get_pressed_modifier(&self, action_name: &str) -> Option { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::Pressed(v) | ActionState::JustPressed(v) => Some(v), + _ => None, + } + } + + /// Returns the action's modifier if it was just pressed. + /// Returns `None` if the action's state is not `ActionState::JustPressed`. + /// + /// This will panic if the action name does not correspond to an action. + pub fn get_just_pressed_modifier(&self, action_name: &str) -> Option { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::JustPressed(v) => Some(v), + _ => None, + } + } + + /// Returns the action's modifier if its an updated axis. + /// Returns `None` if the action's state is not `ActionState::Axis`. + /// + /// This will panic if the action name does not correspond to an action. + pub fn get_axis_modifier(&self, action_name: &str) -> Option { + let action = self.actions.get(action_name) + .expect(&format!("Action {action_name} was not found")); + + match action.state { + ActionState::Axis(v) => Some(v), + _ => None, + } + } +} + +fn actions_system(world: &mut World) -> anyhow::Result<()> { + let keys = world.get_resource::>() + .map(|r| r.deref().clone()); + + if let Some(keys) = keys { + let mut handler = world.get_resource_mut::() + .expect("No Input Action handler was created in the world!"); + + //handler + let layout = handler.layouts.get(&handler.current_layout).expect("No active layout"); + let mapping = layout.mappings.get(&layout.active_mapping).expect("No active mapping"); + + for (action_name, binds) in mapping.action_binds.clone().iter() { // TODO: dont clone + let mut new_state = None; + + for bind in binds.iter() { + match bind.source { + ActionSource::Keyboard(key) => { + // JustPressed needs to be first, since is_pressed includes buttons that + // were just pressed. + if keys.was_just_pressed(key) { + new_state = Some(ActionState::JustPressed(bind.modifier)); + } else if keys.is_pressed(key) { + new_state = Some(ActionState::Pressed(bind.modifier)); + } + }, + ActionSource::Gamepad(_, _) => todo!(), + } + } + + let action = handler.actions.get_mut(action_name).expect("Action name for binding is invalid!"); + + if let Some(new_state) = new_state { + action.state = new_state; + } else { + match action.state { + ActionState::Idle => {}, + //ActionState::Pressed(_) => todo!(), + //ActionState::JustPressed(_) => todo!(), + ActionState::JustReleased => action.state = ActionState::Idle, + //ActionState::Released => action.state = ActionState::I, + _ => action.state = ActionState::JustReleased, + } + //action.state = ActionState::JustReleased; + } + } + } + + Ok(()) +} + +#[derive(Default, Clone, Copy)] +pub struct InputActionPlugin; + +impl Plugin for InputActionPlugin { + fn setup(&self, game: &mut crate::game::Game) { + game.with_system("input_actions", actions_system, &[]); + } } \ No newline at end of file diff --git a/src/input/buttons.rs b/src/input/buttons.rs index e4205d2..ce8dcc4 100644 --- a/src/input/buttons.rs +++ b/src/input/buttons.rs @@ -64,6 +64,8 @@ impl InputButtons { pub(crate) fn add_input_from_winit(&mut self, button: T, state: ElementState) { let event = match state { + // TODO: there is a pause between pressed events of a key when holding down, so it stays in a just pressed state for too long + // When a button is held down, this would change a just pressed button into a pressed button. ElementState::Pressed if self.is_pressed(button.clone()) => ButtonEvent::Pressed(button), ElementState::Pressed => ButtonEvent::JustPressed(button), ElementState::Released => ButtonEvent::Released(button),