From d0b58a5aa1f205cc6ea626083211ca7aa285d3a7 Mon Sep 17 00:00:00 2001 From: Alula Date: Sun, 6 Dec 2020 15:56:16 +0100 Subject: [PATCH] Initial support for gamepads (P2 only) --- Cargo.toml | 3 +- src/input/gilrs_player_controller.rs | 195 ++++++++++++++++++++++++ src/input/keyboard_player_controller.rs | 2 +- src/input/mod.rs | 1 + src/lib.rs | 27 ++-- src/settings.rs | 28 +++- src/shared_game_state.rs | 18 ++- src/stage.rs | 2 - 8 files changed, 251 insertions(+), 25 deletions(-) create mode 100644 src/input/gilrs_player_controller.rs diff --git a/Cargo.toml b/Cargo.toml index 3ad7af2..e527a52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,8 @@ directories = "2" gfx = "0.18" gfx_core = "0.9" gfx_device_gl = {git = "https://github.com/doukutsu-rs/gfx.git", branch = "pre-ll"} -ggez = {git = "https://github.com/doukutsu-rs/ggez.git", rev = "aad56b0d173ca9f4aeb28599075b5af49ab9214e"} +ggez = {git = "https://github.com/doukutsu-rs/ggez.git", rev = "d70d3f059077efde7cae02f37e7b077924880128"} +gilrs = "0.8" glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "android-support"} imgui = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "a990a538b66cb67dba3a072bf299b6a51c001447"} imgui-gfx-renderer = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "a990a538b66cb67dba3a072bf299b6a51c001447"} diff --git a/src/input/gilrs_player_controller.rs b/src/input/gilrs_player_controller.rs new file mode 100644 index 0000000..355e755 --- /dev/null +++ b/src/input/gilrs_player_controller.rs @@ -0,0 +1,195 @@ +use ggez::{Context, GameResult}; +use gilrs::{Axis, Button, GamepadId}; +use num_traits::abs; + +use crate::bitfield; +use crate::input::player_controller::PlayerController; +use crate::shared_game_state::SharedGameState; + +bitfield! { + #[derive(Clone, Copy)] + pub struct KeyState(u16); + impl Debug; + + pub left, set_left: 0; + pub right, set_right: 1; + pub up, set_up: 2; + pub down, set_down: 3; + pub map, set_map: 4; + pub inventory, set_inventory: 5; + pub jump, set_jump: 6; + pub shoot, set_shoot: 7; + pub next_weapon, set_next_weapon: 8; + pub prev_weapon, set_prev_weapon: 9; + pub start, set_start: 10; +} + +/// An implementation of player controller backed by gilrs library. +#[derive(Clone)] +pub struct GilrsPlayerController { + gamepad_id: GamepadId, + left_x: f64, + left_y: f64, + right_x: f64, + right_y: f64, + state: KeyState, + old_state: KeyState, + trigger: KeyState, +} + +impl GilrsPlayerController { + pub fn new(gamepad_id: GamepadId) -> GilrsPlayerController { + GilrsPlayerController { + gamepad_id, + left_x: 0.0, + left_y: 0.0, + right_x: 0.0, + right_y: 0.0, + state: KeyState(0), + old_state: KeyState(0), + trigger: KeyState(0), + } + } +} + +const THRESHOLD: f64 = 0.3; + +impl PlayerController for GilrsPlayerController { + fn update(&mut self, state: &SharedGameState, _ctx: &mut Context) -> GameResult { + if let Some(gilrs) = state.gilrs.as_ref() { + if let Some(gamepad) = gilrs.connected_gamepad(self.gamepad_id) { + let mut axes = [ + (&mut self.left_x, Axis::LeftStickX), + (&mut self.left_y, Axis::LeftStickY), + (&mut self.right_x, Axis::RightStickX), + (&mut self.right_y, Axis::RightStickY), + ]; + + for (axis_val, id) in axes.iter_mut() { + if let Some(axis) = gamepad.axis_data(*id) { + **axis_val = if abs(axis.value()) < 0.12 { 0.0 } else { axis.value() } as f64; + } + } + + self.state.set_up(self.left_y > THRESHOLD); + self.state.set_left(self.left_x < -THRESHOLD); + self.state.set_down(self.left_y < -THRESHOLD); + self.state.set_right(self.left_x > THRESHOLD); + self.state.set_jump(gamepad.is_pressed(Button::South)); + self.state.set_shoot(gamepad.is_pressed(Button::East)); + self.state.set_prev_weapon(gamepad.is_pressed(Button::LeftTrigger)); + self.state.set_next_weapon(gamepad.is_pressed(Button::RightTrigger)); + self.state.set_start(gamepad.is_pressed(Button::Start)); + } + } + + Ok(()) + } + + fn update_trigger(&mut self) { + let mut trigger = self.state.0 ^ self.old_state.0; + trigger &= self.state.0; + self.old_state = self.state; + self.trigger = KeyState(trigger); + } + + fn move_up(&self) -> bool { + self.left_y > THRESHOLD + } + + fn move_left(&self) -> bool { + self.left_x < -THRESHOLD + } + + fn move_down(&self) -> bool { + self.left_y < -THRESHOLD + } + + fn move_right(&self) -> bool { + self.left_x > THRESHOLD + } + + fn prev_weapon(&self) -> bool { + self.state.prev_weapon() + } + + fn next_weapon(&self) -> bool { + self.state.next_weapon() + } + + fn jump(&self) -> bool { + self.state.jump() + } + + fn shoot(&self) -> bool { + self.state.shoot() + } + + fn trigger_up(&self) -> bool { + self.trigger.up() + } + + fn trigger_left(&self) -> bool { + self.trigger.left() + } + + fn trigger_down(&self) -> bool { + self.trigger.down() + } + + fn trigger_right(&self) -> bool { + self.trigger.right() + } + + fn trigger_prev_weapon(&self) -> bool { + self.trigger.prev_weapon() + } + + fn trigger_next_weapon(&self) -> bool { + self.trigger.next_weapon() + } + + fn trigger_jump(&self) -> bool { + self.trigger.jump() + } + + fn trigger_shoot(&self) -> bool { + self.trigger.shoot() + } + + fn trigger_menu_ok(&self) -> bool { + self.trigger.jump() + } + + fn trigger_menu_back(&self) -> bool { + self.trigger.shoot() + } + + fn trigger_menu_pause(&self) -> bool { + self.trigger.start() + } + + fn look_up(&self) -> bool { + self.left_y > THRESHOLD || self.right_y > THRESHOLD + } + + fn look_left(&self) -> bool { + self.left_x < -THRESHOLD || self.right_x < -THRESHOLD + } + + fn look_down(&self) -> bool { + self.left_y < -THRESHOLD || self.right_y < -THRESHOLD + } + + fn look_right(&self) -> bool { + self.left_x > THRESHOLD || self.right_x > THRESHOLD + } + + fn move_analog_x(&self) -> f64 { + self.left_x + } + + fn move_analog_y(&self) -> f64 { + self.left_y + } +} diff --git a/src/input/keyboard_player_controller.rs b/src/input/keyboard_player_controller.rs index a39c864..79f15aa 100644 --- a/src/input/keyboard_player_controller.rs +++ b/src/input/keyboard_player_controller.rs @@ -23,7 +23,7 @@ bitfield! { pub next_weapon, set_next_weapon: 8; pub prev_weapon, set_prev_weapon: 9; pub escape, set_escape: 10; - pub enter, set_enter: 10; + pub enter, set_enter: 11; } #[derive(Clone)] diff --git a/src/input/mod.rs b/src/input/mod.rs index 25d7fce..65b53c2 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,5 +1,6 @@ pub mod combined_menu_controller; pub mod dummy_player_controller; +pub mod gilrs_player_controller; pub mod keyboard_player_controller; pub mod player_controller; pub mod touch_controls; diff --git a/src/lib.rs b/src/lib.rs index e972d87..85e9744 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,12 @@ impl Game { } fn update(&mut self, ctx: &mut Context) -> GameResult { + if let Some(gilrs) = self.state.gilrs.as_mut() { + while let Some(_) = gilrs.next_event() { + // + } + } + if let Some(scene) = self.scene.as_mut() { match self.state.timing_mode { TimingMode::_50Hz | TimingMode::_60Hz => { @@ -160,10 +166,9 @@ impl Game { Ok(()) } - fn key_down_event(&mut self, key_code: KeyCode, _key_mod: KeyMods, repeat: bool) { + fn key_down_event(&mut self, key_code: KeyCode, repeat: bool) { if repeat { return; } - // todo: proper keymaps? let state = &mut self.state; match key_code { KeyCode::F7 => { state.set_speed(1.0) } @@ -183,10 +188,6 @@ impl Game { _ => {} } } - - fn key_up_event(&mut self, key_code: KeyCode, _key_mod: KeyMods) { - // - } } #[cfg(target_os = "android")] @@ -354,8 +355,6 @@ pub fn init() -> GameResult { } WindowEvent::Resized(_) => { if let (Some(ctx), Some(game)) = (&mut context, &mut game) { - let (w, h) = graphics::drawable_size(ctx); - game.state.tmp_canvas = Canvas::with_window_size(ctx).unwrap(); game.state.game_canvas = Canvas::with_window_size(ctx).unwrap(); game.state.lightmap_canvas = Canvas::with_window_size(ctx).unwrap(); @@ -373,20 +372,14 @@ pub fn init() -> GameResult { KeyboardInput { state: el_state, virtual_keycode: Some(keycode), - modifiers, .. }, .. } => { if let (Some(ctx), Some(game)) = (&mut context, &mut game) { - match el_state { - ElementState::Pressed => { - let repeat = keyboard::is_key_repeated(ctx); - game.key_down_event(keycode, modifiers.into(), repeat); - } - ElementState::Released => { - game.key_up_event(keycode, modifiers.into()); - } + if el_state == ElementState::Pressed { + let repeat = keyboard::is_key_repeated(ctx); + game.key_down_event(keycode, repeat); } } } diff --git a/src/settings.rs b/src/settings.rs index 088b601..789809e 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,10 +1,18 @@ use ggez::{Context, GameResult}; +use gilrs::GamepadId; use serde::{Deserialize, Serialize}; use winit::event::VirtualKeyCode; use crate::input::keyboard_player_controller::KeyboardController; use crate::input::player_controller::PlayerController; use crate::player::TargetPlayer; +use crate::input::gilrs_player_controller::GilrsPlayerController; + +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] +pub enum ControllerType { + Keyboard, + Gamepad(usize), +} #[derive(Serialize, Deserialize)] pub struct Settings { @@ -15,6 +23,8 @@ pub struct Settings { pub touch_controls: bool, pub player1_key_map: PlayerKeyMap, pub player2_key_map: PlayerKeyMap, + pub player1_controller_type: ControllerType, + pub player2_controller_type: ControllerType, #[serde(skip)] pub speed: f64, #[serde(skip)] @@ -25,17 +35,29 @@ pub struct Settings { pub debug_outlines: bool, } +fn to_gamepad_id(raw_id: usize) -> GamepadId { + unsafe { + std::mem::transmute(raw_id) + } +} + impl Settings { pub fn load(_ctx: &mut Context) -> GameResult { Ok(Settings::default()) } pub fn create_player1_controller(&self) -> Box { - Box::new(KeyboardController::new(TargetPlayer::Player1)) + match self.player1_controller_type { + ControllerType::Keyboard => Box::new(KeyboardController::new(TargetPlayer::Player1)), + ControllerType::Gamepad(id) => Box::new(GilrsPlayerController::new(to_gamepad_id(id))), + } } pub fn create_player2_controller(&self) -> Box { - Box::new(KeyboardController::new(TargetPlayer::Player2)) + match self.player2_controller_type { + ControllerType::Keyboard => Box::new(KeyboardController::new(TargetPlayer::Player2)), + ControllerType::Gamepad(id) => Box::new(GilrsPlayerController::new(to_gamepad_id(id))), + } } } @@ -49,6 +71,8 @@ impl Default for Settings { touch_controls: cfg!(target_os = "android"), player1_key_map: p1_default_keymap(), player2_key_map: p2_default_keymap(), + player1_controller_type: ControllerType::Keyboard, + player2_controller_type: ControllerType::Keyboard, speed: 1.0, god_mode: false, infinite_booster: false, diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index c44501b..18402d9 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -6,6 +6,7 @@ use chrono::{Datelike, Local}; use ggez::{Context, filesystem, GameResult, graphics}; use ggez::filesystem::OpenOptions; use ggez::graphics::Canvas; +use gilrs::Gilrs; use num_traits::clamp; use crate::bmfont_renderer::BMFontRenderer; @@ -18,7 +19,7 @@ use crate::profile::GameProfile; use crate::rng::RNG; use crate::scene::game_scene::GameScene; use crate::scene::Scene; -use crate::settings::Settings; +use crate::settings::{Settings, ControllerType}; use crate::shaders::Shaders; use crate::sound::SoundManager; use crate::stage::StageData; @@ -108,6 +109,7 @@ pub struct SharedGameState { pub canvas_size: (f32, f32), pub screen_size: (f32, f32), pub next_scene: Option>, + pub gilrs: Option, pub textscript_vm: TextScriptVM, pub season: Season, pub constants: EngineConstants, @@ -128,7 +130,18 @@ impl SharedGameState { let mut constants = EngineConstants::defaults(); let mut base_path = "/"; - let settings = Settings::load(ctx)?; + let mut settings = Settings::load(ctx)?; + let mut gilrs = Gilrs::new().ok(); + + if let Some(gilrs) = gilrs.as_mut() { + for (id, pad) in gilrs.gamepads() { + log::info!("Found gamepad: {} ({})", pad.name(), id); + } + + if let Some((id, _)) = gilrs.gamepads().next() { + settings.player2_controller_type = ControllerType::Gamepad(id.into()); + } + } if filesystem::exists(ctx, "/base/Nicalis.bmp") { info!("Cave Story+ (PC) data files detected."); @@ -181,6 +194,7 @@ impl SharedGameState { screen_size, canvas_size, next_scene: None, + gilrs, textscript_vm: TextScriptVM::new(), season, constants, diff --git a/src/stage.rs b/src/stage.rs index d616a89..b412535 100644 --- a/src/stage.rs +++ b/src/stage.rs @@ -351,8 +351,6 @@ impl StageData { let npc2 = from_shift_jis(&npc2_buf[0..zero_index(&npc2_buf)]); let name = from_shift_jis(&name_buf[0..zero_index(&name_buf)]); - println!("bg type: {}", bg_type); - let stage = StageData { name: name.clone(), map: map.clone(),