first support for gamepad controls
This commit is contained in:
135
src/controls.rs
Normal file
135
src/controls.rs
Normal 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);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(controls.keyboard_state.look_dir.x * -0.001);
|
||||||
tr.rotate_y(ev.delta.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>,
|
||||||
|
|||||||
Reference in New Issue
Block a user