Implement a decent first pass of the input action system
ci/woodpecker/push/build Pipeline was successful
Details
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:
parent
75c0377d9c
commit
8d6e675c82
|
@ -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)
|
||||||
|
|
|
@ -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, &[]);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in New Issue