fix: allow key holddown

This commit is contained in:
2025-08-16 11:57:09 -05:00
parent 78300bdf9c
commit 9a413d1646
4 changed files with 72 additions and 56 deletions

View File

@@ -18,9 +18,9 @@ use crate::{
pub fn ghost_movement_system( pub fn ghost_movement_system(
map: Res<Map>, map: Res<Map>,
delta_time: Res<DeltaTime>, delta_time: Res<DeltaTime>,
mut ghosts: Query<(&mut Ghost, &mut Velocity, &mut Position)>, mut ghosts: Query<(&Ghost, &mut Velocity, &mut Position)>,
) { ) {
for (mut ghost, mut velocity, mut position) in ghosts.iter_mut() { for (_ghost, mut velocity, mut position) in ghosts.iter_mut() {
let mut distance = velocity.speed * 60.0 * delta_time.0; let mut distance = velocity.speed * 60.0 * delta_time.0;
loop { loop {
match *position { match *position {
@@ -32,10 +32,10 @@ pub fn ghost_movement_system(
// Collect all available directions that ghosts can traverse // Collect all available directions that ghosts can traverse
for edge in Direction::DIRECTIONS.iter().flat_map(|d| intersection.get(*d)) { for edge in Direction::DIRECTIONS.iter().flat_map(|d| intersection.get(*d)) {
if edge.traversal_flags.contains(crate::entity::graph::TraversalFlags::GHOST) { if edge.traversal_flags.contains(crate::entity::graph::TraversalFlags::GHOST)
if edge.direction != opposite { && edge.direction != opposite
non_opposite_options.push(edge); {
} non_opposite_options.push(edge);
} }
} }
@@ -67,39 +67,3 @@ pub fn ghost_movement_system(
} }
} }
} }
/// Chooses a random available direction for a ghost at an intersection.
///
/// This function mirrors the behavior from the old ghost implementation,
/// preferring not to reverse direction unless it's the only option.
fn choose_random_direction(map: &Map, velocity: &mut Velocity, position: &Position) {
let current_node = position.current_node();
let intersection = &map.graph.adjacency_list[current_node];
// Collect all available directions that ghosts can traverse
let mut available_directions = SmallVec::<[Direction; 4]>::new();
for direction in Direction::DIRECTIONS {
if let Some(edge) = intersection.get(direction) {
// Check if ghosts can traverse this edge
if edge.traversal_flags.contains(crate::entity::graph::TraversalFlags::GHOST) {
available_directions.push(direction);
}
}
}
// Choose a random direction (avoid reversing unless necessary)
if !available_directions.is_empty() {
let mut rng = SmallRng::from_os_rng();
// Filter out the opposite direction if possible, but allow it if we have limited options
let opposite = velocity.direction.opposite();
let filtered_directions: Vec<_> = available_directions
.iter()
.filter(|&&dir| dir != opposite || available_directions.len() <= 2)
.collect();
if let Some(&random_direction) = filtered_directions.choose(&mut rng) {
velocity.direction = *random_direction;
}
}
}

View File

@@ -1,6 +1,10 @@
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use bevy_ecs::{event::EventWriter, prelude::Res, resource::Resource, system::NonSendMut}; use bevy_ecs::{
event::EventWriter,
resource::Resource,
system::{NonSendMut, ResMut},
};
use sdl2::{event::Event, keyboard::Keycode, EventPump}; use sdl2::{event::Event, keyboard::Keycode, EventPump};
use crate::{ use crate::{
@@ -11,6 +15,8 @@ use crate::{
#[derive(Debug, Clone, Resource)] #[derive(Debug, Clone, Resource)]
pub struct Bindings { pub struct Bindings {
key_bindings: HashMap<Keycode, GameCommand>, key_bindings: HashMap<Keycode, GameCommand>,
movement_keys: HashSet<Keycode>,
last_movement_key: Option<Keycode>,
} }
impl Default for Bindings { impl Default for Bindings {
@@ -35,23 +41,74 @@ impl Default for Bindings {
key_bindings.insert(Keycode::Escape, GameCommand::Exit); key_bindings.insert(Keycode::Escape, GameCommand::Exit);
key_bindings.insert(Keycode::Q, GameCommand::Exit); key_bindings.insert(Keycode::Q, GameCommand::Exit);
Self { key_bindings } let movement_keys = HashSet::from([
Keycode::W,
Keycode::A,
Keycode::S,
Keycode::D,
Keycode::Up,
Keycode::Down,
Keycode::Left,
Keycode::Right,
]);
Self {
key_bindings,
movement_keys,
last_movement_key: None,
}
} }
} }
pub fn input_system(bindings: Res<Bindings>, mut writer: EventWriter<GameEvent>, mut pump: NonSendMut<&'static mut EventPump>) { pub fn input_system(
mut bindings: ResMut<Bindings>,
mut writer: EventWriter<GameEvent>,
mut pump: NonSendMut<&'static mut EventPump>,
) {
let mut movement_key_pressed = false;
for event in pump.poll_iter() { for event in pump.poll_iter() {
match event { match event {
Event::Quit { .. } => { Event::Quit { .. } => {
writer.write(GameEvent::Command(GameCommand::Exit)); writer.write(GameEvent::Command(GameCommand::Exit));
} }
Event::KeyDown { keycode: Some(key), .. } => { Event::KeyUp {
repeat: false,
keycode: Some(key),
..
} => {
// If the last movement key was released, then forget it.
if let Some(last_movement_key) = bindings.last_movement_key {
if last_movement_key == key {
bindings.last_movement_key = None;
}
}
}
Event::KeyDown {
keycode: Some(key),
repeat: false,
..
} => {
let command = bindings.key_bindings.get(&key).copied(); let command = bindings.key_bindings.get(&key).copied();
if let Some(command) = command { if let Some(command) = command {
writer.write(GameEvent::Command(command)); writer.write(GameEvent::Command(command));
} }
if bindings.movement_keys.contains(&key) {
movement_key_pressed = true;
bindings.last_movement_key = Some(key);
}
} }
_ => {} _ => {}
} }
} }
if let Some(last_movement_key) = bindings.last_movement_key {
if !movement_key_pressed {
let command = bindings.key_bindings.get(&last_movement_key).copied();
if let Some(command) = command {
writer.write(GameEvent::Command(command));
}
}
}
} }

View File

@@ -1,12 +1,7 @@
use crate::entity::direction::Direction;
use crate::entity::graph::Graph; use crate::entity::graph::Graph;
use crate::entity::{direction::Direction, graph::Edge}; use crate::error::{EntityError, GameResult};
use crate::error::{EntityError, GameError, GameResult};
use crate::map::builder::Map;
use crate::systems::components::{DeltaTime, EntityType, PlayerControlled};
use bevy_ecs::component::Component; use bevy_ecs::component::Component;
use bevy_ecs::event::EventWriter;
use bevy_ecs::query::With;
use bevy_ecs::system::{Query, Res};
use glam::Vec2; use glam::Vec2;
/// A unique identifier for a node, represented by its index in the graph's storage. /// A unique identifier for a node, represented by its index in the graph's storage.

View File

@@ -23,7 +23,7 @@ pub fn player_control_system(
mut state: ResMut<GlobalState>, mut state: ResMut<GlobalState>,
mut debug_state: ResMut<DebugState>, mut debug_state: ResMut<DebugState>,
mut audio_state: ResMut<AudioState>, mut audio_state: ResMut<AudioState>,
mut players: Query<(&mut BufferedDirection), With<PlayerControlled>>, mut players: Query<&mut BufferedDirection, With<PlayerControlled>>,
mut errors: EventWriter<GameError>, mut errors: EventWriter<GameError>,
) { ) {
// Get the player's movable component (ensuring there is only one player) // Get the player's movable component (ensuring there is only one player)
@@ -73,7 +73,7 @@ pub fn player_movement_system(
map: Res<Map>, map: Res<Map>,
delta_time: Res<DeltaTime>, delta_time: Res<DeltaTime>,
mut entities: Query<(&mut Position, &mut Velocity, &mut BufferedDirection), With<PlayerControlled>>, mut entities: Query<(&mut Position, &mut Velocity, &mut BufferedDirection), With<PlayerControlled>>,
mut errors: EventWriter<GameError>, // mut errors: EventWriter<GameError>,
) { ) {
for (mut position, mut velocity, mut buffered_direction) in entities.iter_mut() { for (mut position, mut velocity, mut buffered_direction) in entities.iter_mut() {
// Decrement the buffered direction remaining time // Decrement the buffered direction remaining time