first support for gamepad controls

This commit is contained in:
2025-03-15 21:28:19 +01:00
parent b73e8b802c
commit 02587135b2
3 changed files with 158 additions and 55 deletions

135
src/controls.rs Normal file
View File

@@ -0,0 +1,135 @@
use bevy::{
input::{
ButtonState,
mouse::{MouseButtonInput, MouseMotion},
},
prelude::*,
};
use crate::shooting::TriggerState;
#[derive(Resource, Debug, Clone, Copy, Default, PartialEq)]
pub struct ControlState {
pub move_dir: Vec2,
pub look_dir: Vec2,
pub jump: bool,
}
#[derive(Resource, Debug, Default)]
pub struct Controls {
pub keyboard_state: ControlState,
pub gamepad_state: Option<ControlState>,
}
pub fn plugin(app: &mut App) {
app.init_resource::<Controls>();
app.add_systems(PreUpdate, clear_mouse_delta);
app.add_systems(Update, (gamepad_controls, keyboard_controls, mouse_rotate));
app.add_systems(Update, mouse_click.run_if(on_event::<MouseButtonInput>));
}
fn gamepad_controls(
mut commands: Commands,
gamepads: Query<(Entity, &Gamepad)>,
mut controls: ResMut<Controls>,
) {
let Some((_e, gamepad)) = gamepads.iter().next() else {
if controls.gamepad_state.is_some() {
controls.gamepad_state = None;
}
return;
};
// info!("gamepad: {:?}", gamepad);
let rotate = gamepad
.get(GamepadButton::RightTrigger2)
.unwrap_or_default();
const EPSILON: f32 = 0.015;
let state = ControlState {
move_dir: gamepad.left_stick(),
look_dir: Vec2::new(
if rotate < 0.5 - EPSILON {
40. * (rotate - 0.5)
} else if rotate > 0.5 + EPSILON {
40. * (rotate - 0.5)
} else {
0.
},
0.,
),
jump: gamepad.pressed(GamepadButton::South),
};
if gamepad.just_pressed(GamepadButton::North) {
commands.trigger(TriggerState::Active);
}
if gamepad.just_released(GamepadButton::North) {
commands.trigger(TriggerState::Inactive);
}
if controls
.gamepad_state
.as_ref()
.map(|last_state| *last_state != state)
.unwrap_or(true)
{
// info!("gamepad state changed: {:?}", state);
controls.gamepad_state = Some(state);
}
}
fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Controls>) {
for ev in mouse.read() {
controls.keyboard_state.look_dir += ev.delta;
}
}
fn clear_mouse_delta(mut controls: ResMut<Controls>) {
controls.keyboard_state.look_dir = Vec2::ZERO;
}
fn keyboard_controls(keyboard: Res<ButtonInput<KeyCode>>, mut controls: ResMut<Controls>) {
let mut direction = Vec2::ZERO;
if keyboard.pressed(KeyCode::KeyW) {
direction = Vec2::Y;
}
if keyboard.pressed(KeyCode::KeyS) {
direction = -Vec2::Y;
}
if keyboard.pressed(KeyCode::KeyA) {
direction += -Vec2::X;
}
if keyboard.pressed(KeyCode::KeyD) {
direction += Vec2::X;
}
controls.keyboard_state.move_dir = direction;
controls.keyboard_state.jump = keyboard.pressed(KeyCode::Space);
}
fn mouse_click(mut events: EventReader<MouseButtonInput>, mut commands: Commands) {
for ev in events.read() {
match ev {
MouseButtonInput {
button: MouseButton::Left,
state: ButtonState::Pressed,
..
} => {
commands.trigger(TriggerState::Active);
}
MouseButtonInput {
button: MouseButton::Left,
state: ButtonState::Released,
..
} => {
commands.trigger(TriggerState::Inactive);
}
_ => {}
}
}
}

View File

