diff --git a/src/controls.rs b/src/controls.rs new file mode 100644 index 0000000..5b49636 --- /dev/null +++ b/src/controls.rs @@ -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, +} + +pub fn plugin(app: &mut App) { + app.init_resource::(); + + 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::)); +} + +fn gamepad_controls( + mut commands: Commands, + gamepads: Query<(Entity, &Gamepad)>, + mut controls: ResMut, +) { + 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, mut controls: ResMut) { + for ev in mouse.read() { + controls.keyboard_state.look_dir += ev.delta; + } +} + +fn clear_mouse_delta(mut controls: ResMut) { + controls.keyboard_state.look_dir = Vec2::ZERO; +} + +fn keyboard_controls(keyboard: Res>, mut controls: ResMut) { + 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, 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); + } + _ => {} + } + } +} diff --git a/src/main.rs b/src/main.rs index 9afdca5..4f5c231 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod alien; mod billboards; mod camera; mod cash; +mod controls; mod cutscene; mod gates; mod heads_ui; @@ -88,6 +89,7 @@ fn main() { app.add_plugins(keys::plugin); app.add_plugins(squish_animation::plugin); app.add_plugins(cutscene::plugin); + app.add_plugins(controls::plugin); app.insert_resource(AmbientLight { color: Color::WHITE, diff --git a/src/player.rs b/src/player.rs index f707b03..7955c23 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,26 +1,21 @@ -use std::{f32::consts::PI, time::Duration}; - use crate::{ DebugVisuals, alien::{ALIEN_ASSET_PATH, Animations}, camera::GameCameraRig, cash::{Cash, CashCollectEvent}, + controls::Controls, heads_ui::HeadChanged, - shooting::TriggerState, tb_entities::SpawnPoint, }; use avian3d::prelude::*; use bevy::{ - input::{ - ButtonState, - mouse::{MouseButtonInput, MouseMotion}, - }, prelude::*, window::{CursorGrabMode, PrimaryWindow}, }; use bevy_dolly::prelude::Rig; use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*}; use bevy_tnua_avian3d::TnuaAvian3dSensorShape; +use std::{f32::consts::PI, time::Duration}; #[derive(Component, Default)] pub struct Player; @@ -63,10 +58,9 @@ pub fn plugin(app: &mut App) { apply_controls.in_set(TnuaUserControlsSystemSet), ); - app.add_systems(Update, mouse_rotate.run_if(on_event::)); - app.add_systems(Update, mouse_click.run_if(on_event::)); + app.add_systems(Update, rotate_view); - app.add_observer(updaate_head); + app.add_observer(update_head); } fn spawn( @@ -125,36 +119,16 @@ fn spawn( player_spawned.spawned = true; } -fn mouse_rotate( - mut mouse: EventReader, +fn rotate_view( + controls: Res, // todo: Put the player head as a child of the rig to avoid this mess: mut player: Query<&mut Transform, Or<(With, With)>>, ) { - for ev in mouse.read() { - for mut tr in &mut player { - tr.rotate_y(ev.delta.x * -0.001); - } - } -} + for mut tr in &mut player { + tr.rotate_y(controls.keyboard_state.look_dir.x * -0.001); -fn mouse_click(mut events: EventReader, 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); - } - _ => {} + if let Some(gamepad) = controls.gamepad_state { + tr.rotate_y(gamepad.look_dir.x * -0.001); } } } @@ -181,10 +155,10 @@ fn initial_grab_cursor(mut primary_window: Query<&mut Window, With>, + controls: Res, mut query: Query<&mut TnuaController>, player: Query<&Transform, With>, - mut controls: ResMut, + mut movement: ResMut, ) { let Ok(mut controller) = query.get_single_mut() else { return; @@ -194,23 +168,15 @@ fn apply_controls( return; }; - let mut direction = Vec3::ZERO; + let controls = controls + .gamepad_state + .unwrap_or_else(|| controls.keyboard_state); - if keyboard.pressed(KeyCode::KeyW) { - direction = player.forward().as_vec3() * -1.; - } - 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(); - } + let mut direction = player.forward().as_vec3() * -controls.move_dir.y; + direction += player.right().as_vec3() * -controls.move_dir.x; - if controls.any_direction != (direction != Vec3::ZERO) { - controls.any_direction = direction != Vec3::ZERO; + if movement.any_direction != (direction != Vec3::ZERO) { + movement.any_direction = direction != Vec3::ZERO; } 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 // stops holding the jump button, simply stop feeding the action. - if keyboard.pressed(KeyCode::Space) { + if controls.jump { controller.action(TnuaBuiltinJump { // The height is the only mandatory field of the jump button. height: 4.0, @@ -317,7 +283,7 @@ fn toggle_animation( } } -fn updaate_head( +fn update_head( trigger: Trigger, mut commands: Commands, asset_server: Res,