Implement a decent first pass of the input action system
ci/woodpecker/push/build Pipeline was successful Details

This still needs some work, mostly just names of things and finding a better way to add the InputActionPLugin and ActionHandler
This commit is contained in:
SeanOMik 2023-11-05 22:50:57 -05:00
parent 75c0377d9c
commit 8d6e675c82
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
3 changed files with 228 additions and 28 deletions

View File

@ -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}; use lyra_engine::assets::{ResourceManager, Model};
mod free_fly_camera; mod free_fly_camera;
use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera}; use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera};
use tracing::debug;
struct FixedTimestep { struct FixedTimestep {
max_tps: u32, max_tps: u32,
@ -58,22 +59,6 @@ struct TpsAccumulator(f32);
#[async_std::main] #[async_std::main]
async fn 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 setup_sys = |world: &mut World| -> anyhow::Result<()> {
{ {
let mut window_options = world.get_resource_mut::<Ct<WindowOptions>>().unwrap(); let mut window_options = world.get_resource_mut::<Ct<WindowOptions>>().unwrap();
@ -144,9 +129,46 @@ async fn main() {
fps_plugin(game); 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::<ActionHandler>().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 Game::initialize().await
.with_plugin(lyra_engine::DefaultPlugins) .with_plugin(lyra_engine::DefaultPlugins)
.with_startup_system(setup_sys) .with_startup_system(setup_sys)
.with_plugin(action_handler_plugin)
//.with_plugin(fps_plugin) //.with_plugin(fps_plugin)
.with_plugin(jiggle_plugin) .with_plugin(jiggle_plugin)
.with_plugin(FreeFlyCameraPlugin) .with_plugin(FreeFlyCameraPlugin)

View File

@ -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)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
enum GamepadFormat { 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)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ActionKind { pub enum ActionKind {
Button, Button,
@ -101,13 +119,15 @@ pub enum ActionKind {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Action { pub struct Action {
kind: ActionKind, pub kind: ActionKind,
pub state: ActionState,
} }
impl Action { impl Action {
pub fn new(kind: ActionKind) -> Self { pub fn new(kind: ActionKind) -> Self {
Self { Self {
kind, kind,
state: ActionState::Idle,
} }
} }
} }
@ -130,12 +150,14 @@ impl Default for LayoutId {
#[derive(Clone)] #[derive(Clone)]
pub struct Layout { pub struct Layout {
mappings: HashMap<ActionMappingId, ActionMapping>, mappings: HashMap<ActionMappingId, ActionMapping>,
active_mapping: ActionMappingId,
} }
impl Layout { impl Layout {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
mappings: HashMap::new(), mappings: HashMap::new(),
active_mapping: Default::default(),
} }
} }
@ -215,10 +237,12 @@ impl ActionMapping {
} }
} }
#[derive(Clone)]
pub struct ActionHandler { pub struct ActionHandler {
actions: HashMap<String, Action>, pub actions: HashMap<String, Action>,
layouts: HashMap<LayoutId, Layout>, pub layouts: HashMap<LayoutId, Layout>,
current_layout: LayoutId, pub current_layout: LayoutId,
pub current_mapping: ActionMappingId,
} }
impl ActionHandler { impl ActionHandler {
@ -227,16 +251,17 @@ impl ActionHandler {
actions: HashMap::new(), actions: HashMap::new(),
layouts: HashMap::new(), layouts: HashMap::new(),
current_layout: LayoutId::default(), 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.layouts.insert(id, Layout::new());
self 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(); /* let layout = self.layouts.get_mut(&layout_id).unwrap();
layout.actions.insert(label.to_string(), action); */ layout.actions.insert(label.to_string(), action); */
self.actions.insert(label.to_string(), action); self.actions.insert(label.to_string(), action);
@ -244,10 +269,161 @@ impl ActionHandler {
self 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(); let layout = self.layouts.get_mut(&mapping.layout).unwrap();
layout.add_mapping(mapping); layout.add_mapping(mapping);
self 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<f32> {
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<f32> {
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<f32> {
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::<InputButtons<KeyCode>>()
.map(|r| r.deref().clone());
if let Some(keys) = keys {
let mut handler = world.get_resource_mut::<ActionHandler>()
.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, &[]);
}
} }

View File

@ -64,6 +64,8 @@ impl<T: Button> InputButtons<T> {
pub(crate) fn add_input_from_winit(&mut self, button: T, state: ElementState) { pub(crate) fn add_input_from_winit(&mut self, button: T, state: ElementState) {
let event = match state { 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 if self.is_pressed(button.clone()) => ButtonEvent::Pressed(button),
ElementState::Pressed => ButtonEvent::JustPressed(button), ElementState::Pressed => ButtonEvent::JustPressed(button),
ElementState::Released => ButtonEvent::Released(button), ElementState::Released => ButtonEvent::Released(button),