Add gamecube adapter support
This commit is contained in:
Generated
+157
@@ -96,6 +96,12 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler32"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.12"
|
version = "0.8.12"
|
||||||
@@ -2197,6 +2203,26 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "binread"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "258d2d861723c05ad02956cae4878ceddb5e05b0dc2841ca73a3479ab7a46f4d"
|
||||||
|
dependencies = [
|
||||||
|
"binread_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "binread_derive"
|
||||||
|
version = "1.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d63ec80ac459fd10ae68854c26ee1c5ab5ac93866dba8f8a95cabbfeaa79b284"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@@ -3264,6 +3290,16 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -3483,12 +3519,24 @@ dependencies = [
|
|||||||
"bitfields",
|
"bitfields",
|
||||||
"clap",
|
"clap",
|
||||||
"fixed",
|
"fixed",
|
||||||
|
"gc-adapter-neo",
|
||||||
"jackdaw_runtime",
|
"jackdaw_runtime",
|
||||||
"serde",
|
"serde",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gc-adapter-neo"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd350c081bf33e501e9a5a11732294dfe266683d6b58a366b097146f3e633cb5"
|
||||||
|
dependencies = [
|
||||||
|
"binread",
|
||||||
|
"modular-bitfield",
|
||||||
|
"rusb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
@@ -4511,6 +4559,26 @@ version = "0.2.189"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libflate"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18"
|
||||||
|
dependencies = [
|
||||||
|
"adler32",
|
||||||
|
"crc32fast",
|
||||||
|
"libflate_lz77",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libflate_lz77"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf"
|
||||||
|
dependencies = [
|
||||||
|
"rle-decode-fast",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -4549,6 +4617,20 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libusb1-sys"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be241693102a24766d0b8526c8988771edac2842630d7e730f8e9fbc014f3703"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"libflate",
|
||||||
|
"pkg-config",
|
||||||
|
"tar",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linebender_resource_handle"
|
name = "linebender_resource_handle"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -4649,6 +4731,27 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "modular-bitfield"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74"
|
||||||
|
dependencies = [
|
||||||
|
"modular-bitfield-impl",
|
||||||
|
"static_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "modular-bitfield-impl"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moxcms"
|
name = "moxcms"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -6070,6 +6173,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rle-decode-fast"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "robust"
|
name = "robust"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -6128,6 +6237,16 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusb"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6f32cd45962594f9f8cd4547b5757132715600e2c8840aa9ccd3d1a9ed6fdc6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"libusb1-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -6609,6 +6728,17 @@ dependencies = [
|
|||||||
"zeno",
|
"zeno",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.119"
|
version = "2.0.119"
|
||||||
@@ -6677,6 +6807,17 @@ dependencies = [
|
|||||||
"slotmap",
|
"slotmap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tar"
|
||||||
|
version = "0.4.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
|
||||||
|
dependencies = [
|
||||||
|
"filetime",
|
||||||
|
"libc",
|
||||||
|
"xattr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "term"
|
name = "term"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -7076,6 +7217,12 @@ version = "1.0.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ea29db9f94ff08bb619656b8120878f280526f71dc88b5262c958a510181812"
|
checksum = "7ea29db9f94ff08bb619656b8120878f280526f71dc88b5262c958a510181812"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vec_map"
|
name = "vec_map"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@@ -8052,6 +8199,16 @@ version = "0.13.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xattr"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rustix 1.1.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xcursor"
|
name = "xcursor"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ strum = "0.28.0"
|
|||||||
strum_macros = "0.28.0"
|
strum_macros = "0.28.0"
|
||||||
bevy_common_assets = { version = "0.17.0", features = ["ron"] }
|
bevy_common_assets = { version = "0.17.0", features = ["ron"] }
|
||||||
bevy_asset_loader = "0.27.0"
|
bevy_asset_loader = "0.27.0"
|
||||||
|
gc-adapter-neo = { version = "1.0.0", features = ["libusb"] }
|
||||||
|
|
||||||
# Enable a large amount of optimization in the dev profile for dependencies.
|
# Enable a large amount of optimization in the dev profile for dependencies.
|
||||||
[profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
air_acceleration_base: "0.19",
|
air_acceleration_base: "0.19",
|
||||||
air_acceleration_stick: "0.09",
|
air_acceleration_stick: "0.09",
|
||||||
air_friction: "0.05",
|
air_friction: "0.05",
|
||||||
|
|
||||||
|
air_dodge_duration: 49,
|
||||||
|
air_dodge_velocity: "2.790",
|
||||||
|
air_dodge_decay: "0.9"
|
||||||
),
|
),
|
||||||
animations: {
|
animations: {
|
||||||
Wait: "PlyPurin5K_Share_ACTION_Wait",
|
Wait: "PlyPurin5K_Share_ACTION_Wait",
|
||||||
@@ -35,5 +39,6 @@
|
|||||||
JumpForward: "PlyPurin5K_Share_ACTION_JumpF",
|
JumpForward: "PlyPurin5K_Share_ACTION_JumpF",
|
||||||
JumpBack: "PlyPurin5K_Share_ACTION_JumpB",
|
JumpBack: "PlyPurin5K_Share_ACTION_JumpB",
|
||||||
Landing: "PlyPurin5K_Share_ACTION_Landing",
|
Landing: "PlyPurin5K_Share_ACTION_Landing",
|
||||||
|
AirDodge: "PlyPurin5K_Share_ACTION_EscapeAir",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -51,8 +51,6 @@ impl FighterAttributes {
|
|||||||
let accel = accel + direction * self.base_dash_accel;
|
let accel = accel + direction * self.base_dash_accel;
|
||||||
let target_vel = input.movement.x * self.max_dash_vel;
|
let target_vel = input.movement.x * self.max_dash_vel;
|
||||||
|
|
||||||
info!("ACCEL {}", accel);
|
|
||||||
|
|
||||||
(accel, target_vel)
|
(accel, target_vel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ impl FighterStateImpl for JumpSquatState {
|
|||||||
state_context.animations.clips[&AnimKind::JumpSquat],
|
state_context.animations.clips[&AnimKind::JumpSquat],
|
||||||
Duration::ZERO,
|
Duration::ZERO,
|
||||||
);
|
);
|
||||||
|
state_context.input.clear_buffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -14,7 +14,7 @@ use crate::input::FighterInputFrame;
|
|||||||
use crate::netcode::GGRSCfg;
|
use crate::netcode::GGRSCfg;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
|
|
||||||
#[derive(Clone, Copy, EnumIter)]
|
#[derive(Clone, Copy, EnumIter, Debug)]
|
||||||
pub enum FighterCommands {
|
pub enum FighterCommands {
|
||||||
SmashMoveLeft,
|
SmashMoveLeft,
|
||||||
SmashMoveRight,
|
SmashMoveRight,
|
||||||
@@ -153,9 +153,8 @@ pub fn postprocess_input(
|
|||||||
} else {
|
} else {
|
||||||
FighterCommands::SmashMoveLeft
|
FighterCommands::SmashMoveLeft
|
||||||
};
|
};
|
||||||
input.set_lifetime(command_to_set, game_settings.input_common.input_buffer_size);
|
|
||||||
|
|
||||||
if input.current_frame.movement.x > 0.0 {}
|
input.set_lifetime(command_to_set, game_settings.input_common.input_buffer_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
//! Turns [`GcAdapterSnapshot`] port states into real Bevy `Gamepad`
|
||||||
|
//! entities, the same way `bevy_gilrs` turns OS gamepad events into them.
|
||||||
|
|
||||||
|
use bevy::input::gamepad::{
|
||||||
|
GamepadAxis, GamepadButton, GamepadConnection, GamepadConnectionEvent, GamepadSettings,
|
||||||
|
RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
|
||||||
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::poll::{GcAdapterSnapshot, GcPortState};
|
||||||
|
|
||||||
|
/// GameCube adapters report 4 ports.
|
||||||
|
pub const GC_PORT_COUNT: usize = 4;
|
||||||
|
|
||||||
|
/// USB vendor/product ID of the official adapter, reported on the `Gamepad`
|
||||||
|
/// component so it shows up the same way a real USB gamepad would.
|
||||||
|
const GC_ADAPTER_VID: u16 = 0x057e;
|
||||||
|
const GC_ADAPTER_PID: u16 = 0x0337;
|
||||||
|
|
||||||
|
/// Marks a `Gamepad` entity as backed by a specific GameCube adapter port
|
||||||
|
/// (0-3), and is the join key [`crate::input::gamepad::sample_gamepad`]
|
||||||
|
/// uses to map GGRS handles to ports.
|
||||||
|
#[derive(Component, Debug, Clone, Copy)]
|
||||||
|
pub struct GcPort(pub u8);
|
||||||
|
|
||||||
|
/// Tracks which entity (if any) represents each of the 4 adapter ports,
|
||||||
|
/// and the last snapshot seen, so both systems below can diff instead of
|
||||||
|
/// re-deriving state every frame.
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct GcPorts {
|
||||||
|
pub entities: [Option<Entity>; GC_PORT_COUNT],
|
||||||
|
last: [GcPortState; GC_PORT_COUNT],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn passthrough_gamepad_settings() -> GamepadSettings {
|
||||||
|
let mut settings = GamepadSettings::default();
|
||||||
|
for axis in GamepadAxis::all() {
|
||||||
|
let axis_settings = settings.axis_settings.entry(axis).or_default();
|
||||||
|
// Order matters: each setter rejects a value that would cross the
|
||||||
|
// *other* bound it's paired with, so widen the live zones before
|
||||||
|
// shrinking the dead zones down to zero.
|
||||||
|
axis_settings.set_livezone_lowerbound(-1.0);
|
||||||
|
axis_settings.set_livezone_upperbound(1.0);
|
||||||
|
axis_settings.set_deadzone_lowerbound(0.0);
|
||||||
|
axis_settings.set_deadzone_upperbound(0.0);
|
||||||
|
axis_settings.set_threshold(0.0);
|
||||||
|
}
|
||||||
|
for button in GamepadButton::all() {
|
||||||
|
let button_axis_settings = settings.button_axis_settings.entry(button).or_default();
|
||||||
|
button_axis_settings.low = 0.0;
|
||||||
|
button_axis_settings.high = 1.0;
|
||||||
|
button_axis_settings.threshold = 0.0;
|
||||||
|
}
|
||||||
|
settings
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns/despawns `Gamepad` entities to match which adapter ports are
|
||||||
|
/// connected. Must run before `gamepad_connection_system` so the entity it
|
||||||
|
/// spawns already exists by the time Bevy inserts the `Gamepad` component
|
||||||
|
/// on it (mirrors what `bevy_gilrs::gilrs_event_system` does).
|
||||||
|
pub fn gc_connection_system(
|
||||||
|
mut commands: Commands,
|
||||||
|
snapshot: Res<GcAdapterSnapshot>,
|
||||||
|
mut ports: ResMut<GcPorts>,
|
||||||
|
mut connection_events: MessageWriter<GamepadConnectionEvent>,
|
||||||
|
) {
|
||||||
|
let current = snapshot.read();
|
||||||
|
let ports = &mut *ports;
|
||||||
|
|
||||||
|
for i in 0..GC_PORT_COUNT {
|
||||||
|
let was_connected = ports.last[i].connected;
|
||||||
|
let is_connected = current[i].connected;
|
||||||
|
|
||||||
|
if is_connected && !was_connected {
|
||||||
|
let entity = ports.entities[i].unwrap_or_else(|| {
|
||||||
|
let entity = commands
|
||||||
|
.spawn((GcPort(i as u8), passthrough_gamepad_settings()))
|
||||||
|
.id();
|
||||||
|
ports.entities[i] = Some(entity);
|
||||||
|
entity
|
||||||
|
});
|
||||||
|
connection_events.write(GamepadConnectionEvent::new(
|
||||||
|
entity,
|
||||||
|
GamepadConnection::Connected {
|
||||||
|
name: format!("GameCube Controller (Port {})", i + 1),
|
||||||
|
vendor_id: Some(GC_ADAPTER_VID),
|
||||||
|
product_id: Some(GC_ADAPTER_PID),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
} else if !is_connected && was_connected {
|
||||||
|
if let Some(entity) = ports.entities[i] {
|
||||||
|
connection_events.write(GamepadConnectionEvent::new(
|
||||||
|
entity,
|
||||||
|
GamepadConnection::Disconnected,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a GC unsigned trigger byte to the 0.0..=1.0 range
|
||||||
|
/// `GamepadButton` analog values use.
|
||||||
|
fn unsigned_axis_float(raw: u8) -> f32 {
|
||||||
|
(raw as f32) / 255.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Diffs the current snapshot against last frame's and writes
|
||||||
|
/// `RawGamepadEvent`s for anything that changed, which
|
||||||
|
/// `gamepad_event_processing_system` turns into live `Gamepad` component
|
||||||
|
/// state. Must run after `gc_connection_system` (so newly-connected ports
|
||||||
|
/// have an entity/`GamepadSettings`) and before
|
||||||
|
/// `gamepad_event_processing_system`.
|
||||||
|
pub fn gc_event_system(
|
||||||
|
snapshot: Res<GcAdapterSnapshot>,
|
||||||
|
mut ports: ResMut<GcPorts>,
|
||||||
|
mut raw_events: MessageWriter<RawGamepadEvent>,
|
||||||
|
mut axis_events: MessageWriter<RawGamepadAxisChangedEvent>,
|
||||||
|
mut button_events: MessageWriter<RawGamepadButtonChangedEvent>,
|
||||||
|
) {
|
||||||
|
let current = snapshot.read();
|
||||||
|
|
||||||
|
for i in 0..GC_PORT_COUNT {
|
||||||
|
let Some(entity) = ports.entities[i] else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let prev = &ports.last[i];
|
||||||
|
let now = ¤t[i];
|
||||||
|
if !now.connected {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! send_axis {
|
||||||
|
($axis:expr, $value:expr) => {{
|
||||||
|
let event = RawGamepadAxisChangedEvent::new(entity, $axis, $value);
|
||||||
|
axis_events.write(event);
|
||||||
|
raw_events.write(RawGamepadEvent::Axis(event));
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! send_button {
|
||||||
|
($button:expr, $value:expr) => {{
|
||||||
|
let event = RawGamepadButtonChangedEvent::new(entity, $button, $value);
|
||||||
|
button_events.write(event);
|
||||||
|
raw_events.write(RawGamepadEvent::Button(event));
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
if prev.left_stick != now.left_stick {
|
||||||
|
send_axis!(GamepadAxis::LeftStickX, now.left_stick.0);
|
||||||
|
send_axis!(GamepadAxis::LeftStickY, now.left_stick.1);
|
||||||
|
}
|
||||||
|
if prev.right_stick != now.right_stick {
|
||||||
|
send_axis!(
|
||||||
|
GamepadAxis::RightStickX,
|
||||||
|
now.right_stick.0
|
||||||
|
);
|
||||||
|
send_axis!(
|
||||||
|
GamepadAxis::RightStickY,
|
||||||
|
now.right_stick.1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if prev.triggers != now.triggers {
|
||||||
|
// The analog triggers are represented as button-axis values, as we have no GamepadAxis to use.
|
||||||
|
send_button!(GamepadButton::LeftTrigger2, unsigned_axis_float(now.triggers.0));
|
||||||
|
send_button!(GamepadButton::RightTrigger2, unsigned_axis_float(now.triggers.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! digital {
|
||||||
|
($field:ident, $button:expr) => {
|
||||||
|
if prev.$field != now.$field {
|
||||||
|
send_button!($button, if now.$field { 1.0 } else { 0.0 });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
digital!(a, GamepadButton::South);
|
||||||
|
digital!(b, GamepadButton::West);
|
||||||
|
digital!(x, GamepadButton::East);
|
||||||
|
digital!(y, GamepadButton::North);
|
||||||
|
digital!(z, GamepadButton::Z);
|
||||||
|
digital!(start, GamepadButton::Start);
|
||||||
|
digital!(dpad_up, GamepadButton::DPadUp);
|
||||||
|
digital!(dpad_down, GamepadButton::DPadDown);
|
||||||
|
digital!(dpad_left, GamepadButton::DPadLeft);
|
||||||
|
digital!(dpad_right, GamepadButton::DPadRight);
|
||||||
|
digital!(left_trigger_digital, GamepadButton::LeftTrigger);
|
||||||
|
digital!(right_trigger_digital, GamepadButton::RightTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
ports.last = current;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
pub mod bridge;
|
||||||
|
pub mod poll;
|
||||||
|
|
||||||
|
pub use bridge::{GcPort, GcPorts, GC_PORT_COUNT};
|
||||||
|
pub use poll::{GcAdapterSnapshot, GcPortState};
|
||||||
|
|
||||||
|
use bevy::input::gamepad::{gamepad_connection_system, gamepad_event_processing_system};
|
||||||
|
use bevy::input::InputSystems;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GamecubeAdapterPlugin;
|
||||||
|
|
||||||
|
impl Plugin for GamecubeAdapterPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.init_resource::<GcPorts>();
|
||||||
|
|
||||||
|
let Ok(snapshot) = poll::spawn_poll_thread() else {
|
||||||
|
// No adapter connected; leave GcPorts empty and skip wiring the
|
||||||
|
// polling systems.
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
app.insert_resource(snapshot).add_systems(
|
||||||
|
PreUpdate,
|
||||||
|
(
|
||||||
|
bridge::gc_connection_system.before(gamepad_connection_system),
|
||||||
|
bridge::gc_event_system
|
||||||
|
.after(gamepad_connection_system)
|
||||||
|
.before(gamepad_event_processing_system),
|
||||||
|
)
|
||||||
|
.in_set(InputSystems),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
use std::panic::{self, AssertUnwindSafe};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use gc_adapter_neo::{Controller, GcAdapter};
|
||||||
|
#[derive(Clone, Copy, PartialEq, Default)]
|
||||||
|
pub struct GcPortState {
|
||||||
|
pub connected: bool,
|
||||||
|
pub a: bool,
|
||||||
|
pub b: bool,
|
||||||
|
pub x: bool,
|
||||||
|
pub y: bool,
|
||||||
|
pub z: bool,
|
||||||
|
pub start: bool,
|
||||||
|
pub dpad_up: bool,
|
||||||
|
pub dpad_down: bool,
|
||||||
|
pub dpad_left: bool,
|
||||||
|
pub dpad_right: bool,
|
||||||
|
pub left_trigger_digital: bool,
|
||||||
|
pub right_trigger_digital: bool,
|
||||||
|
pub left_stick: (f32, f32),
|
||||||
|
pub right_stick: (f32, f32),
|
||||||
|
/// Raw trigger bytes, 0 is released, 255 is fully pressed.
|
||||||
|
pub triggers: (u8, u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GcPortState {
|
||||||
|
fn make_port_state(c: &Controller, cal: &gc_adapter_neo::PortCalibration) -> Self {
|
||||||
|
if !c.connected() {
|
||||||
|
return Self::default();
|
||||||
|
}
|
||||||
|
let b = &c.buttons;
|
||||||
|
Self {
|
||||||
|
connected: true,
|
||||||
|
a: b.a(),
|
||||||
|
b: b.b(),
|
||||||
|
x: b.x(),
|
||||||
|
y: b.y(),
|
||||||
|
z: b.z(),
|
||||||
|
start: b.start(),
|
||||||
|
dpad_up: b.dpad_up(),
|
||||||
|
dpad_down: b.dpad_down(),
|
||||||
|
dpad_left: b.dpad_left(),
|
||||||
|
dpad_right: b.dpad_right(),
|
||||||
|
left_trigger_digital: b.left_trigger(),
|
||||||
|
right_trigger_digital: b.right_trigger(),
|
||||||
|
left_stick: cal.left_stick(&c),
|
||||||
|
right_stick: cal.right_stick(&c),
|
||||||
|
triggers: (c.triggers.left.raw(), c.triggers.right.raw()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared, `Send + Sync` handle to the latest snapshot from the poll
|
||||||
|
/// thread. Safe to store as an ordinary [`Resource`] (no `NonSend` needed)
|
||||||
|
/// because the `GcAdapter` itself never leaves its thread.
|
||||||
|
#[derive(Resource, Clone)]
|
||||||
|
pub struct GcAdapterSnapshot {
|
||||||
|
ports: Arc<Mutex<[GcPortState; 4]>>,
|
||||||
|
shutdown: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GcAdapterSnapshot {
|
||||||
|
pub fn read(&self) -> [GcPortState; 4] {
|
||||||
|
*self.ports.lock().unwrap_or_else(|e| e.into_inner())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for GcAdapterSnapshot {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Only actually signals shutdown once the last clone (this resource
|
||||||
|
// is never cloned in practice, but be defensive) is dropped.
|
||||||
|
if Arc::strong_count(&self.shutdown) <= 1 {
|
||||||
|
self.shutdown.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connects to the first GameCube adapter found on the USB bus and starts
|
||||||
|
/// polling it on a dedicated thread. Returns `None` (after logging a
|
||||||
|
/// warning) if no adapter is plugged in
|
||||||
|
pub fn spawn_poll_thread() -> Result<GcAdapterSnapshot, gc_adapter_neo::UsbError> {
|
||||||
|
let mut adapter = match GcAdapter::from_usb()? {
|
||||||
|
Some(adapter) => adapter,
|
||||||
|
None => {
|
||||||
|
warn!("No GameCube Controller Adapter found on USB; gamepad input disabled.");
|
||||||
|
return Err(gc_adapter_neo::UsbError::Disconnected);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let ports = Arc::new(Mutex::new([GcPortState::default(); 4]));
|
||||||
|
let shutdown = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
let thread_ports = ports.clone();
|
||||||
|
let thread_shutdown = shutdown.clone();
|
||||||
|
std::thread::Builder::new()
|
||||||
|
.name("gc-adapter-poll".into())
|
||||||
|
.spawn(move || {
|
||||||
|
// Flush whatever the adapter had buffered before we started
|
||||||
|
if let Err(payload) =
|
||||||
|
panic::catch_unwind(AssertUnwindSafe(|| adapter.refresh_inputs()))
|
||||||
|
{
|
||||||
|
error!(
|
||||||
|
"GameCube Controller Adapter USB refresh panicked on startup: {payload:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let mut rate_window_start = std::time::Instant::now();
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let mut rate_window_count: u32 = 0;
|
||||||
|
|
||||||
|
while !thread_shutdown.load(Ordering::Relaxed) {
|
||||||
|
let result =
|
||||||
|
panic::catch_unwind(AssertUnwindSafe(|| adapter.read_controllers()));
|
||||||
|
|
||||||
|
let controllers = match result {
|
||||||
|
Ok(Ok(controllers)) => controllers,
|
||||||
|
// Ignore this, for some reason we get a bunch of these when we start reading
|
||||||
|
Ok(Err(
|
||||||
|
gc_adapter_neo::AdapterError::Parse(_)
|
||||||
|
| gc_adapter_neo::AdapterError::ShortRead(_),
|
||||||
|
)) => continue,
|
||||||
|
Ok(Err(gc_adapter_neo::AdapterError::Usb(err))) => {
|
||||||
|
error!(
|
||||||
|
"GameCube Controller Adapter USB read failed ({err:?}); stopping \
|
||||||
|
polling. Gamepad input will stop updating until the game is \
|
||||||
|
restarted."
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(payload) => {
|
||||||
|
error!(
|
||||||
|
"GameCube Controller Adapter USB read panicked: {payload:?}; \
|
||||||
|
stopping polling. Gamepad input will stop updating until the \
|
||||||
|
game is restarted."
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut states = [GcPortState::default(); 4];
|
||||||
|
for i in 0..4 {
|
||||||
|
states[i] = GcPortState::make_port_state(&controllers[i], adapter.calibration(i));
|
||||||
|
}
|
||||||
|
*thread_ports.lock().unwrap_or_else(|e| e.into_inner()) = states;
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
rate_window_count += 1;
|
||||||
|
let elapsed = rate_window_start.elapsed();
|
||||||
|
if elapsed >= std::time::Duration::from_secs(1) {
|
||||||
|
debug!(
|
||||||
|
"GameCube adapter snapshot publish rate: {:.1} Hz",
|
||||||
|
rate_window_count as f64 / elapsed.as_secs_f64()
|
||||||
|
);
|
||||||
|
rate_window_count = 0;
|
||||||
|
rate_window_start = std::time::Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect("failed to spawn gc-adapter poll thread");
|
||||||
|
|
||||||
|
Ok(GcAdapterSnapshot { ports, shutdown })
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
use bevy::math::Vec2;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::game_settings::GameSettings;
|
||||||
|
use crate::input::map::GamepadBinding;
|
||||||
|
use crate::input::{BaseInputMap, FighterInputFrame, InputMapAction};
|
||||||
|
use crate::math::int::FGi32;
|
||||||
|
use crate::math::vec::FGVec2;
|
||||||
|
|
||||||
|
pub fn sample_gamepad(pad: &Gamepad, input_map: &BaseInputMap, game_settings: &GameSettings) -> FighterInputFrame {
|
||||||
|
let mut input_frame = FighterInputFrame::default();
|
||||||
|
let mut movement = Vec2::ZERO;
|
||||||
|
|
||||||
|
for gamepad_element in &input_map.gamepad {
|
||||||
|
match &gamepad_element.binding {
|
||||||
|
GamepadBinding::Button(button) => {
|
||||||
|
let pressed = pad.pressed(*button);
|
||||||
|
match gamepad_element.action {
|
||||||
|
InputMapAction::Jump => input_frame.jump |= pressed,
|
||||||
|
InputMapAction::Shield => input_frame.shield |= pressed,
|
||||||
|
// Digital buttons driving movement isn't used by the
|
||||||
|
// default bindings (the stick covers that), but stay
|
||||||
|
// consistent with the keyboard backend if one is added.
|
||||||
|
InputMapAction::MovementXDir(sign) if pressed => movement.x += sign as f32,
|
||||||
|
InputMapAction::MovementYDir(sign) if pressed => movement.y += sign as f32,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// gamepad does not have a separate axis for left and right, so sign here should be 1
|
||||||
|
GamepadBinding::Axis(axis, sign) => {
|
||||||
|
let value = pad.get(*axis).unwrap_or(0.0) * (*sign as f32);
|
||||||
|
match gamepad_element.action {
|
||||||
|
InputMapAction::MovementXDir(_) => movement.x += value,
|
||||||
|
InputMapAction::MovementYDir(_) => movement.y += value,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if movement.x.abs() < game_settings.input_common.stick_deadzone {
|
||||||
|
movement.x = 0.0;
|
||||||
|
}
|
||||||
|
if movement.y.abs() < game_settings.input_common.stick_deadzone {
|
||||||
|
movement.y = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if movement.length_squared() > 1.0 {
|
||||||
|
movement = movement.normalize();
|
||||||
|
}
|
||||||
|
input_frame.movement = FGVec2::new(FGi32::from_num(movement.x), FGi32::from_num(movement.y));
|
||||||
|
|
||||||
|
input_frame
|
||||||
|
}
|
||||||
+29
-45
@@ -1,57 +1,41 @@
|
|||||||
//! Keyboard backend: samples the keys into the local input frames GGRS sends
|
//! Keyboard backend: samples the keys into a [`FighterInputFrame`].
|
||||||
//! over the wire.
|
|
||||||
|
|
||||||
use bevy::platform::collections::HashMap;
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_ggrs::{LocalInputs, LocalPlayers};
|
|
||||||
|
|
||||||
use crate::input::{BaseInputMap, FighterInputFrame, InputActionState, InputMapAction};
|
use crate::input::{BaseInputMap, FighterInputFrame, InputActionState, InputMapAction};
|
||||||
use crate::math::int::FGi32;
|
use crate::math::int::FGi32;
|
||||||
use crate::netcode::GGRSCfg;
|
|
||||||
|
|
||||||
pub fn preprocess_keyboard_input(
|
pub fn sample_keyboard(key: &ButtonInput<KeyCode>, input_map: &BaseInputMap) -> FighterInputFrame {
|
||||||
mut commands: Commands,
|
let mut input_frame = FighterInputFrame::default();
|
||||||
key: Res<ButtonInput<KeyCode>>,
|
for keyboard_elment in &input_map.keyboard {
|
||||||
input_map: Res<BaseInputMap>,
|
let action_state = if key.just_pressed(keyboard_elment.key) {
|
||||||
local_players: Res<LocalPlayers>,
|
InputActionState::JustPressed
|
||||||
) {
|
} else if key.just_released(keyboard_elment.key) {
|
||||||
// Keyboard input is only for player 0
|
InputActionState::JustReleased
|
||||||
|
} else if key.pressed(keyboard_elment.key) {
|
||||||
|
InputActionState::Pressed
|
||||||
|
} else {
|
||||||
|
InputActionState::Released
|
||||||
|
};
|
||||||
|
|
||||||
let mut local_inputs = HashMap::new();
|
let action_strength = match action_state {
|
||||||
|
InputActionState::JustPressed | InputActionState::Pressed => FGi32::ONE,
|
||||||
|
InputActionState::JustReleased | InputActionState::Released => FGi32::ZERO,
|
||||||
|
};
|
||||||
|
|
||||||
for handle in &local_players.0 {
|
match keyboard_elment.action {
|
||||||
let mut input_frame = FighterInputFrame::default();
|
InputMapAction::MovementXDir(sign) => {
|
||||||
for keyboard_elment in &input_map.keyboard {
|
input_frame.movement.x += (sign) * action_strength
|
||||||
let action_state = if key.just_pressed(keyboard_elment.key) {
|
|
||||||
InputActionState::JustPressed
|
|
||||||
} else if key.just_released(keyboard_elment.key) {
|
|
||||||
InputActionState::JustReleased
|
|
||||||
} else if key.pressed(keyboard_elment.key) {
|
|
||||||
InputActionState::Pressed
|
|
||||||
} else {
|
|
||||||
InputActionState::Released
|
|
||||||
};
|
|
||||||
|
|
||||||
let action_strength = match action_state {
|
|
||||||
InputActionState::JustPressed | InputActionState::Pressed => FGi32::ONE,
|
|
||||||
InputActionState::JustReleased | InputActionState::Released => FGi32::ZERO,
|
|
||||||
};
|
|
||||||
|
|
||||||
match keyboard_elment.action {
|
|
||||||
InputMapAction::MovementXDir(sign) => {
|
|
||||||
input_frame.movement.x += (sign) * action_strength
|
|
||||||
}
|
|
||||||
InputMapAction::MovementYDir(sign) => {
|
|
||||||
input_frame.movement.y += (sign) * action_strength
|
|
||||||
}
|
|
||||||
InputMapAction::Jump => input_frame.jump = action_strength != FGi32::ZERO,
|
|
||||||
InputMapAction::Shield => input_frame.shield = action_strength != FGi32::ZERO,
|
|
||||||
}
|
}
|
||||||
|
InputMapAction::MovementYDir(sign) => {
|
||||||
|
input_frame.movement.y += (sign) * action_strength
|
||||||
|
}
|
||||||
|
InputMapAction::Jump => input_frame.jump = action_strength != FGi32::ZERO,
|
||||||
|
InputMapAction::Shield => input_frame.shield = action_strength != FGi32::ZERO,
|
||||||
}
|
}
|
||||||
|
|
||||||
input_frame.movement = input_frame.movement.normalize_or_zero();
|
|
||||||
input_frame.directional_attack = input_frame.directional_attack.normalize_or_zero();
|
|
||||||
local_inputs.insert(*handle, input_frame);
|
|
||||||
}
|
}
|
||||||
commands.insert_resource(LocalInputs::<GGRSCfg>(local_inputs));
|
|
||||||
|
input_frame.movement = input_frame.movement.normalize_or_zero();
|
||||||
|
input_frame.directional_attack = input_frame.directional_attack.normalize_or_zero();
|
||||||
|
input_frame
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-4
@@ -1,7 +1,3 @@
|
|||||||
//! What a physical button means. Device-agnostic: a binding names an
|
|
||||||
//! [`InputMapAction`], and each device backend (see [`super::keyboard`])
|
|
||||||
//! decides how to read it.
|
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -28,7 +24,20 @@ pub struct KeyboardInputMapElement {
|
|||||||
pub action: InputMapAction,
|
pub action: InputMapAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum GamepadBinding {
|
||||||
|
Button(GamepadButton),
|
||||||
|
Axis(GamepadAxis, i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GamepadInputMapElement {
|
||||||
|
pub binding: GamepadBinding,
|
||||||
|
pub action: InputMapAction,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct BaseInputMap {
|
pub struct BaseInputMap {
|
||||||
pub keyboard: Vec<KeyboardInputMapElement>,
|
pub keyboard: Vec<KeyboardInputMapElement>,
|
||||||
|
pub gamepad: Vec<GamepadInputMapElement>,
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-6
@@ -1,23 +1,28 @@
|
|||||||
//! Input, from physical device to the buffered per-fighter state that the
|
//! Input, from physical device to the buffered per-fighter state that the
|
||||||
//! state machine reads.
|
//! state machine reads.
|
||||||
//!
|
|
||||||
//! The pipeline is: [`keyboard`] samples devices into [`FighterInputFrame`]s
|
|
||||||
//! that GGRS ships over the wire, then [`buffer::postprocess_input`] turns the
|
|
||||||
//! rolled-back frames into [`FighterInput`].
|
|
||||||
|
|
||||||
|
use bevy::platform::collections::HashMap;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_ggrs::prelude::*;
|
use bevy_ggrs::prelude::*;
|
||||||
|
use bevy_ggrs::{LocalInputs, LocalPlayers};
|
||||||
|
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
pub mod control;
|
pub mod control;
|
||||||
pub mod frame;
|
pub mod frame;
|
||||||
|
pub mod gamecube;
|
||||||
|
pub mod gamepad;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod map;
|
pub mod map;
|
||||||
|
|
||||||
pub use buffer::{FighterCommands, FighterInput};
|
pub use buffer::{FighterCommands, FighterInput};
|
||||||
pub use frame::FighterInputFrame;
|
pub use frame::FighterInputFrame;
|
||||||
pub use map::{BaseInputMap, InputActionState, InputMapAction, KeyboardInputMapElement};
|
pub use map::{
|
||||||
|
BaseInputMap, GamepadBinding, GamepadInputMapElement, InputActionState, InputMapAction,
|
||||||
|
KeyboardInputMapElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::game_settings::GameSettings;
|
||||||
|
use crate::netcode::GGRSCfg;
|
||||||
use crate::schedule::GameplaySet;
|
use crate::schedule::GameplaySet;
|
||||||
|
|
||||||
/// Owns the input map, the local-input collection that feeds GGRS, and the
|
/// Owns the input map, the local-input collection that feeds GGRS, and the
|
||||||
@@ -60,8 +65,34 @@ impl Plugin for FighterInputPlugin {
|
|||||||
action: InputMapAction::Shield,
|
action: InputMapAction::Shield,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
gamepad: vec![
|
||||||
|
// left stick
|
||||||
|
GamepadInputMapElement {
|
||||||
|
binding: GamepadBinding::Axis(GamepadAxis::LeftStickX, 1),
|
||||||
|
action: InputMapAction::MovementXDir(1),
|
||||||
|
},
|
||||||
|
GamepadInputMapElement {
|
||||||
|
binding: GamepadBinding::Axis(GamepadAxis::LeftStickY, 1),
|
||||||
|
action: InputMapAction::MovementYDir(1),
|
||||||
|
},
|
||||||
|
// jump
|
||||||
|
GamepadInputMapElement {
|
||||||
|
binding: GamepadBinding::Button(GamepadButton::North),
|
||||||
|
action: InputMapAction::Jump,
|
||||||
|
},
|
||||||
|
// shield/airdodge -- both shoulder buttons, Melee-style
|
||||||
|
GamepadInputMapElement {
|
||||||
|
binding: GamepadBinding::Button(GamepadButton::LeftTrigger),
|
||||||
|
action: InputMapAction::Shield,
|
||||||
|
},
|
||||||
|
GamepadInputMapElement {
|
||||||
|
binding: GamepadBinding::Button(GamepadButton::RightTrigger),
|
||||||
|
action: InputMapAction::Shield,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
.add_systems(ReadInputs, keyboard::preprocess_keyboard_input)
|
.add_plugins(gamecube::GamecubeAdapterPlugin)
|
||||||
|
.add_systems(ReadInputs, read_local_inputs)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
buffer::postprocess_input.in_set(GameplaySet::Input),
|
buffer::postprocess_input.in_set(GameplaySet::Input),
|
||||||
@@ -69,3 +100,32 @@ impl Plugin for FighterInputPlugin {
|
|||||||
.rollback_component_with_clone::<FighterInput>();
|
.rollback_component_with_clone::<FighterInput>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_local_inputs(
|
||||||
|
mut commands: Commands,
|
||||||
|
key: Res<ButtonInput<KeyCode>>,
|
||||||
|
input_map: Res<BaseInputMap>,
|
||||||
|
gc_ports: Res<gamecube::GcPorts>,
|
||||||
|
gamepads: Query<&Gamepad>,
|
||||||
|
local_players: Res<LocalPlayers>,
|
||||||
|
game_settings: Res<GameSettings>
|
||||||
|
) {
|
||||||
|
let mut local_inputs = HashMap::new();
|
||||||
|
|
||||||
|
for handle in &local_players.0 {
|
||||||
|
let gamepad = gc_ports
|
||||||
|
.entities
|
||||||
|
.get(*handle)
|
||||||
|
.copied()
|
||||||
|
.flatten()
|
||||||
|
.and_then(|entity| gamepads.get(entity).ok());
|
||||||
|
|
||||||
|
let input_frame = match gamepad {
|
||||||
|
Some(pad) => gamepad::sample_gamepad(pad, &input_map, &game_settings),
|
||||||
|
None => keyboard::sample_keyboard(&key, &input_map),
|
||||||
|
};
|
||||||
|
|
||||||
|
local_inputs.insert(*handle, input_frame);
|
||||||
|
}
|
||||||
|
commands.insert_resource(LocalInputs::<GGRSCfg>(local_inputs));
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,13 +57,6 @@ pub enum AppState {
|
|||||||
MatchLoadFailed,
|
MatchLoadFailed,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_gamepads(q: Query<(&Name, &Gamepad)>) {
|
|
||||||
info!("GP:");
|
|
||||||
for (name, gp) in q {
|
|
||||||
info!("Gamepad detected: {}", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Your game's Bevy plugin. The editor finds it by this name (override
|
/// Your game's Bevy plugin. The editor finds it by this name (override
|
||||||
/// with `plugin = "..."` in jackdaw.toml) and runs it on Play; the
|
/// with `plugin = "..."` in jackdaw.toml) and runs it on Play; the
|
||||||
/// standalone binary adds it in `main.rs`.
|
/// standalone binary adds it in `main.rs`.
|
||||||
@@ -106,7 +99,6 @@ impl Plugin for GamePlugin {
|
|||||||
.on_failure_continue_to_state(AppState::CommonAssetLoadFailed)
|
.on_failure_continue_to_state(AppState::CommonAssetLoadFailed)
|
||||||
.load_collection::<fighter::manifest::FighterManifestAssets>(),
|
.load_collection::<fighter::manifest::FighterManifestAssets>(),
|
||||||
)
|
)
|
||||||
.add_systems(Startup, show_gamepads)
|
|
||||||
.add_systems(
|
.add_systems(
|
||||||
OnEnter(AppState::PrepareFighterManifests),
|
OnEnter(AppState::PrepareFighterManifests),
|
||||||
fighter::manifest::prepare_fighter_manifests,
|
fighter::manifest::prepare_fighter_manifests,
|
||||||
|
|||||||
Reference in New Issue
Block a user