mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-07-12 15:56:03 +00:00
Initial support for gamepads (P2 only)
This commit is contained in:
parent
5f259b572e
commit
d0b58a5aa1
|
@ -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"}
|
||||
|
|
195
src/input/gilrs_player_controller.rs
Normal file
195
src/input/gilrs_player_controller.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)]
|
||||
|
|
|
@ -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;
|
||||
|
|
27
src/lib.rs
27
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Settings> {
|
||||
Ok(Settings::default())
|
||||
}
|
||||
|
||||
pub fn create_player1_controller(&self) -> Box<dyn PlayerController> {
|
||||
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<dyn PlayerController> {
|
||||
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,
|
||||
|
|
|
@ -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<Box<dyn Scene>>,
|
||||
pub gilrs: Option<Gilrs>,
|
||||
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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in a new issue