mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-09 04:07:57 -06:00
feat: re-implement pausing mechanism with tick-perfect audio & state pauses
This commit is contained in:
14
src/audio.rs
14
src/audio.rs
@@ -191,6 +191,20 @@ impl Audio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pauses all currently playing audio channels.
|
||||||
|
pub fn pause_all(&mut self) {
|
||||||
|
if !self.disabled {
|
||||||
|
mixer::Channel::all().pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resumes all currently playing audio channels.
|
||||||
|
pub fn resume_all(&mut self) {
|
||||||
|
if !self.disabled {
|
||||||
|
mixer::Channel::all().resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Instantly mutes or unmutes all audio channels by adjusting their volume.
|
/// Instantly mutes or unmutes all audio channels by adjusting their volume.
|
||||||
///
|
///
|
||||||
/// Sets all 4 mixer channels to zero volume when muting, or restores them to
|
/// Sets all 4 mixer channels to zero volume when muting, or restores them to
|
||||||
|
|||||||
13
src/game.rs
13
src/game.rs
@@ -19,8 +19,8 @@ use crate::systems::{
|
|||||||
BackbufferResource, Blinking, BufferedDirection, Collider, DebugState, DebugTextureResource, DeltaTime, DirectionalAnimation,
|
BackbufferResource, Blinking, BufferedDirection, Collider, DebugState, DebugTextureResource, DeltaTime, DirectionalAnimation,
|
||||||
EntityType, Frozen, FruitSprites, GameStage, Ghost, GhostAnimation, GhostAnimations, GhostBundle, GhostCollider, GhostState,
|
EntityType, Frozen, FruitSprites, GameStage, Ghost, GhostAnimation, GhostAnimations, GhostBundle, GhostCollider, GhostState,
|
||||||
GlobalState, ItemBundle, ItemCollider, LastAnimationState, LinearAnimation, MapTextureResource, MovementModifiers, NodeId,
|
GlobalState, ItemBundle, ItemCollider, LastAnimationState, LinearAnimation, MapTextureResource, MovementModifiers, NodeId,
|
||||||
PacmanCollider, PlayerAnimation, PlayerBundle, PlayerControlled, PlayerDeathAnimation, PlayerLives, Position, RenderDirty,
|
PacmanCollider, Paused, PlayerAnimation, PlayerBundle, PlayerControlled, PlayerDeathAnimation, PlayerLives, Position,
|
||||||
Renderable, ScoreResource, StartupSequence, SystemId, SystemTimings, Timing, TouchState, Velocity, Visibility,
|
RenderDirty, Renderable, ScoreResource, StartupSequence, SystemId, SystemTimings, Timing, TouchState, Velocity, Visibility,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::texture::animated::{DirectionalTiles, TileSequence};
|
use crate::texture::animated::{DirectionalTiles, TileSequence};
|
||||||
@@ -443,6 +443,7 @@ impl Game {
|
|||||||
world.insert_resource(GameStage::Starting(StartupSequence::TextOnly {
|
world.insert_resource(GameStage::Starting(StartupSequence::TextOnly {
|
||||||
remaining_ticks: constants::startup::STARTUP_FRAMES,
|
remaining_ticks: constants::startup::STARTUP_FRAMES,
|
||||||
}));
|
}));
|
||||||
|
world.insert_resource(Paused(false));
|
||||||
|
|
||||||
world.insert_non_send_resource(event_pump);
|
world.insert_non_send_resource(event_pump);
|
||||||
world.insert_non_send_resource::<&mut Canvas<Window>>(Box::leak(Box::new(canvas)));
|
world.insert_non_send_resource::<&mut Canvas<Window>>(Box::leak(Box::new(canvas)));
|
||||||
@@ -457,6 +458,7 @@ impl Game {
|
|||||||
fn configure_schedule(schedule: &mut Schedule) {
|
fn configure_schedule(schedule: &mut Schedule) {
|
||||||
let stage_system = profile(SystemId::Stage, systems::stage_system);
|
let stage_system = profile(SystemId::Stage, systems::stage_system);
|
||||||
let input_system = profile(SystemId::Input, systems::input::input_system);
|
let input_system = profile(SystemId::Input, systems::input::input_system);
|
||||||
|
let pause_system = profile(SystemId::Input, systems::handle_pause_command);
|
||||||
let player_control_system = profile(SystemId::PlayerControls, systems::player_control_system);
|
let player_control_system = profile(SystemId::PlayerControls, systems::player_control_system);
|
||||||
let player_movement_system = profile(SystemId::PlayerMovement, systems::player_movement_system);
|
let player_movement_system = profile(SystemId::PlayerMovement, systems::player_movement_system);
|
||||||
let player_tunnel_slowdown_system = profile(SystemId::PlayerMovement, systems::player::player_tunnel_slowdown_system);
|
let player_tunnel_slowdown_system = profile(SystemId::PlayerMovement, systems::player::player_tunnel_slowdown_system);
|
||||||
@@ -483,6 +485,7 @@ impl Game {
|
|||||||
*local % 2 == 0
|
*local % 2 == 0
|
||||||
}),
|
}),
|
||||||
player_control_system,
|
player_control_system,
|
||||||
|
pause_system,
|
||||||
)
|
)
|
||||||
.chain();
|
.chain();
|
||||||
|
|
||||||
@@ -525,9 +528,9 @@ impl Game {
|
|||||||
))
|
))
|
||||||
.configure_sets((
|
.configure_sets((
|
||||||
GameplaySet::Input,
|
GameplaySet::Input,
|
||||||
GameplaySet::Update,
|
GameplaySet::Update.run_if(|paused: Res<Paused>| !paused.0),
|
||||||
GameplaySet::Respond,
|
GameplaySet::Respond.run_if(|paused: Res<Paused>| !paused.0),
|
||||||
RenderSet::Animation,
|
RenderSet::Animation.run_if(|paused: Res<Paused>| !paused.0),
|
||||||
RenderSet::Draw,
|
RenderSet::Draw,
|
||||||
RenderSet::Present,
|
RenderSet::Present,
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ pub enum AudioEvent {
|
|||||||
PlayDeath,
|
PlayDeath,
|
||||||
/// Stop all currently playing sounds
|
/// Stop all currently playing sounds
|
||||||
StopAll,
|
StopAll,
|
||||||
|
/// Pause all sounds
|
||||||
|
Pause,
|
||||||
|
/// Resume all sounds
|
||||||
|
Resume,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Non-send resource wrapper for SDL2 audio system
|
/// Non-send resource wrapper for SDL2 audio system
|
||||||
@@ -92,6 +96,22 @@ pub fn audio_system(
|
|||||||
debug!("Audio disabled, ignoring stop all request");
|
debug!("Audio disabled, ignoring stop all request");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AudioEvent::Pause => {
|
||||||
|
if !audio.0.is_disabled() {
|
||||||
|
debug!("Pausing all audio");
|
||||||
|
audio.0.pause_all();
|
||||||
|
} else {
|
||||||
|
debug!("Audio disabled, ignoring pause all request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AudioEvent::Resume => {
|
||||||
|
if !audio.0.is_disabled() {
|
||||||
|
debug!("Resuming all audio");
|
||||||
|
audio.0.resume_all();
|
||||||
|
} else {
|
||||||
|
debug!("Audio disabled, ignoring resume all request");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::mem::discriminant;
|
use std::mem::discriminant;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::constants;
|
use crate::constants;
|
||||||
use crate::events::StageTransition;
|
use crate::events::StageTransition;
|
||||||
@@ -20,6 +20,8 @@ use bevy_ecs::{
|
|||||||
system::{Commands, Query, Res, ResMut, Single},
|
system::{Commands, Query, Res, ResMut, Single},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::events::{GameCommand, GameEvent};
|
||||||
|
|
||||||
#[derive(Resource, Clone)]
|
#[derive(Resource, Clone)]
|
||||||
pub struct PlayerAnimation(pub DirectionalAnimation);
|
pub struct PlayerAnimation(pub DirectionalAnimation);
|
||||||
|
|
||||||
@@ -45,6 +47,28 @@ pub enum GameStage {
|
|||||||
GameOver,
|
GameOver,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Debug, Default)]
|
||||||
|
pub struct Paused(pub bool);
|
||||||
|
|
||||||
|
pub fn handle_pause_command(
|
||||||
|
mut events: EventReader<GameEvent>,
|
||||||
|
mut paused: ResMut<Paused>,
|
||||||
|
mut audio_events: EventWriter<AudioEvent>,
|
||||||
|
) {
|
||||||
|
for event in events.read() {
|
||||||
|
if let GameEvent::Command(GameCommand::TogglePause) = event {
|
||||||
|
paused.0 = !paused.0;
|
||||||
|
if paused.0 {
|
||||||
|
info!("Game paused");
|
||||||
|
audio_events.write(AudioEvent::Pause);
|
||||||
|
} else {
|
||||||
|
info!("Game resumed");
|
||||||
|
audio_events.write(AudioEvent::Resume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait TooSimilar {
|
pub trait TooSimilar {
|
||||||
fn too_similar(&self, other: &Self) -> bool;
|
fn too_similar(&self, other: &Self) -> bool;
|
||||||
}
|
}
|
||||||
@@ -161,7 +185,7 @@ pub fn stage_system(
|
|||||||
mut ghost_query: Query<(Entity, &Ghost, &mut Position, &mut GhostState), (With<GhostCollider>, Without<PlayerControlled>)>,
|
mut ghost_query: Query<(Entity, &Ghost, &mut Position, &mut GhostState), (With<GhostCollider>, Without<PlayerControlled>)>,
|
||||||
) {
|
) {
|
||||||
let old_state = *game_state;
|
let old_state = *game_state;
|
||||||
let mut new_state: Option<GameStage> = None;
|
let mut new_state_opt: Option<GameStage> = None;
|
||||||
|
|
||||||
// Handle stage transition requests before normal ticking
|
// Handle stage transition requests before normal ticking
|
||||||
for event in stage_event_reader.read() {
|
for event in stage_event_reader.read() {
|
||||||
@@ -172,7 +196,7 @@ pub fn stage_system(
|
|||||||
let pac_node = player.1.current_node();
|
let pac_node = player.1.current_node();
|
||||||
|
|
||||||
debug!(ghost = ?ghost_type, node = pac_node, "Ghost eaten, entering pause state");
|
debug!(ghost = ?ghost_type, node = pac_node, "Ghost eaten, entering pause state");
|
||||||
new_state = Some(GameStage::GhostEatenPause {
|
new_state_opt = Some(GameStage::GhostEatenPause {
|
||||||
remaining_ticks: 30,
|
remaining_ticks: 30,
|
||||||
ghost_entity,
|
ghost_entity,
|
||||||
ghost_type,
|
ghost_type,
|
||||||
@@ -180,29 +204,11 @@ pub fn stage_system(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_state: GameStage = match new_state.unwrap_or(*game_state) {
|
let new_state: GameStage = new_state_opt.unwrap_or_else(|| match *game_state {
|
||||||
GameStage::Starting(startup) => match startup {
|
GameStage::Playing => {
|
||||||
StartupSequence::TextOnly { remaining_ticks } => {
|
// This is the default state, do nothing
|
||||||
if remaining_ticks > 0 {
|
*game_state
|
||||||
GameStage::Starting(StartupSequence::TextOnly {
|
}
|
||||||
remaining_ticks: remaining_ticks - 1,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
GameStage::Starting(StartupSequence::CharactersVisible { remaining_ticks: 60 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StartupSequence::CharactersVisible { remaining_ticks } => {
|
|
||||||
if remaining_ticks > 0 {
|
|
||||||
GameStage::Starting(StartupSequence::CharactersVisible {
|
|
||||||
remaining_ticks: remaining_ticks - 1,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
info!("Startup sequence completed, beginning gameplay");
|
|
||||||
GameStage::Playing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GameStage::Playing => GameStage::Playing,
|
|
||||||
GameStage::GhostEatenPause {
|
GameStage::GhostEatenPause {
|
||||||
remaining_ticks,
|
remaining_ticks,
|
||||||
ghost_entity,
|
ghost_entity,
|
||||||
@@ -221,11 +227,32 @@ pub fn stage_system(
|
|||||||
GameStage::Playing
|
GameStage::Playing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameStage::PlayerDying(dying) => match dying {
|
GameStage::Starting(sequence) => match sequence {
|
||||||
|
StartupSequence::TextOnly { remaining_ticks } => {
|
||||||
|
if remaining_ticks > 0 {
|
||||||
|
GameStage::Starting(StartupSequence::TextOnly {
|
||||||
|
remaining_ticks: remaining_ticks.saturating_sub(1),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
GameStage::Starting(StartupSequence::CharactersVisible { remaining_ticks: 60 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StartupSequence::CharactersVisible { remaining_ticks } => {
|
||||||
|
if remaining_ticks > 0 {
|
||||||
|
GameStage::Starting(StartupSequence::CharactersVisible {
|
||||||
|
remaining_ticks: remaining_ticks.saturating_sub(1),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
info!("Startup sequence completed, beginning gameplay");
|
||||||
|
GameStage::Playing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GameStage::PlayerDying(sequence) => match sequence {
|
||||||
DyingSequence::Frozen { remaining_ticks } => {
|
DyingSequence::Frozen { remaining_ticks } => {
|
||||||
if remaining_ticks > 0 {
|
if remaining_ticks > 0 {
|
||||||
GameStage::PlayerDying(DyingSequence::Frozen {
|
GameStage::PlayerDying(DyingSequence::Frozen {
|
||||||
remaining_ticks: remaining_ticks - 1,
|
remaining_ticks: remaining_ticks.saturating_sub(1),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let death_animation = &player_death_animation.0;
|
let death_animation = &player_death_animation.0;
|
||||||
@@ -237,7 +264,7 @@ pub fn stage_system(
|
|||||||
DyingSequence::Animating { remaining_ticks } => {
|
DyingSequence::Animating { remaining_ticks } => {
|
||||||
if remaining_ticks > 0 {
|
if remaining_ticks > 0 {
|
||||||
GameStage::PlayerDying(DyingSequence::Animating {
|
GameStage::PlayerDying(DyingSequence::Animating {
|
||||||
remaining_ticks: remaining_ticks - 1,
|
remaining_ticks: remaining_ticks.saturating_sub(1),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
GameStage::PlayerDying(DyingSequence::Hidden { remaining_ticks: 60 })
|
GameStage::PlayerDying(DyingSequence::Hidden { remaining_ticks: 60 })
|
||||||
@@ -246,7 +273,7 @@ pub fn stage_system(
|
|||||||
DyingSequence::Hidden { remaining_ticks } => {
|
DyingSequence::Hidden { remaining_ticks } => {
|
||||||
if remaining_ticks > 0 {
|
if remaining_ticks > 0 {
|
||||||
GameStage::PlayerDying(DyingSequence::Hidden {
|
GameStage::PlayerDying(DyingSequence::Hidden {
|
||||||
remaining_ticks: remaining_ticks - 1,
|
remaining_ticks: remaining_ticks.saturating_sub(1),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
player_lives.0 = player_lives.0.saturating_sub(1);
|
player_lives.0 = player_lives.0.saturating_sub(1);
|
||||||
@@ -255,14 +282,14 @@ pub fn stage_system(
|
|||||||
info!(remaining_lives = player_lives.0, "Player died, returning to startup sequence");
|
info!(remaining_lives = player_lives.0, "Player died, returning to startup sequence");
|
||||||
GameStage::Starting(StartupSequence::CharactersVisible { remaining_ticks: 60 })
|
GameStage::Starting(StartupSequence::CharactersVisible { remaining_ticks: 60 })
|
||||||
} else {
|
} else {
|
||||||
warn!("All lives lost, game over");
|
info!("All lives lost, game over");
|
||||||
GameStage::GameOver
|
GameStage::GameOver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GameStage::GameOver => GameStage::GameOver,
|
GameStage::GameOver => *game_state,
|
||||||
};
|
});
|
||||||
|
|
||||||
if old_state == new_state {
|
if old_state == new_state {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user