very initial gamepad support

This commit is contained in:
Sallai József 2022-07-20 16:07:24 +03:00
parent 6d8e58090a
commit 9932b1209f
9 changed files with 569 additions and 8 deletions

View File

@ -59,6 +59,7 @@ cpal = "0.13"
directories = "3"
downcast = "0.11"
funty = "=1.1.0" # https://github.com/bitvecto-rs/bitvec/issues/105
gilrs = { version = "0.9.0", features = ["serde-serialize"] }
glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "8dd457b9adb7dbac7ade337246b6356c784272d9", optional = true, default_features = false, features = ["x11"] }
imgui = "0.8.0"
image = { version = "0.23", default-features = false, features = ["png", "bmp"] }

View File

@ -1,15 +1,17 @@
use crate::framework::backend::{init_backend, BackendRenderer};
use crate::framework::error::GameResult;
use crate::framework::filesystem::Filesystem;
use crate::framework::gamepad::GamepadContext;
use crate::framework::keyboard::KeyboardContext;
use crate::Game;
use crate::graphics::VSyncMode;
use crate::Game;
pub struct Context {
pub headless: bool,
pub size_hint: (u16, u16),
pub(crate) filesystem: Filesystem,
pub(crate) renderer: Option<Box<dyn BackendRenderer>>,
pub(crate) gamepad_context: GamepadContext,
pub(crate) keyboard_context: KeyboardContext,
pub(crate) real_screen_size: (u32, u32),
pub(crate) screen_size: (f32, f32),
@ -24,6 +26,7 @@ impl Context {
size_hint: (640, 480),
filesystem: Filesystem::new(),
renderer: None,
gamepad_context: GamepadContext::new(),
keyboard_context: KeyboardContext::new(),
real_screen_size: (320, 240),
screen_size: (320.0, 240.0),

160
src/framework/gamepad.rs Normal file
View File

@ -0,0 +1,160 @@
use std::collections::HashMap;
use gilrs::{Axis, Button, Gamepad, GamepadId};
use serde::{Deserialize, Serialize};
use crate::{framework::context::Context, settings::PlayerControllerInputType};
#[derive(Clone, Debug)]
pub enum AxisDirection {
None,
Up,
Left,
Right,
Down,
}
impl AxisDirection {
pub fn compare(&self, value: f64, axis_sensitivity: f64) -> bool {
match self {
AxisDirection::None => false,
AxisDirection::Up => value > axis_sensitivity,
AxisDirection::Left => value < -axis_sensitivity,
AxisDirection::Right => value > axis_sensitivity,
AxisDirection::Down => value < -axis_sensitivity,
}
}
}
#[derive(Clone, Debug)]
pub struct GamepadData {
left_x: f64,
left_y: f64,
right_x: f64,
right_y: f64,
axis_sensitivity: f64,
}
impl GamepadData {
pub(crate) fn new(axis_sensitivity: f64) -> Self {
GamepadData { left_x: 0.0, left_y: 0.0, right_x: 0.0, right_y: 0.0, axis_sensitivity }
}
}
#[derive(Clone, Debug)]
pub struct GamepadContext {
gamepads: HashMap<GamepadId, GamepadData>,
}
impl GamepadContext {
pub(crate) fn new() -> Self {
Self { gamepads: HashMap::new() }
}
fn gamepad_exists(&self, gamepad: &Gamepad) -> bool {
self.gamepads.contains_key(&gamepad.id())
}
pub(crate) fn add_gamepad(&mut self, gamepad: &Gamepad, axis_sensitivity: f64) {
self.gamepads.insert(gamepad.id(), GamepadData::new(axis_sensitivity));
}
pub(crate) fn remove_gamepad(&mut self, gamepad: &Gamepad) {
self.gamepads.remove(&gamepad.id());
}
pub(crate) fn is_active(
&self,
gamepad: &Gamepad,
input_type: &PlayerControllerInputType,
axis_direction: AxisDirection,
) -> bool {
match input_type {
PlayerControllerInputType::ButtonInput(button) => self.is_button_active(gamepad, *button),
PlayerControllerInputType::AxisInput(axis) => self.is_axis_active(gamepad, *axis, axis_direction),
PlayerControllerInputType::Either(button, axis) => {
self.is_button_active(gamepad, *button) || self.is_axis_active(gamepad, *axis, axis_direction)
}
}
}
pub(crate) fn is_button_active(&self, gamepad: &Gamepad, button: Button) -> bool {
if !self.gamepad_exists(gamepad) {
return false;
}
gamepad.is_pressed(button)
}
pub(crate) fn is_axis_active(&self, gamepad: &Gamepad, axis: Axis, direction: AxisDirection) -> bool {
if !self.gamepad_exists(gamepad) {
return false;
}
let data = self.gamepads.get(&gamepad.id()).unwrap();
match axis {
Axis::LeftStickX => direction.compare(data.left_x, data.axis_sensitivity),
Axis::LeftStickY => direction.compare(data.left_y, data.axis_sensitivity),
Axis::RightStickX => direction.compare(data.right_x, data.axis_sensitivity),
Axis::RightStickY => direction.compare(data.right_y, data.axis_sensitivity),
_ => false,
}
}
pub(crate) fn update_axes(&mut self, gamepad: &Gamepad) {
if !self.gamepad_exists(gamepad) {
return;
}
let data = self.gamepads.get_mut(&gamepad.id()).unwrap();
let mut axes = [
(&mut data.left_x, Axis::LeftStickX),
(&mut data.left_y, Axis::LeftStickY),
(&mut data.right_x, Axis::RightStickX),
(&mut data.right_y, Axis::RightStickY),
];
for (axis_val, id) in axes.iter_mut() {
if let Some(axis) = gamepad.axis_data(*id) {
**axis_val = if axis.value().abs() < 0.12 { 0.0 } else { axis.value() } as f64;
}
}
}
}
impl Default for GamepadContext {
fn default() -> Self {
Self::new()
}
}
pub fn add_gamepad(context: &mut Context, gamepad: &Gamepad, axis_sensitivity: f64) {
context.gamepad_context.add_gamepad(gamepad, axis_sensitivity);
}
pub fn remove_gamepad(context: &mut Context, gamepad: &Gamepad) {
context.gamepad_context.remove_gamepad(gamepad);
}
pub fn is_active(
ctx: &Context,
gamepad: &Gamepad,
input_type: &PlayerControllerInputType,
axis_direction: AxisDirection,
) -> bool {
ctx.gamepad_context.is_active(gamepad, input_type, axis_direction)
}
pub fn is_button_active(ctx: &Context, gamepad: &Gamepad, button: Button) -> bool {
ctx.gamepad_context.is_button_active(gamepad, button)
}
pub fn is_axis_active(ctx: &Context, gamepad: &Gamepad, axis: Axis, direction: AxisDirection) -> bool {
ctx.gamepad_context.is_axis_active(gamepad, axis, direction)
}
pub fn update_axes(ctx: &mut Context, gamepad: &Gamepad) {
ctx.gamepad_context.update_axes(gamepad);
}

View File

@ -1,16 +1,17 @@
#![allow(unused)]
pub mod backend;
pub mod backend_null;
#[cfg(feature = "backend-glutin")]
pub mod backend_glutin;
#[cfg(feature = "render-opengl")]
mod gl;
pub mod backend_null;
#[cfg(feature = "backend-sdl")]
pub mod backend_sdl2;
pub mod context;
pub mod error;
pub mod filesystem;
pub mod gamepad;
#[cfg(feature = "render-opengl")]
mod gl;
pub mod graphics;
pub mod keyboard;
#[cfg(feature = "render-opengl")]

View File

@ -0,0 +1,247 @@
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::gamepad::{self, AxisDirection};
use crate::input::player_controller::PlayerController;
use crate::player::TargetPlayer;
use crate::shared_game_state::SharedGameState;
use crate::{bitfield, settings::PlayerControllerInputType};
use gilrs::{Button, GamepadId};
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 escape, set_escape: 10;
pub enter, set_enter: 11;
pub skip, set_skip: 12;
pub strafe, set_strafe: 13;
}
#[derive(Clone)]
pub struct GamepadController {
gamepad_id: GamepadId,
target: TargetPlayer,
state: KeyState,
old_state: KeyState,
trigger: KeyState,
}
impl GamepadController {
pub fn new(gamepad_id: GamepadId, target: TargetPlayer) -> GamepadController {
GamepadController { gamepad_id, target, state: KeyState(0), old_state: KeyState(0), trigger: KeyState(0) }
}
}
impl PlayerController for GamepadController {
fn update(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let button_map = match self.target {
TargetPlayer::Player1 => &state.settings.player1_controller_button_map,
TargetPlayer::Player2 => &state.settings.player2_controller_button_map,
};
if let Some(gilrs) = &state.gilrs {
if let Some(gamepad) = gilrs.connected_gamepad(self.gamepad_id) {
gamepad::update_axes(ctx, &gamepad);
self.state.set_up(gamepad::is_active(ctx, &gamepad, &button_map.up, AxisDirection::Up));
self.state.set_down(gamepad::is_active(ctx, &gamepad, &button_map.down, AxisDirection::Down));
self.state.set_left(gamepad::is_active(ctx, &gamepad, &button_map.left, AxisDirection::Left));
self.state.set_right(gamepad::is_active(ctx, &gamepad, &button_map.right, AxisDirection::Right));
self.state.set_map(gamepad::is_active(ctx, &gamepad, &button_map.map, AxisDirection::None));
self.state.set_inventory(gamepad::is_active(ctx, &gamepad, &button_map.inventory, AxisDirection::None));
self.state.set_jump(gamepad::is_active(ctx, &gamepad, &button_map.jump, AxisDirection::None));
self.state.set_shoot(gamepad::is_active(ctx, &gamepad, &button_map.shoot, AxisDirection::None));
self.state.set_next_weapon(gamepad::is_active(
ctx,
&gamepad,
&button_map.next_weapon,
AxisDirection::None,
));
self.state.set_prev_weapon(gamepad::is_active(
ctx,
&gamepad,
&button_map.prev_weapon,
AxisDirection::None,
));
self.state.set_escape(gamepad::is_active(
ctx,
&gamepad,
&PlayerControllerInputType::ButtonInput(Button::Start),
AxisDirection::None,
));
self.state.set_enter(gamepad::is_active(ctx, &gamepad, &button_map.jump, AxisDirection::None));
self.state.set_skip(gamepad::is_active(ctx, &gamepad, &button_map.skip, AxisDirection::None));
self.state.set_strafe(gamepad::is_active(ctx, &gamepad, &button_map.strafe, AxisDirection::None));
}
}
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.state.up()
}
fn move_left(&self) -> bool {
self.state.left()
}
fn move_down(&self) -> bool {
self.state.down()
}
fn move_right(&self) -> bool {
self.state.right()
}
fn prev_weapon(&self) -> bool {
self.state.prev_weapon()
}
fn next_weapon(&self) -> bool {
self.state.next_weapon()
}
fn map(&self) -> bool {
self.state.map()
}
fn inventory(&self) -> bool {
self.state.inventory()
}
fn jump(&self) -> bool {
self.state.jump()
}
fn shoot(&self) -> bool {
self.state.shoot()
}
fn skip(&self) -> bool {
self.state.skip()
}
fn strafe(&self) -> bool {
self.state.strafe()
}
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_map(&self) -> bool {
self.trigger.map()
}
fn trigger_inventory(&self) -> bool {
self.trigger.inventory()
}
fn trigger_jump(&self) -> bool {
self.trigger.jump()
}
fn trigger_shoot(&self) -> bool {
self.trigger.shoot()
}
fn trigger_skip(&self) -> bool {
self.trigger.skip()
}
fn trigger_strafe(&self) -> bool {
self.trigger.strafe()
}
fn trigger_menu_ok(&self) -> bool {
self.trigger.jump() || self.trigger.enter()
}
fn trigger_menu_back(&self) -> bool {
self.trigger.shoot() || self.trigger.escape()
}
fn trigger_menu_pause(&self) -> bool {
self.trigger.escape()
}
fn look_up(&self) -> bool {
self.state.up()
}
fn look_left(&self) -> bool {
self.state.left()
}
fn look_down(&self) -> bool {
self.state.down()
}
fn look_right(&self) -> bool {
self.state.right()
}
fn move_analog_x(&self) -> f64 {
if self.state.left() && self.state.right() {
0.0
} else if self.state.left() {
-1.0
} else if self.state.right() {
1.0
} else {
0.0
}
}
fn move_analog_y(&self) -> f64 {
if self.state.up() && self.state.down() {
0.0
} else if self.state.up() {
-1.0
} else if self.state.down() {
1.0
} else {
0.0
}
}
}

View File

@ -1,5 +1,6 @@
pub mod combined_menu_controller;
pub mod dummy_player_controller;
pub mod gamepad_player_controller;
pub mod keyboard_player_controller;
pub mod player_controller;
pub mod replay_player_controller;

View File

@ -11,6 +11,8 @@ use std::sync::Mutex;
use std::time::{Duration, Instant};
use directories::ProjectDirs;
use framework::gamepad;
use gilrs::EventType;
use lazy_static::lazy_static;
use crate::builtin_fs::BuiltinFS;
@ -110,6 +112,34 @@ impl Game {
}
fn update(&mut self, ctx: &mut Context) -> GameResult {
{
let state_ref = unsafe { &mut *self.state.get() };
if let Some(gilrs) = &mut state_ref.gilrs {
while let Some(e) = gilrs.next_event() {
let gamepad = gilrs.gamepad(e.id);
match e.event {
EventType::Connected => {
log::info!("Gamepad connected: {} (ID: {})", gamepad.name(), gamepad.id());
let axis_sensitivity = state_ref.settings.get_gamepad_axis_sensitivity(gamepad.id());
gamepad::add_gamepad(ctx, &gamepad, axis_sensitivity);
// TODO: replace the controller of all players that use this gamepad from keyboard to gamepad
}
EventType::Disconnected => {
log::info!("Gamepad disconnected: {} (ID: {})", gamepad.name(), gamepad.id());
gamepad::remove_gamepad(ctx, &gamepad);
// TODO: fall back to keyboard for all players that use this gamepad
}
_ => {}
}
}
}
}
if let Some(scene) = &mut self.scene {
let state_ref = unsafe { &mut *self.state.get() };

View File

@ -1,8 +1,11 @@
use gilrs::{Axis, Button, GamepadId};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem::{user_create, user_open};
use crate::framework::keyboard::ScanCode;
use crate::graphics::VSyncMode;
use crate::input::gamepad_player_controller::GamepadController;
use crate::input::keyboard_player_controller::KeyboardController;
use crate::input::player_controller::PlayerController;
use crate::input::touch_player_controller::TouchPlayerController;
@ -34,10 +37,22 @@ pub struct Settings {
pub timing_mode: TimingMode,
#[serde(default = "default_interpolation")]
pub organya_interpolation: InterpolationMode,
#[serde(default = "default_controller_type")]
pub player1_controller_type: ControllerType,
#[serde(default = "default_controller_type")]
pub player2_controller_type: ControllerType,
#[serde(default = "p1_default_keymap")]
pub player1_key_map: PlayerKeyMap,
#[serde(default = "p2_default_keymap")]
pub player2_key_map: PlayerKeyMap,
#[serde(default = "player_default_controller_button_map")]
pub player1_controller_button_map: PlayerControllerButtonMap,
#[serde(default = "player_default_controller_button_map")]
pub player2_controller_button_map: PlayerControllerButtonMap,
#[serde(default = "default_controller_axis_sensitivity")]
pub player1_controller_axis_sensitivity: f64,
#[serde(default = "default_controller_axis_sensitivity")]
pub player2_controller_axis_sensitivity: f64,
#[serde(skip, default = "default_speed")]
pub speed: f64,
#[serde(skip)]
@ -65,7 +80,7 @@ fn default_true() -> bool {
#[inline(always)]
fn current_version() -> u32 {
11
12
}
#[inline(always)]
@ -108,6 +123,11 @@ fn default_screen_shake_intensity() -> ScreenShakeIntensity {
ScreenShakeIntensity::Full
}
#[inline(always)]
fn default_controller_type() -> ControllerType {
ControllerType::Keyboard
}
impl Settings {
pub fn load(ctx: &Context) -> GameResult<Settings> {
if let Ok(file) = user_open(ctx, "/settings.json") {
@ -170,6 +190,16 @@ impl Settings {
self.window_mode = default_window_mode();
}
if self.version == 11 {
self.version = 12;
self.player1_controller_type = default_controller_type();
self.player2_controller_type = default_controller_type();
self.player1_controller_button_map = player_default_controller_button_map();
self.player2_controller_button_map = player_default_controller_button_map();
self.player1_controller_axis_sensitivity = default_controller_axis_sensitivity();
self.player2_controller_axis_sensitivity = default_controller_axis_sensitivity();
}
if self.version != initial_version {
log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version);
}
@ -189,11 +219,27 @@ impl Settings {
return Box::new(TouchPlayerController::new());
}
Box::new(KeyboardController::new(TargetPlayer::Player1))
match self.player1_controller_type {
ControllerType::Keyboard => Box::new(KeyboardController::new(TargetPlayer::Player1)),
ControllerType::Gamepad(id) => Box::new(GamepadController::new(id, TargetPlayer::Player1)),
}
}
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(GamepadController::new(id, TargetPlayer::Player2)),
}
}
pub fn get_gamepad_axis_sensitivity(&self, id: GamepadId) -> f64 {
if self.player1_controller_type == ControllerType::Gamepad(id) {
self.player1_controller_axis_sensitivity
} else if self.player2_controller_type == ControllerType::Gamepad(id) {
self.player2_controller_axis_sensitivity
} else {
default_controller_axis_sensitivity()
}
}
}
@ -213,8 +259,14 @@ impl Default for Settings {
sfx_volume: 1.0,
timing_mode: default_timing(),
organya_interpolation: InterpolationMode::Linear,
player1_controller_type: default_controller_type(),
player2_controller_type: default_controller_type(),
player1_key_map: p1_default_keymap(),
player2_key_map: p2_default_keymap(),
player1_controller_button_map: player_default_controller_button_map(),
player2_controller_button_map: player_default_controller_button_map(),
player1_controller_axis_sensitivity: default_controller_axis_sensitivity(),
player2_controller_axis_sensitivity: default_controller_axis_sensitivity(),
speed: 1.0,
god_mode: false,
infinite_booster: false,
@ -281,3 +333,55 @@ fn p2_default_keymap() -> PlayerKeyMap {
strafe: ScanCode::RShift,
}
}
#[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq)]
pub enum ControllerType {
Keyboard,
Gamepad(GamepadId),
}
#[derive(serde::Serialize, serde::Deserialize)]
pub enum PlayerControllerInputType {
ButtonInput(Button),
AxisInput(Axis),
Either(Button, Axis),
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct PlayerControllerButtonMap {
pub left: PlayerControllerInputType,
pub up: PlayerControllerInputType,
pub right: PlayerControllerInputType,
pub down: PlayerControllerInputType,
pub prev_weapon: PlayerControllerInputType,
pub next_weapon: PlayerControllerInputType,
pub jump: PlayerControllerInputType,
pub shoot: PlayerControllerInputType,
pub skip: PlayerControllerInputType,
pub inventory: PlayerControllerInputType,
pub map: PlayerControllerInputType,
pub strafe: PlayerControllerInputType,
}
#[inline(always)]
pub fn player_default_controller_button_map() -> PlayerControllerButtonMap {
PlayerControllerButtonMap {
left: PlayerControllerInputType::Either(Button::DPadLeft, Axis::LeftStickX),
up: PlayerControllerInputType::Either(Button::DPadUp, Axis::LeftStickY),
right: PlayerControllerInputType::Either(Button::DPadRight, Axis::LeftStickX),
down: PlayerControllerInputType::Either(Button::DPadDown, Axis::LeftStickY),
prev_weapon: PlayerControllerInputType::ButtonInput(Button::LeftTrigger),
next_weapon: PlayerControllerInputType::ButtonInput(Button::RightTrigger),
jump: PlayerControllerInputType::ButtonInput(Button::East),
shoot: PlayerControllerInputType::ButtonInput(Button::South),
skip: PlayerControllerInputType::ButtonInput(Button::LeftTrigger2),
strafe: PlayerControllerInputType::ButtonInput(Button::RightTrigger2),
inventory: PlayerControllerInputType::ButtonInput(Button::North),
map: PlayerControllerInputType::ButtonInput(Button::West),
}
}
#[inline(always)]
pub fn default_controller_axis_sensitivity() -> f64 {
0.3
}

View File

@ -15,7 +15,7 @@ use crate::framework::error::GameResult;
use crate::framework::graphics::{create_texture_mutable, set_render_target};
use crate::framework::keyboard::ScanCode;
use crate::framework::vfs::OpenOptions;
use crate::framework::{filesystem, graphics};
use crate::framework::{filesystem, gamepad, graphics};
#[cfg(feature = "hooks")]
use crate::hooks::init_hooks;
use crate::i18n::Locale;
@ -38,6 +38,8 @@ use crate::stage::StageData;
use crate::texture_set::TextureSet;
use crate::vanilla::VanillaExtractor;
use gilrs::Gilrs;
#[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum TimingMode {
_50Hz,
@ -287,6 +289,7 @@ pub struct SharedGameState {
pub super_quake_counter: u16,
pub teleporter_slots: Vec<(u16, u16)>,
pub carets: Vec<Caret>,
pub gilrs: Option<Gilrs>,
pub touch_controls: TouchControls,
pub mod_path: Option<String>,
pub mod_list: ModList,
@ -331,6 +334,16 @@ impl SharedGameState {
let mut sound_manager = SoundManager::new(ctx)?;
let settings = Settings::load(ctx)?;
let mod_requirements = ModRequirements::load(ctx)?;
let mut gilrs = Gilrs::new().ok();
if let Some(gilrs) = &mut gilrs {
for (id, gamepad) in gilrs.gamepads() {
log::info!("Found gamepad {} (ID {})", gamepad.name(), id);
let axis_sensitivity = settings.get_gamepad_axis_sensitivity(id);
gamepad::add_gamepad(ctx, &gamepad, axis_sensitivity);
}
}
let vanilla_extractor = VanillaExtractor::from(ctx, "Doukutsu.exe".to_string());
if vanilla_extractor.is_some() {
@ -424,6 +437,7 @@ impl SharedGameState {
super_quake_counter: 0,
teleporter_slots: Vec::with_capacity(8),
carets: Vec::with_capacity(32),
gilrs,
touch_controls: TouchControls::new(),
mod_path: None,
mod_list,