diff --git a/src/game.rs b/src/game.rs index d42c0ca..7536f4d 100644 --- a/src/game.rs +++ b/src/game.rs @@ -11,6 +11,7 @@ use crate::error::{GameError, GameResult}; use crate::events::{CollisionTrigger, GameEvent, StageTransition}; use crate::map::builder::Map; use crate::map::direction::Direction; +use crate::systems::state::IntroPlayed; use crate::systems::{ self, audio_system, blinking_system, collision_system, combined_render_system, directional_render_system, dirty_render_system, eaten_ghost_system, fruit_sprite_system, ghost_collision_observer, ghost_movement_system, @@ -438,6 +439,7 @@ impl Game { world.insert_resource(RenderDirty::default()); world.insert_resource(DebugState::default()); world.insert_resource(AudioState::default()); + world.insert_resource(IntroPlayed::default()); world.insert_resource(CursorPosition::default()); world.insert_resource(TouchState::default()); world.insert_resource(GameStage::Starting(StartupSequence::TextOnly { diff --git a/src/systems/audio.rs b/src/systems/audio.rs index b2b337b..2a679bc 100644 --- a/src/systems/audio.rs +++ b/src/systems/audio.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ }; use tracing::{debug, trace}; -use crate::{audio::Audio, error::GameError}; +use crate::{audio::Audio, audio::Sound, error::GameError}; /// Resource for tracking audio state #[derive(Resource, Debug, Clone, Default)] @@ -25,10 +25,10 @@ pub struct AudioState { /// Events for triggering audio playback #[derive(Event, Debug, Clone, Copy, PartialEq, Eq)] pub enum AudioEvent { - /// Play the "eat" sound when Pac-Man consumes a pellet - PlayEat, - /// Play the death sound - PlayDeath, + /// Play a specific sound effect + PlaySound(Sound), + /// Play the cycling waka sound variant + Waka, /// Stop all currently playing sounds StopAll, /// Pause all sounds @@ -61,7 +61,7 @@ pub fn audio_system( // Process audio events for event in audio_events.read() { match event { - AudioEvent::PlayEat => { + AudioEvent::Waka => { if !audio.0.is_disabled() && !audio_state.muted { trace!(sound_index = audio_state.sound_index, "Playing eat sound"); audio.0.waka(); @@ -76,15 +76,15 @@ pub fn audio_system( ); } } - AudioEvent::PlayDeath => { + AudioEvent::PlaySound(sound) => { if !audio.0.is_disabled() && !audio_state.muted { - trace!("Playing death sound"); - audio.0.play(crate::audio::Sound::PacmanDeath); + trace!(?sound, "Playing sound"); + audio.0.play(*sound); } else { debug!( disabled = audio.0.is_disabled(), muted = audio_state.muted, - "Skipping death sound due to audio state" + "Skipping sound due to audio state" ); } } diff --git a/src/systems/collision.rs b/src/systems/collision.rs index aa44cb8..c54e62c 100644 --- a/src/systems/collision.rs +++ b/src/systems/collision.rs @@ -8,6 +8,7 @@ use bevy_ecs::{ }; use tracing::{debug, trace, warn}; +use crate::audio::Sound; use crate::{ constants, systems::{movement::Position, AudioEvent, DyingSequence, FruitSprites, GameStage, Ghost, ScoreResource, SpawnTrigger}, @@ -165,8 +166,8 @@ pub fn ghost_collision_observer( ghost_type, }); - // Play eat sound - events.write(AudioEvent::PlayEat); + // Play ghost eaten sound + events.write(AudioEvent::PlaySound(Sound::Ghost)); } else if matches!(*ghost_state, GhostState::Normal) { // Pac-Man dies warn!(ghost = ?ghost_type, "Pacman hit by normal ghost, player dies"); @@ -226,7 +227,15 @@ pub fn item_collision_observer( // Trigger audio if appropriate if entity_type.is_collectible() { - events.write(AudioEvent::PlayEat); + match *entity_type { + EntityType::Fruit(_) => { + events.write(AudioEvent::PlaySound(Sound::Fruit)); + } + EntityType::Pellet | EntityType::PowerPellet => { + events.write(AudioEvent::Waka); + } + _ => {} + } } // Make non-eaten ghosts frightened when power pellet is collected diff --git a/src/systems/state.rs b/src/systems/state.rs index 8f077de..151285f 100644 --- a/src/systems/state.rs +++ b/src/systems/state.rs @@ -28,6 +28,10 @@ pub struct PlayerAnimation(pub DirectionalAnimation); #[derive(Resource, Clone)] pub struct PlayerDeathAnimation(pub LinearAnimation); +/// Tracks whether the beginning sound has been played for the current startup sequence +#[derive(Resource, Debug, Default, Clone, Copy)] +pub struct IntroPlayed(pub bool); + /// A resource to track the overall stage of the game from a high-level perspective. #[derive(Resource, Debug, PartialEq, Eq, Clone, Copy)] pub enum GameStage { @@ -183,6 +187,7 @@ pub fn stage_system( player: Single<(Entity, &mut Position), With>, mut item_query: Query<(Entity, &EntityType), With>, mut ghost_query: Query<(Entity, &Ghost, &mut Position, &mut GhostState), (With, Without)>, + mut intro_played: ResMut, ) { let old_state = *game_state; let mut new_state_opt: Option = None; @@ -229,6 +234,11 @@ pub fn stage_system( } GameStage::Starting(sequence) => match sequence { StartupSequence::TextOnly { remaining_ticks } => { + // Play the beginning sound once at the start of TextOnly stage + if !intro_played.0 { + audio_events.write(AudioEvent::PlaySound(crate::audio::Sound::Beginning)); + intro_played.0 = true; + } if remaining_ticks > 0 { GameStage::Starting(StartupSequence::TextOnly { remaining_ticks: remaining_ticks.saturating_sub(1), @@ -354,7 +364,7 @@ pub fn stage_system( .insert((Dying, player_death_animation.0.clone())); // Play the death sound - audio_events.write(AudioEvent::PlayDeath); + audio_events.write(AudioEvent::PlaySound(crate::audio::Sound::PacmanDeath)); } (_, GameStage::PlayerDying(DyingSequence::Hidden { .. })) => { // Pac-Man's death animation is complete, so he should be hidden just like the ghosts. @@ -423,6 +433,8 @@ pub fn stage_system( for entity in blinking_query.iter_mut() { commands.entity(entity).remove::(); } + // Reset intro flag for the next round + intro_played.0 = false; } (_, GameStage::GameOver) => { // Freeze blinking