Fix input drops/repetitions (#69)
This commit is contained in:
@@ -109,7 +109,7 @@ fn attempt_connection(mut commands: Commands) -> Result {
|
||||
client::NetcodeClient::new(
|
||||
auth,
|
||||
NetcodeConfig {
|
||||
client_timeout_secs: 1,
|
||||
client_timeout_secs: 3,
|
||||
..default()
|
||||
},
|
||||
)?,
|
||||
|
||||
@@ -117,7 +117,10 @@ fn start_server(mut commands: Commands) -> Result {
|
||||
Name::from("Server"),
|
||||
LocalAddr(server_addr),
|
||||
ServerUdpIo::default(),
|
||||
NetcodeServer::new(NetcodeConfig::default()),
|
||||
NetcodeServer::new(NetcodeConfig {
|
||||
client_timeout_secs: 3,
|
||||
..Default::default()
|
||||
}),
|
||||
Link::new(Some(conditioner)),
|
||||
));
|
||||
commands.trigger(server::Start);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use super::TriggerMissile;
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::BuildExplosionSprite,
|
||||
abilities::{ExplodingProjectile, ExplodingProjectileSet},
|
||||
heads_database::HeadsDatabase,
|
||||
physics_layers::GameLayer,
|
||||
protocol::{GltfSceneRoot, PlaySound},
|
||||
utils::{commands::CommandExt, explosions::Explosion, global_observer, trail::Trail},
|
||||
utils::{global_observer, trail::Trail},
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
@@ -23,7 +23,10 @@ struct MissileProjectile {
|
||||
}
|
||||
|
||||
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(
|
||||
FixedUpdate,
|
||||
(update, timeout).run_if(in_state(GameState::Playing)),
|
||||
@@ -141,20 +144,14 @@ fn shot_collision(
|
||||
continue;
|
||||
};
|
||||
|
||||
commands.trigger(PlaySound::MissileExplosion);
|
||||
|
||||
commands.entity(shot_entity).despawn();
|
||||
|
||||
commands.trigger(Explosion {
|
||||
commands.entity(shot_entity).insert(ExplodingProjectile {
|
||||
sound: PlaySound::MissileExplosion,
|
||||
damage,
|
||||
position: shot_pos,
|
||||
radius: 6.,
|
||||
});
|
||||
|
||||
commands.trigger_server(BuildExplosionSprite {
|
||||
pos: shot_pos,
|
||||
pixels_per_meter: 16.,
|
||||
time: 0.01,
|
||||
radius: 6.0,
|
||||
animation: true,
|
||||
anim_pixels_per_meter: 16.0,
|
||||
anim_time: 0.01,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,23 +6,27 @@ pub mod missile;
|
||||
pub mod thrown;
|
||||
|
||||
use crate::{
|
||||
GameState,
|
||||
aim::AimTarget,
|
||||
character::CharacterHierarchy,
|
||||
global_observer,
|
||||
heads::ActiveHeads,
|
||||
heads_database::HeadsDatabase,
|
||||
GameState, global_observer,
|
||||
loading_assets::GameAssets,
|
||||
physics_layers::GameLayer,
|
||||
player::{Player, PlayerBodyMesh},
|
||||
protocol::PlaySound,
|
||||
utils::{billboards::Billboard, sprite_3d_animation::AnimationTimer},
|
||||
};
|
||||
#[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_sprite3d::{Sprite3dBuilder, Sprite3dParams};
|
||||
pub use healing::Healing;
|
||||
#[cfg(feature = "server")]
|
||||
use healing::HealingStateChanged;
|
||||
#[cfg(feature = "server")]
|
||||
use lightyear::prelude::input::native::ActionState;
|
||||
@@ -86,6 +90,7 @@ pub struct TriggerCurver(pub TriggerData);
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct TriggerStateRes {
|
||||
#[cfg(feature = "server")]
|
||||
next_trigger_timestamp: f32,
|
||||
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) {
|
||||
app.init_resource::<TriggerStateRes>();
|
||||
|
||||
@@ -106,20 +130,61 @@ pub fn plugin(app: &mut App) {
|
||||
app.add_plugins(healing::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);
|
||||
#[cfg(feature = "server")]
|
||||
app.add_systems(
|
||||
Update,
|
||||
(update, update_heal_ability).run_if(in_state(GameState::Playing)),
|
||||
FixedUpdate,
|
||||
(on_trigger_state, update, update_heal_ability)
|
||||
.chain()
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
#[cfg(feature = "server")]
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
on_trigger_state.run_if(in_state(GameState::Playing)),
|
||||
explode_projectiles.in_set(ExplodingProjectileSet::Explode),
|
||||
);
|
||||
|
||||
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")]
|
||||
fn on_trigger_state(
|
||||
mut res: ResMut<TriggerStateRes>,
|
||||
@@ -136,6 +201,7 @@ fn on_trigger_state(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn update(
|
||||
mut res: ResMut<TriggerStateRes>,
|
||||
mut commands: Commands,
|
||||
@@ -201,6 +267,7 @@ fn update(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn update_heal_ability(
|
||||
res: Res<TriggerStateRes>,
|
||||
mut commands: Commands,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
use super::TriggerThrow;
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::BuildExplosionSprite,
|
||||
abilities::{ExplodingProjectile, ExplodingProjectileSet},
|
||||
heads_database::HeadsDatabase,
|
||||
physics_layers::GameLayer,
|
||||
protocol::{GltfSceneRoot, PlaySound},
|
||||
utils::{
|
||||
auto_rotate::AutoRotation, commands::CommandExt, explosions::Explosion, global_observer,
|
||||
},
|
||||
utils::{auto_rotate::AutoRotation, global_observer},
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
@@ -24,7 +21,10 @@ pub struct ThrownProjectile {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -116,28 +116,14 @@ fn shot_collision(
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Ok(mut entity) = commands.get_entity(shot_entity) {
|
||||
entity.try_despawn();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
commands.trigger(PlaySound::ThrowHit);
|
||||
|
||||
commands.trigger(Explosion {
|
||||
commands.entity(shot_entity).insert(ExplodingProjectile {
|
||||
sound: PlaySound::ThrowHit,
|
||||
damage,
|
||||
position: shot_pos,
|
||||
//TODO: should be around 1 grid in distance
|
||||
radius: 5.,
|
||||
radius: 5.0,
|
||||
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,27 +1,37 @@
|
||||
use super::{ControlState, Controls};
|
||||
use crate::{
|
||||
GameState,
|
||||
control::{CharacterInputEnabled, ControllerSet},
|
||||
};
|
||||
use bevy::{
|
||||
input::{
|
||||
#[cfg(feature = "client")]
|
||||
use crate::control::ControllerSet;
|
||||
use crate::{GameState, control::CharacterInputEnabled};
|
||||
#[cfg(feature = "client")]
|
||||
use bevy::input::{
|
||||
ButtonState,
|
||||
gamepad::{GamepadConnection, GamepadEvent},
|
||||
mouse::{MouseButtonInput, MouseMotion},
|
||||
},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
#[cfg(feature = "client")]
|
||||
use lightyear::input::client::InputSet;
|
||||
#[cfg(feature = "client")]
|
||||
use lightyear::prelude::input::native::{ActionState, InputMarker};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "client")]
|
||||
use std::hash::Hash;
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.init_resource::<Controls>();
|
||||
app.init_resource::<InputStateCache<KeyCode>>();
|
||||
|
||||
app.register_required_components::<Gamepad, InputStateCache<GamepadButton>>();
|
||||
|
||||
app.register_type::<ControllerSettings>();
|
||||
|
||||
app.add_systems(PreUpdate, (cache_keyboard_state, cache_gamepad_state));
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
{
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
FixedPreUpdate,
|
||||
(
|
||||
gamepad_controls,
|
||||
keyboard_controls,
|
||||
@@ -29,19 +39,18 @@ pub fn plugin(app: &mut App) {
|
||||
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)),
|
||||
),
|
||||
);
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
{
|
||||
use lightyear::prelude::client::input::InputSet;
|
||||
app.add_systems(
|
||||
)
|
||||
.add_systems(
|
||||
FixedPreUpdate,
|
||||
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
|
||||
fn combine_controls(controls: Res<Controls>, mut combined_controls: ResMut<ControlState>) {
|
||||
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;
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
/// Applies a square deadzone to a Vec2
|
||||
fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
|
||||
Vec2::new(
|
||||
@@ -110,9 +219,13 @@ fn deadzone_square(v: Vec2, min: f32) -> Vec2 {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
/// Collect gamepad inputs
|
||||
fn gamepad_controls(gamepads: Query<(Entity, &Gamepad)>, mut controls: ResMut<Controls>) {
|
||||
let Some((_e, gamepad)) = gamepads.iter().next() else {
|
||||
fn gamepad_controls(
|
||||
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() {
|
||||
controls.gamepad_state = None;
|
||||
}
|
||||
@@ -146,17 +259,17 @@ fn gamepad_controls(gamepads: Query<(Entity, &Gamepad)>, mut controls: ResMut<Co
|
||||
let state = ControlState {
|
||||
move_dir: deadzone_square(gamepad.left_stick(), deadzone_left_stick),
|
||||
look_dir,
|
||||
jump: gamepad.pressed(GamepadButton::South),
|
||||
view_mode: gamepad.pressed(GamepadButton::LeftTrigger2),
|
||||
trigger: gamepad.pressed(GamepadButton::RightTrigger2),
|
||||
just_triggered: gamepad.just_pressed(GamepadButton::RightTrigger2),
|
||||
select_left: gamepad.just_pressed(GamepadButton::LeftTrigger),
|
||||
select_right: gamepad.just_pressed(GamepadButton::RightTrigger),
|
||||
backpack_left: gamepad.just_pressed(GamepadButton::DPadLeft),
|
||||
backpack_right: gamepad.just_pressed(GamepadButton::DPadRight),
|
||||
backpack_swap: gamepad.just_pressed(GamepadButton::DPadDown),
|
||||
backpack_toggle: gamepad.just_pressed(GamepadButton::DPadUp),
|
||||
cash_heal: gamepad.just_pressed(GamepadButton::East),
|
||||
jump: cache.pressed(GamepadButton::South),
|
||||
view_mode: cache.pressed(GamepadButton::LeftTrigger2),
|
||||
trigger: cache.pressed(GamepadButton::RightTrigger2),
|
||||
just_triggered: cache.just_pressed(GamepadButton::RightTrigger2),
|
||||
select_left: cache.just_pressed(GamepadButton::LeftTrigger),
|
||||
select_right: cache.just_pressed(GamepadButton::RightTrigger),
|
||||
backpack_left: cache.just_pressed(GamepadButton::DPadLeft),
|
||||
backpack_right: cache.just_pressed(GamepadButton::DPadRight),
|
||||
backpack_swap: cache.just_pressed(GamepadButton::DPadDown),
|
||||
backpack_toggle: cache.just_pressed(GamepadButton::DPadUp),
|
||||
cash_heal: cache.just_pressed(GamepadButton::East),
|
||||
};
|
||||
|
||||
if controls
|
||||
@@ -169,6 +282,7 @@ fn gamepad_controls(gamepads: Query<(Entity, &Gamepad)>, mut controls: ResMut<Co
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
/// Collect mouse movement input
|
||||
fn mouse_rotate(mut mouse: EventReader<MouseMotion>, mut controls: ResMut<Controls>) {
|
||||
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
|
||||
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 down_binds = [KeyCode::KeyS, KeyCode::ArrowDown];
|
||||
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);
|
||||
|
||||
controls.keyboard_state.move_dir = direction;
|
||||
controls.keyboard_state.jump = keyboard.pressed(KeyCode::Space);
|
||||
controls.keyboard_state.view_mode = keyboard.pressed(KeyCode::Tab);
|
||||
controls.keyboard_state.backpack_toggle = keyboard.just_pressed(KeyCode::KeyB);
|
||||
controls.keyboard_state.backpack_swap = keyboard.just_pressed(KeyCode::Enter);
|
||||
controls.keyboard_state.backpack_left = keyboard.just_pressed(KeyCode::Comma);
|
||||
controls.keyboard_state.backpack_right = keyboard.just_pressed(KeyCode::Period);
|
||||
controls.keyboard_state.select_left = keyboard.just_pressed(KeyCode::KeyQ);
|
||||
controls.keyboard_state.select_right = keyboard.just_pressed(KeyCode::KeyE);
|
||||
controls.keyboard_state.cash_heal = keyboard.just_pressed(KeyCode::Enter);
|
||||
controls.keyboard_state.jump = cache.pressed(KeyCode::Space);
|
||||
controls.keyboard_state.view_mode = cache.pressed(KeyCode::Tab);
|
||||
controls.keyboard_state.backpack_toggle = cache.just_pressed(KeyCode::KeyB);
|
||||
controls.keyboard_state.backpack_swap = cache.just_pressed(KeyCode::Enter);
|
||||
controls.keyboard_state.backpack_left = cache.just_pressed(KeyCode::Comma);
|
||||
controls.keyboard_state.backpack_right = cache.just_pressed(KeyCode::Period);
|
||||
controls.keyboard_state.select_left = cache.just_pressed(KeyCode::KeyQ);
|
||||
controls.keyboard_state.select_right = cache.just_pressed(KeyCode::KeyE);
|
||||
controls.keyboard_state.cash_heal = cache.just_pressed(KeyCode::Enter);
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
/// Collect mouse button input when pressed
|
||||
fn mouse_click(mut events: EventReader<MouseButtonInput>, mut controls: ResMut<Controls>) {
|
||||
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.just_triggered = false;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
/// Receive gamepad connections and disconnections
|
||||
fn gamepad_connections(mut evr_gamepad: EventReader<GamepadEvent>) {
|
||||
for ev in evr_gamepad.read() {
|
||||
|
||||
@@ -45,7 +45,9 @@ impl MapEntities for ControlState {
|
||||
|
||||
#[derive(Resource, Debug, Default)]
|
||||
struct Controls {
|
||||
#[cfg(feature = "client")]
|
||||
keyboard_state: ControlState,
|
||||
#[cfg(feature = "client")]
|
||||
gamepad_state: Option<ControlState>,
|
||||
}
|
||||
|
||||
@@ -74,9 +76,12 @@ pub fn plugin(app: &mut App) {
|
||||
app.add_event::<ControllerSwitchEvent>();
|
||||
|
||||
app.configure_sets(
|
||||
FixedPreUpdate,
|
||||
ControllerSet::CollectInputs.run_if(in_state(GameState::Playing)),
|
||||
)
|
||||
.configure_sets(
|
||||
FixedUpdate,
|
||||
(
|
||||
ControllerSet::CollectInputs,
|
||||
ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController(
|
||||
ControllerSet::ApplyControlsFly,
|
||||
))),
|
||||
|
||||
@@ -107,8 +107,6 @@ fn on_spawn_check(
|
||||
.insert_if(Ai, || !spawn.disable_ai)
|
||||
.with_child((Name::from("body-rig"), AnimatedCharacter::new(id)))
|
||||
.observe(on_kill);
|
||||
#[cfg(feature = "server")]
|
||||
ecommands.insert(Replicate::to_clients(NetworkTarget::All));
|
||||
|
||||
commands.trigger(SpawnCharacter(transform.translation));
|
||||
commands.trigger(PlaySound::Beaming);
|
||||
|
||||
Reference in New Issue
Block a user