Fix input drops/repetitions (#69)
This commit is contained in:
@@ -109,7 +109,7 @@ fn attempt_connection(mut commands: Commands) -> Result {
|
|||||||
client::NetcodeClient::new(
|
client::NetcodeClient::new(
|
||||||
auth,
|
auth,
|
||||||
NetcodeConfig {
|
NetcodeConfig {
|
||||||
client_timeout_secs: 1,
|
client_timeout_secs: 3,
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
)?,
|
)?,
|
||||||
|
|||||||
@@ -117,7 +117,10 @@ fn start_server(mut commands: Commands) -> Result {
|
|||||||
Name::from("Server"),
|
Name::from("Server"),
|
||||||
LocalAddr(server_addr),
|
LocalAddr(server_addr),
|
||||||
ServerUdpIo::default(),
|
ServerUdpIo::default(),
|
||||||
NetcodeServer::new(NetcodeConfig::default()),
|
NetcodeServer::new(NetcodeConfig {
|
||||||
|
client_timeout_secs: 3,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
Link::new(Some(conditioner)),
|
Link::new(Some(conditioner)),
|
||||||
));
|
));
|
||||||
commands.trigger(server::Start);
|
commands.trigger(server::Start);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use super::TriggerMissile;
|
use super::TriggerMissile;
|
||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
GameState,
|
||||||
abilities::BuildExplosionSprite,
|
abilities::{ExplodingProjectile, ExplodingProjectileSet},
|
||||||
heads_database::HeadsDatabase,
|
heads_database::HeadsDatabase,
|
||||||
physics_layers::GameLayer,
|
physics_layers::GameLayer,
|
||||||
protocol::{GltfSceneRoot, PlaySound},
|
protocol::{GltfSceneRoot, PlaySound},
|
||||||
utils::{commands::CommandExt, explosions::Explosion, global_observer, trail::Trail},
|
utils::{global_observer, trail::Trail},
|
||||||
};
|
};
|
||||||
use avian3d::prelude::*;
|
use avian3d::prelude::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@@ -23,7 +23,10 @@ struct MissileProjectile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.add_systems(Update, shot_collision.run_if(in_state(GameState::Playing)));
|
app.add_systems(
|
||||||
|
FixedUpdate,
|
||||||
|
shot_collision.in_set(ExplodingProjectileSet::Mark),
|
||||||
|
);
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
FixedUpdate,
|
FixedUpdate,
|
||||||
(update, timeout).run_if(in_state(GameState::Playing)),
|
(update, timeout).run_if(in_state(GameState::Playing)),
|
||||||
@@ -141,20 +144,14 @@ fn shot_collision(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
commands.trigger(PlaySound::MissileExplosion);
|
commands.entity(shot_entity).insert(ExplodingProjectile {
|
||||||
|
sound: PlaySound::MissileExplosion,
|
||||||
commands.entity(shot_entity).despawn();
|
|
||||||
|
|
||||||
commands.trigger(Explosion {
|
|
||||||
damage,
|
damage,
|
||||||
position: shot_pos,
|
position: shot_pos,
|
||||||
radius: 6.,
|
radius: 6.0,
|
||||||
});
|
animation: true,
|
||||||
|
anim_pixels_per_meter: 16.0,
|
||||||
commands.trigger_server(BuildExplosionSprite {
|
anim_time: 0.01,
|
||||||
pos: shot_pos,
|
|
||||||
pixels_per_meter: 16.,
|
|
||||||
time: 0.01,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,27 @@ pub mod missile;
|
|||||||
pub mod thrown;
|
pub mod thrown;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
GameState, global_observer,
|
||||||
aim::AimTarget,
|
|
||||||
character::CharacterHierarchy,
|
|
||||||
global_observer,
|
|
||||||
heads::ActiveHeads,
|
|
||||||
heads_database::HeadsDatabase,
|
|
||||||
loading_assets::GameAssets,
|
loading_assets::GameAssets,
|
||||||
physics_layers::GameLayer,
|
physics_layers::GameLayer,
|
||||||
player::{Player, PlayerBodyMesh},
|
|
||||||
protocol::PlaySound,
|
protocol::PlaySound,
|
||||||
utils::{billboards::Billboard, sprite_3d_animation::AnimationTimer},
|
utils::{billboards::Billboard, sprite_3d_animation::AnimationTimer},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
use crate::{control::ControlState, head::ActiveHead};
|
use crate::{
|
||||||
|
aim::AimTarget,
|
||||||
|
character::CharacterHierarchy,
|
||||||
|
control::ControlState,
|
||||||
|
head::ActiveHead,
|
||||||
|
heads::ActiveHeads,
|
||||||
|
heads_database::HeadsDatabase,
|
||||||
|
player::{Player, PlayerBodyMesh},
|
||||||
|
utils::explosions::Explosion,
|
||||||
|
};
|
||||||
use bevy::{pbr::NotShadowCaster, prelude::*};
|
use bevy::{pbr::NotShadowCaster, prelude::*};
|
||||||
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
|
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
|
||||||
pub use healing::Healing;
|
pub use healing::Healing;
|
||||||
|
#[cfg(feature = "server")]
|
||||||
use healing::HealingStateChanged;
|
use healing::HealingStateChanged;
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
use lightyear::prelude::input::native::ActionState;
|
use lightyear::prelude::input::native::ActionState;
|
||||||
@@ -86,6 +90,7 @@ pub struct TriggerCurver(pub TriggerData);
|
|||||||
|
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
pub struct TriggerStateRes {
|
pub struct TriggerStateRes {
|
||||||
|
#[cfg(feature = "server")]
|
||||||
next_trigger_timestamp: f32,
|
next_trigger_timestamp: f32,
|
||||||
active: bool,
|
active: bool,
|
||||||
}
|
}
|
||||||
@@ -96,6 +101,25 @@ impl TriggerStateRes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
#[component(storage = "SparseSet")]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct ExplodingProjectile {
|
||||||
|
sound: PlaySound,
|
||||||
|
damage: u32,
|
||||||
|
position: Vec3,
|
||||||
|
radius: f32,
|
||||||
|
animation: bool,
|
||||||
|
anim_pixels_per_meter: f32,
|
||||||
|
anim_time: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum ExplodingProjectileSet {
|
||||||
|
Mark,
|
||||||
|
Explode,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.init_resource::<TriggerStateRes>();
|
app.init_resource::<TriggerStateRes>();
|
||||||
|
|
||||||
@@ -106,20 +130,61 @@ pub fn plugin(app: &mut App) {
|
|||||||
app.add_plugins(healing::plugin);
|
app.add_plugins(healing::plugin);
|
||||||
app.add_plugins(curver::plugin);
|
app.add_plugins(curver::plugin);
|
||||||
|
|
||||||
|
app.configure_sets(
|
||||||
|
FixedUpdate,
|
||||||
|
ExplodingProjectileSet::Explode
|
||||||
|
.after(ExplodingProjectileSet::Mark)
|
||||||
|
.run_if(in_state(GameState::Playing)),
|
||||||
|
);
|
||||||
|
|
||||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||||
|
#[cfg(feature = "server")]
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
FixedUpdate,
|
||||||
(update, update_heal_ability).run_if(in_state(GameState::Playing)),
|
(on_trigger_state, update, update_heal_ability)
|
||||||
|
.chain()
|
||||||
|
.run_if(in_state(GameState::Playing)),
|
||||||
);
|
);
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
FixedUpdate,
|
FixedUpdate,
|
||||||
on_trigger_state.run_if(in_state(GameState::Playing)),
|
explode_projectiles.in_set(ExplodingProjectileSet::Explode),
|
||||||
);
|
);
|
||||||
|
|
||||||
global_observer!(app, build_explosion_sprite);
|
global_observer!(app, build_explosion_sprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
fn explode_projectiles(mut commands: Commands, query: Query<(Entity, &ExplodingProjectile)>) {
|
||||||
|
for (shot_entity, projectile) in query.iter() {
|
||||||
|
if let Ok(mut entity) = commands.get_entity(shot_entity) {
|
||||||
|
entity.try_despawn();
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.trigger(projectile.sound.clone());
|
||||||
|
|
||||||
|
commands.trigger(Explosion {
|
||||||
|
damage: projectile.damage,
|
||||||
|
position: projectile.position,
|
||||||
|
//TODO: should be around 1 grid in distance
|
||||||
|
radius: projectile.radius,
|
||||||
|
});
|
||||||
|
|
||||||
|
//TODO: support different impact animations
|
||||||
|
if projectile.animation {
|
||||||
|
use crate::utils::commands::CommandExt;
|
||||||
|
|
||||||
|
commands.trigger_server(BuildExplosionSprite {
|
||||||
|
pos: projectile.position,
|
||||||
|
pixels_per_meter: projectile.anim_pixels_per_meter,
|
||||||
|
time: projectile.anim_time,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
fn on_trigger_state(
|
fn on_trigger_state(
|
||||||
mut res: ResMut<TriggerStateRes>,
|
mut res: ResMut<TriggerStateRes>,
|
||||||
@@ -136,6 +201,7 @@ fn on_trigger_state(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
fn update(
|
fn update(
|
||||||
mut res: ResMut<TriggerStateRes>,
|
mut res: ResMut<TriggerStateRes>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
@@ -201,6 +267,7 @@ fn update(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
fn update_heal_ability(
|
fn update_heal_ability(
|
||||||
res: Res<TriggerStateRes>,
|
res: Res<TriggerStateRes>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
use super::TriggerThrow;
|
use super::TriggerThrow;
|
||||||
use crate::{
|
use crate::{
|
||||||
GameState,
|
abilities::{ExplodingProjectile, ExplodingProjectileSet},
|
||||||
abilities::BuildExplosionSprite,
|
|
||||||
heads_database::HeadsDatabase,
|
heads_database::HeadsDatabase,
|
||||||
physics_layers::GameLayer,
|
physics_layers::GameLayer,
|
||||||
protocol::{GltfSceneRoot, PlaySound},
|
protocol::{GltfSceneRoot, PlaySound},
|
||||||
utils::{
|
utils::{auto_rotate::AutoRotation, global_observer},
|
||||||
auto_rotate::AutoRotation, commands::CommandExt, explosions::Explosion, global_observer,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use avian3d::prelude::*;
|
use avian3d::prelude::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@@ -24,7 +21,10 @@ pub struct ThrownProjectile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.add_systems(Update, shot_collision.run_if(in_state(GameState::Playing)));
|
app.add_systems(
|
||||||
|
FixedUpdate,
|
||||||
|
shot_collision.in_set(ExplodingProjectileSet::Mark),
|
||||||
|
);
|
||||||
|
|
||||||
global_observer!(app, on_trigger_thrown);
|
global_observer!(app, on_trigger_thrown);
|
||||||
}
|
}
|
||||||
@@ -116,28 +116,14 @@ fn shot_collision(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(mut entity) = commands.get_entity(shot_entity) {
|
commands.entity(shot_entity).insert(ExplodingProjectile {
|
||||||
entity.try_despawn();
|
sound: PlaySound::ThrowHit,
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.trigger(PlaySound::ThrowHit);
|
|
||||||
|
|
||||||
commands.trigger(Explosion {
|
|
||||||
damage,
|
damage,
|
||||||
position: shot_pos,
|
position: shot_pos,
|
||||||
//TODO: should be around 1 grid in distance
|
radius: 5.0,
|
||||||
radius: 5.,
|
animation,
|
||||||
|
anim_pixels_per_meter: 32.0,
|
||||||
|
anim_time: 0.02,
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: support different impact animations
|
|
||||||
if animation {
|
|
||||||
commands.trigger_server(BuildExplosionSprite {
|
|
||||||
pos: shot_pos,
|
|
||||||
pixels_per_meter: 32.,
|
|
||||||
time: 0.02,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,56 @@
|
|||||||
use super::{ControlState, Controls};
|
use super::{ControlState, Controls};
|
||||||
use crate::{
|
#[cfg(feature = "client")]
|
||||||
GameState,
|
use crate::control::ControllerSet;
|
||||||
control::{CharacterInputEnabled, ControllerSet},
|
use crate::{GameState, control::CharacterInputEnabled};
|
||||||
};
|
#[cfg(feature = "client")]
|
||||||
use bevy::{
|
use bevy::input::{
|
||||||
input::{
|
ButtonState,
|
||||||
ButtonState,
|
gamepad::{GamepadConnection, GamepadEvent},
|
||||||
gamepad::{GamepadConnection, GamepadEvent},
|
mouse::{MouseButtonInput, MouseMotion},
|
||||||
mouse::{MouseButtonInput, MouseMotion},
|
|
||||||
},
|
|
||||||
prelude::*,
|
|
||||||
};
|
};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
use lightyear::input::client::InputSet;
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
use lightyear::prelude::input::native::{ActionState, InputMarker};
|
use lightyear::prelude::input::native::{ActionState, InputMarker};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.init_resource::<Controls>();
|
app.init_resource::<Controls>();
|
||||||
|
app.init_resource::<InputStateCache<KeyCode>>();
|
||||||
|
|
||||||
|
app.register_required_components::<Gamepad, InputStateCache<GamepadButton>>();
|
||||||
|
|
||||||
app.register_type::<ControllerSettings>();
|
app.register_type::<ControllerSettings>();
|
||||||
|
|
||||||
app.add_systems(
|
app.add_systems(PreUpdate, (cache_keyboard_state, cache_gamepad_state));
|
||||||
FixedUpdate,
|
|
||||||
(
|
|
||||||
gamepad_controls,
|
|
||||||
keyboard_controls,
|
|
||||||
mouse_rotate,
|
|
||||||
mouse_click,
|
|
||||||
gamepad_connections.run_if(on_event::<GamepadEvent>),
|
|
||||||
combine_controls,
|
|
||||||
)
|
|
||||||
.chain()
|
|
||||||
.in_set(ControllerSet::CollectInputs)
|
|
||||||
.run_if(
|
|
||||||
in_state(GameState::Playing)
|
|
||||||
.and(resource_exists_and_equals(CharacterInputEnabled::On)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
{
|
{
|
||||||
use lightyear::prelude::client::input::InputSet;
|
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
|
FixedPreUpdate,
|
||||||
|
(
|
||||||
|
gamepad_controls,
|
||||||
|
keyboard_controls,
|
||||||
|
mouse_rotate,
|
||||||
|
mouse_click,
|
||||||
|
gamepad_connections.run_if(on_event::<GamepadEvent>),
|
||||||
|
combine_controls,
|
||||||
|
clear_keyboard_state,
|
||||||
|
clear_gamepad_state,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
|
.in_set(ControllerSet::CollectInputs)
|
||||||
|
.before(InputSet::WriteClientInputs)
|
||||||
|
.run_if(
|
||||||
|
in_state(GameState::Playing)
|
||||||
|
.and(resource_exists_and_equals(CharacterInputEnabled::On)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
FixedPreUpdate,
|
FixedPreUpdate,
|
||||||
buffer_inputs.in_set(InputSet::WriteClientInputs),
|
buffer_inputs.in_set(InputSet::WriteClientInputs),
|
||||||
);
|
);
|
||||||
@@ -82,6 +91,105 @@ fn reset_control_state_on_disable(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Caches information that depends on the Update schedule so that it can be read safely from the Fixed schedule
|
||||||
|
/// without losing/duplicating info
|
||||||
|
#[derive(Component, Resource)]
|
||||||
|
struct InputStateCache<Button> {
|
||||||
|
map: HashMap<Button, InputState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Button> Default for InputStateCache<Button> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { map: default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
impl<Button: Hash + Eq> InputStateCache<Button> {
|
||||||
|
fn clear(&mut self) {
|
||||||
|
for state in self.map.values_mut() {
|
||||||
|
*state = InputState::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pressed(&self, button: Button) -> bool {
|
||||||
|
self.map
|
||||||
|
.get(&button)
|
||||||
|
.map(|state| state.pressed)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn just_pressed(&self, button: Button) -> bool {
|
||||||
|
self.map
|
||||||
|
.get(&button)
|
||||||
|
.map(|state| state.just_pressed)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct InputState {
|
||||||
|
pressed: bool,
|
||||||
|
just_pressed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache_keyboard_state(
|
||||||
|
mut cache: ResMut<InputStateCache<KeyCode>>,
|
||||||
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
|
) {
|
||||||
|
let mut cache_key = |key| {
|
||||||
|
cache.map.entry(key).or_default().pressed |= keyboard.pressed(key);
|
||||||
|
cache.map.entry(key).or_default().just_pressed |= keyboard.just_pressed(key);
|
||||||
|
};
|
||||||
|
cache_key(KeyCode::Space);
|
||||||
|
cache_key(KeyCode::Tab);
|
||||||
|
cache_key(KeyCode::KeyB);
|
||||||
|
cache_key(KeyCode::Enter);
|
||||||
|
cache_key(KeyCode::Comma);
|
||||||
|
cache_key(KeyCode::Period);
|
||||||
|
cache_key(KeyCode::KeyQ);
|
||||||
|
cache_key(KeyCode::KeyE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
fn clear_keyboard_state(mut cache: ResMut<InputStateCache<KeyCode>>) {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache_gamepad_state(mut gamepads: Query<(&Gamepad, &mut InputStateCache<GamepadButton>)>) {
|
||||||
|
for (gamepad, mut cache) in gamepads.iter_mut() {
|
||||||
|
let mut cache_button = |button| {
|
||||||
|
cache.map.entry(button).or_default().pressed |= gamepad.pressed(button);
|
||||||
|
cache.map.entry(button).or_default().just_pressed |= gamepad.just_pressed(button);
|
||||||
|
};
|
||||||
|
|
||||||
|
cache_button(GamepadButton::North);
|
||||||
|
cache_button(GamepadButton::East);
|
||||||
|
cache_button(GamepadButton::South);
|
||||||
|
cache_button(GamepadButton::West);
|
||||||
|
cache_button(GamepadButton::DPadUp);
|
||||||
|
cache_button(GamepadButton::DPadRight);
|
||||||
|
cache_button(GamepadButton::DPadDown);
|
||||||
|
cache_button(GamepadButton::DPadLeft);
|
||||||
|
cache_button(GamepadButton::LeftTrigger);
|
||||||
|
cache_button(GamepadButton::LeftTrigger2);
|
||||||
|
cache_button(GamepadButton::RightTrigger);
|
||||||
|
cache_button(GamepadButton::RightTrigger2);
|
||||||
|
cache_button(GamepadButton::Select);
|
||||||
|
cache_button(GamepadButton::Start);
|
||||||
|
cache_button(GamepadButton::LeftThumb);
|
||||||
|
cache_button(GamepadButton::RightThumb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
fn clear_gamepad_state(mut caches: Query<&mut InputStateCache<GamepadButton>>) {
|
||||||
|
for mut cache in caches.iter_mut() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
/// Take keyboard and gamepad state and combine them into unified input state
|
/// Take keyboard and gamepad state and combine them into unified input state
|
||||||
fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<ControlState>) {
|
fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<ControlState>) {
|
||||||
let keyboard = controls.keyboard_state;
|
let keyboard = controls.keyboard_state;
|
||||||
@@ -102,6 +210,7 @@ fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<Contr
|
|||||||
combined_controls.cash_heal = gamepad.cash_heal | keyboard.cash_heal;
|
combined_controls.cash_heal = gamepad.cash_heal | keyboard.cash_heal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
/// Applies a square deadzone to a Vec2
|
/// Applies a square deadzone to a Vec2
|
||||||
fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
|
fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
|
||||||
Vec2::new(
|
Vec2::new(
|
||||||
@@ -110,9 +219,13 @@ fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
/// Collect gamepad inputs
|
/// Collect gamepad inputs
|
||||||
fn gamepad_controls(gamepads: Query<(Entity, &Gamepad)>, mut controls: ResMut<Controls>) {
|
fn gamepad_controls(
|
||||||
let Some((_e, gamepad)) = gamepads.iter().next() else {
|
gamepads: Query<(Entity, &Gamepad, &InputStateCache<GamepadButton>)>,
|
||||||
|
mut controls: ResMut<Controls>,
|
||||||
|
) {
|
||||||
|
let Some((_e, gamepad, cache)) = gamepads.iter().next() else {
|
||||||
if controls.gamepad_state.is_some() {
|
if controls.gamepad_state.is_some() {
|
||||||
controls.gamepad_state = None;
|
controls.gamepad_state = None;
|
||||||
}
|
}
|
||||||
@@ -146,17 +259,17 @@ fn gamepad_controls(gamepads: Query<(Entity, &Gamepad)>, mut controls: ResMut<Co
|
|||||||
let state = ControlState {
|
let state = ControlState {
|
||||||
move_dir: deadzone_square(gamepad.left_stick(), deadzone_left_stick),
|
move_dir: deadzone_square(gamepad.left_stick(), deadzone_left_stick),
|
||||||
look_dir,
|
look_dir,
|
||||||
jump: gamepad.pressed(GamepadButton::South),
|
jump: cache.pressed(GamepadButton::South),
|
||||||
view_mode: gamepad.pressed(GamepadButton::LeftTrigger2),
|
view_mode: cache.pressed(GamepadButton::LeftTrigger2),
|
||||||
trigger: gamepad.pressed(GamepadButton::RightTrigger2),
|
trigger: cache.pressed(GamepadButton::RightTrigger2),
|
||||||
just_triggered: gamepad.just_pressed(GamepadButton::RightTrigger2),
|
just_triggered: cache.just_pressed(GamepadButton::RightTrigger2),
|
||||||
select_left: gamepad.just_pressed(GamepadButton::LeftTrigger),
|
select_left: cache.just_pressed(GamepadButton::LeftTrigger),
|
||||||
select_right: gamepad.just_pressed(GamepadButton::RightTrigger),
|
select_right: cache.just_pressed(GamepadButton::RightTrigger),
|
||||||
backpack_left: gamepad.just_pressed(GamepadButton::DPadLeft),
|
backpack_left: cache.just_pressed(GamepadButton::DPadLeft),
|
||||||
backpack_right: gamepad.just_pressed(GamepadButton::DPadRight),
|
backpack_right: cache.just_pressed(GamepadButton::DPadRight),
|
||||||
backpack_swap: gamepad.just_pressed(GamepadButton::DPadDown),
|
backpack_swap: cache.just_pressed(GamepadButton::DPadDown),
|
||||||
backpack_toggle: gamepad.just_pressed(GamepadButton::DPadUp),
|
backpack_toggle: cache.just_pressed(GamepadButton::DPadUp),
|
||||||
cash_heal: gamepad.just_pressed(GamepadButton::East),
|
cash_heal: cache.just_pressed(GamepadButton::East),
|
||||||
};
|
};
|
||||||
|
|
||||||
if controls
|
if controls
|
||||||
@@ -169,6 +282,7 @@ fn gamepad_controls(gamepads: Query<(Entity, &Gamepad)>, mut controls: ResMut<Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
/// Collect mouse movement input
|
/// Collect mouse movement input
|
||||||
fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Controls>) {
|
fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Controls>) {
|
||||||
controls.keyboard_state.look_dir = Vec2::ZERO;
|
controls.keyboard_state.look_dir = Vec2::ZERO;
|
||||||
@@ -178,8 +292,13 @@ fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Contro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
/// Collect keyboard input
|
/// Collect keyboard input
|
||||||
fn keyboard_controls(keyboard: Res<ButtonInput<KeyCode>>, mut controls: ResMut<Controls>) {
|
fn keyboard_controls(
|
||||||
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
|
cache: Res<InputStateCache<KeyCode>>,
|
||||||
|
mut controls: ResMut<Controls>,
|
||||||
|
) {
|
||||||
let up_binds = [KeyCode::KeyW, KeyCode::ArrowUp];
|
let up_binds = [KeyCode::KeyW, KeyCode::ArrowUp];
|
||||||
let down_binds = [KeyCode::KeyS, KeyCode::ArrowDown];
|
let down_binds = [KeyCode::KeyS, KeyCode::ArrowDown];
|
||||||
let left_binds = [KeyCode::KeyA, KeyCode::ArrowLeft];
|
let left_binds = [KeyCode::KeyA, KeyCode::ArrowLeft];
|
||||||
@@ -195,17 +314,18 @@ fn keyboard_controls(keyboard: Res<ButtonInput<KeyCode>>, mut controls: ResMut<C
|
|||||||
let direction = Vec2::new(horizontal as f32, vertical as f32).clamp_length_max(1.0);
|
let direction = Vec2::new(horizontal as f32, vertical as f32).clamp_length_max(1.0);
|
||||||
|
|
||||||
controls.keyboard_state.move_dir = direction;
|
controls.keyboard_state.move_dir = direction;
|
||||||
controls.keyboard_state.jump = keyboard.pressed(KeyCode::Space);
|
controls.keyboard_state.jump = cache.pressed(KeyCode::Space);
|
||||||
controls.keyboard_state.view_mode = keyboard.pressed(KeyCode::Tab);
|
controls.keyboard_state.view_mode = cache.pressed(KeyCode::Tab);
|
||||||
controls.keyboard_state.backpack_toggle = keyboard.just_pressed(KeyCode::KeyB);
|
controls.keyboard_state.backpack_toggle = cache.just_pressed(KeyCode::KeyB);
|
||||||
controls.keyboard_state.backpack_swap = keyboard.just_pressed(KeyCode::Enter);
|
controls.keyboard_state.backpack_swap = cache.just_pressed(KeyCode::Enter);
|
||||||
controls.keyboard_state.backpack_left = keyboard.just_pressed(KeyCode::Comma);
|
controls.keyboard_state.backpack_left = cache.just_pressed(KeyCode::Comma);
|
||||||
controls.keyboard_state.backpack_right = keyboard.just_pressed(KeyCode::Period);
|
controls.keyboard_state.backpack_right = cache.just_pressed(KeyCode::Period);
|
||||||
controls.keyboard_state.select_left = keyboard.just_pressed(KeyCode::KeyQ);
|
controls.keyboard_state.select_left = cache.just_pressed(KeyCode::KeyQ);
|
||||||
controls.keyboard_state.select_right = keyboard.just_pressed(KeyCode::KeyE);
|
controls.keyboard_state.select_right = cache.just_pressed(KeyCode::KeyE);
|
||||||
controls.keyboard_state.cash_heal = keyboard.just_pressed(KeyCode::Enter);
|
controls.keyboard_state.cash_heal = cache.just_pressed(KeyCode::Enter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
/// Collect mouse button input when pressed
|
/// Collect mouse button input when pressed
|
||||||
fn mouse_click(mut events: EventReader<MouseButtonInput>, mut controls: ResMut<Controls>) {
|
fn mouse_click(mut events: EventReader<MouseButtonInput>, mut controls: ResMut<Controls>) {
|
||||||
controls.keyboard_state.just_triggered = false;
|
controls.keyboard_state.just_triggered = false;
|
||||||
@@ -226,13 +346,13 @@ fn mouse_click(mut events: EventReader<MouseButtonInput>, mut controls: ResMut<C
|
|||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
controls.keyboard_state.trigger = false;
|
controls.keyboard_state.trigger = false;
|
||||||
controls.keyboard_state.just_triggered = false;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
/// Receive gamepad connections and disconnections
|
/// Receive gamepad connections and disconnections
|
||||||
fn gamepad_connections(mut evr_gamepad: EventReader<GamepadEvent>) {
|
fn gamepad_connections(mut evr_gamepad: EventReader<GamepadEvent>) {
|
||||||
for ev in evr_gamepad.read() {
|
for ev in evr_gamepad.read() {
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ impl MapEntities for ControlState {
|
|||||||
|
|
||||||
#[derive(Resource, Debug, Default)]
|
#[derive(Resource, Debug, Default)]
|
||||||
struct Controls {
|
struct Controls {
|
||||||
|
#[cfg(feature = "client")]
|
||||||
keyboard_state: ControlState,
|
keyboard_state: ControlState,
|
||||||
|
#[cfg(feature = "client")]
|
||||||
gamepad_state: Option<ControlState>,
|
gamepad_state: Option<ControlState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,9 +76,12 @@ pub fn plugin(app: &mut App) {
|
|||||||
app.add_event::<ControllerSwitchEvent>();
|
app.add_event::<ControllerSwitchEvent>();
|
||||||
|
|
||||||
app.configure_sets(
|
app.configure_sets(
|
||||||
|
FixedPreUpdate,
|
||||||
|
ControllerSet::CollectInputs.run_if(in_state(GameState::Playing)),
|
||||||
|
)
|
||||||
|
.configure_sets(
|
||||||
FixedUpdate,
|
FixedUpdate,
|
||||||
(
|
(
|
||||||
ControllerSet::CollectInputs,
|
|
||||||
ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController(
|
ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController(
|
||||||
ControllerSet::ApplyControlsFly,
|
ControllerSet::ApplyControlsFly,
|
||||||
))),
|
))),
|
||||||
|
|||||||
@@ -107,8 +107,6 @@ fn on_spawn_check(
|
|||||||
.insert_if(Ai, || !spawn.disable_ai)
|
.insert_if(Ai, || !spawn.disable_ai)
|
||||||
.with_child((Name::from("body-rig"), AnimatedCharacter::new(id)))
|
.with_child((Name::from("body-rig"), AnimatedCharacter::new(id)))
|
||||||
.observe(on_kill);
|
.observe(on_kill);
|
||||||
#[cfg(feature = "server")]
|
|
||||||
ecommands.insert(Replicate::to_clients(NetworkTarget::All));
|
|
||||||
|
|
||||||
commands.trigger(SpawnCharacter(transform.translation));
|
commands.trigger(SpawnCharacter(transform.translation));
|
||||||
commands.trigger(PlaySound::Beaming);
|
commands.trigger(PlaySound::Beaming);
|
||||||
|
|||||||
Reference in New Issue
Block a user