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};
|
||||
|
||||
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::<Ct<WindowOptions>>().unwrap();
|
||||
|
@ -144,9 +129,46 @@ async fn main() {
|
|||
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
|
||||
.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)
|
||||
|
|
|
@ -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<ActionMappingId, ActionMapping>,
|
||||
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<String, Action>,
|
||||
layouts: HashMap<LayoutId, Layout>,
|
||||
current_layout: LayoutId,
|
||||
pub actions: HashMap<String, Action>,
|
||||
pub layouts: HashMap<LayoutId, Layout>,
|
||||
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<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) {
|
||||
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),
|
||||
|
|
Loading…
Reference in New Issue