@@ -3,6 +3,7 @@ mod alien;
mod billboards; mod billboards;
mod camera; mod camera;
mod cash; mod cash;
mod controls;
mod cutscene; mod cutscene;
mod gates; mod gates;
mod heads_ui; mod heads_ui;
@@ -88,6 +89,7 @@ fn main() {
app.add_plugins(keys::plugin); app.add_plugins(keys::plugin);
app.add_plugins(squish_animation::plugin); app.add_plugins(squish_animation::plugin);
app.add_plugins(cutscene::plugin); app.add_plugins(cutscene::plugin);
app.add_plugins(controls::plugin);
app.insert_resource(AmbientLight { app.insert_resource(AmbientLight {
color: Color::WHITE, color: Color::WHITE,

View File

@@ -1,26 +1,21 @@
use std::{f32::consts::PI, time::Duration};
use crate::{ use crate::{
DebugVisuals, DebugVisuals,
alien::{ALIEN_ASSET_PATH, Animations}, alien::{ALIEN_ASSET_PATH, Animations},
camera::GameCameraRig, camera::GameCameraRig,
cash::{Cash, CashCollectEvent}, cash::{Cash, CashCollectEvent},
controls::Controls,
heads_ui::HeadChanged, heads_ui::HeadChanged,
shooting::TriggerState,
tb_entities::SpawnPoint, tb_entities::SpawnPoint,
}; };
use avian3d::prelude::*; use avian3d::prelude::*;
use bevy::{ use bevy::{
input::{
ButtonState,
mouse::{MouseButtonInput, MouseMotion},
},
prelude::*, prelude::*,
window::{CursorGrabMode, PrimaryWindow}, window::{CursorGrabMode, PrimaryWindow},
}; };
use bevy_dolly::prelude::Rig; use bevy_dolly::prelude::Rig;
use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*}; use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*};
use bevy_tnua_avian3d::TnuaAvian3dSensorShape; use bevy_tnua_avian3d::TnuaAvian3dSensorShape;
use std::{f32::consts::PI, time::Duration};
#[derive(Component, Default)] #[derive(Component, Default)]
pub struct Player; pub struct Player;
@@ -63,10 +58,9 @@ pub fn plugin(app: &mut App) {
apply_controls.in_set(TnuaUserControlsSystemSet), apply_controls.in_set(TnuaUserControlsSystemSet),
); );
app.add_systems(Update, mouse_rotate.run_if(on_event::<MouseMotion>)); app.add_systems(Update, rotate_view);
app.add_systems(Update, mouse_click.run_if(on_event::<MouseButtonInput>));
app.add_observer(updaate_head); app.add_observer(update_head);
} }
fn spawn( fn spawn(
@@ -125,36 +119,16 @@ fn spawn(
player_spawned.spawned = true; player_spawned.spawned = true;
} }
fn mouse_rotate( fn rotate_view(
mut mouse: EventReader<MouseMotion>, controls: Res<Controls>,
// todo: Put the player head as a child of the rig to avoid this mess: // todo: Put the player head as a child of the rig to avoid this mess:
mut player: Query<&mut Transform, Or<(With<PlayerRig>, With<PlayerHead>)>>, mut player: Query<&mut Transform, Or<(With<PlayerRig>, With<PlayerHead>)>>,
) { ) {
for ev in mouse.read() {
for mut tr in &mut player { for mut tr in &mut player {
tr.rotate_y(ev.delta.x * -0.001); tr.rotate_y(controls.keyboard_state.look_dir.x * -0.001);
}
}
}
fn mouse_click(mut events: EventReader<MouseButtonInput>, mut commands: Commands) { if let Some(gamepad) = controls.gamepad_state {
for ev in events.read() { tr.rotate_y(gamepad.look_dir.x * -0.001);
match ev {
MouseButtonInput {
button: MouseButton::Left,
state: ButtonState::Pressed,
..
} => {
commands.trigger(TriggerState::Active);
}
MouseButtonInput {
button: MouseButton::Left,
state: ButtonState::Released,
..
} => {
commands.trigger(TriggerState::Inactive);
}
_ => {}
} }
} }
} }
@@ -181,10 +155,10 @@ fn initial_grab_cursor(mut primary_window: Query<&mut Window, With<PrimaryWindow
} }
fn apply_controls( fn apply_controls(
keyboard: Res<ButtonInput<KeyCode>>, controls: Res<Controls>,
mut query: Query<&mut TnuaController>, mut query: Query<&mut TnuaController>,
player: Query<&Transform, With<PlayerRig>>, player: Query<&Transform, With<PlayerRig>>,
mut controls: ResMut<PlayerMovement>, mut movement: ResMut<PlayerMovement>,
) { ) {
let Ok(mut controller) = query.get_single_mut() else { let Ok(mut controller) = query.get_single_mut() else {
return; return;
@@ -194,23 +168,15 @@ fn apply_controls(
return; return;
}; };
let mut direction = Vec3::ZERO; let controls = controls
.gamepad_state
.unwrap_or_else(|| controls.keyboard_state);
if keyboard.pressed(KeyCode::KeyW) { let mut direction = player.forward().as_vec3() * -controls.move_dir.y;
direction = player.forward().as_vec3() * -1.; direction += player.right().as_vec3() * -controls.move_dir.x;
}
if keyboard.pressed(KeyCode::KeyS) {
direction = player.forward().as_vec3();
}
if keyboard.pressed(KeyCode::KeyA) {
direction += player.right().as_vec3();
}
if keyboard.pressed(KeyCode::KeyD) {
direction += player.left().as_vec3();
}
if controls.any_direction != (direction != Vec3::ZERO) { if movement.any_direction != (direction != Vec3::ZERO) {
controls.any_direction = direction != Vec3::ZERO; movement.any_direction = direction != Vec3::ZERO;
} }
controller.basis(TnuaBuiltinWalk { controller.basis(TnuaBuiltinWalk {
@@ -228,7 +194,7 @@ fn apply_controls(
// Feed the jump action every frame as long as the player holds the jump button. If the player // Feed the jump action every frame as long as the player holds the jump button. If the player
// stops holding the jump button, simply stop feeding the action. // stops holding the jump button, simply stop feeding the action.
if keyboard.pressed(KeyCode::Space) { if controls.jump {
controller.action(TnuaBuiltinJump { controller.action(TnuaBuiltinJump {
// The height is the only mandatory field of the jump button. // The height is the only mandatory field of the jump button.
height: 4.0, height: 4.0,
@@ -317,7 +283,7 @@ fn toggle_animation(
} }
} }
fn updaate_head( fn update_head(
trigger: Trigger<HeadChanged>, trigger: Trigger<HeadChanged>,
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,