mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-13 12:12:25 -06:00
feat(audio): setup intro jingle, use fruit & ghost sounds, improve AudioEvent
This commit is contained in:
@@ -11,6 +11,7 @@ use crate::error::{GameError, GameResult};
|
|||||||
use crate::events::{CollisionTrigger, GameEvent, StageTransition};
|
use crate::events::{CollisionTrigger, GameEvent, StageTransition};
|
||||||
use crate::map::builder::Map;
|
use crate::map::builder::Map;
|
||||||
use crate::map::direction::Direction;
|
use crate::map::direction::Direction;
|
||||||
|
use crate::systems::state::IntroPlayed;
|
||||||
use crate::systems::{
|
use crate::systems::{
|
||||||
self, audio_system, blinking_system, collision_system, combined_render_system, directional_render_system,
|
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,
|
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(RenderDirty::default());
|
||||||
world.insert_resource(DebugState::default());
|
world.insert_resource(DebugState::default());
|
||||||
world.insert_resource(AudioState::default());
|
world.insert_resource(AudioState::default());
|
||||||
|
world.insert_resource(IntroPlayed::default());
|
||||||
world.insert_resource(CursorPosition::default());
|
world.insert_resource(CursorPosition::default());
|
||||||
world.insert_resource(TouchState::default());
|
world.insert_resource(TouchState::default());
|
||||||
world.insert_resource(GameStage::Starting(StartupSequence::TextOnly {
|
world.insert_resource(GameStage::Starting(StartupSequence::TextOnly {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use bevy_ecs::{
|
|||||||
};
|
};
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
use crate::{audio::Audio, error::GameError};
|
use crate::{audio::Audio, audio::Sound, error::GameError};
|
||||||
|
|
||||||
/// Resource for tracking audio state
|
/// Resource for tracking audio state
|
||||||
#[derive(Resource, Debug, Clone, Default)]
|
#[derive(Resource, Debug, Clone, Default)]
|
||||||
@@ -25,10 +25,10 @@ pub struct AudioState {
|
|||||||
/// Events for triggering audio playback
|
/// Events for triggering audio playback
|
||||||
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AudioEvent {
|
pub enum AudioEvent {
|
||||||
/// Play the "eat" sound when Pac-Man consumes a pellet
|
/// Play a specific sound effect
|
||||||
PlayEat,
|
PlaySound(Sound),
|
||||||
/// Play the death sound
|
/// Play the cycling waka sound variant
|
||||||
PlayDeath,
|
Waka,
|
||||||
/// Stop all currently playing sounds
|
/// Stop all currently playing sounds
|
||||||
StopAll,
|
StopAll,
|
||||||
/// Pause all sounds
|
/// Pause all sounds
|
||||||
@@ -61,7 +61,7 @@ pub fn audio_system(
|
|||||||
// Process audio events
|
// Process audio events
|
||||||
for event in audio_events.read() {
|
for event in audio_events.read() {
|
||||||
match event {
|
match event {
|
||||||
AudioEvent::PlayEat => {
|
AudioEvent::Waka => {
|
||||||
if !audio.0.is_disabled() && !audio_state.muted {
|
if !audio.0.is_disabled() && !audio_state.muted {
|
||||||
trace!(sound_index = audio_state.sound_index, "Playing eat sound");
|
trace!(sound_index = audio_state.sound_index, "Playing eat sound");
|
||||||
audio.0.waka();
|
audio.0.waka();
|
||||||
@@ -76,15 +76,15 @@ pub fn audio_system(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AudioEvent::PlayDeath => {
|
AudioEvent::PlaySound(sound) => {
|
||||||
if !audio.0.is_disabled() && !audio_state.muted {
|
if !audio.0.is_disabled() && !audio_state.muted {
|
||||||
trace!("Playing death sound");
|
trace!(?sound, "Playing sound");
|
||||||
audio.0.play(crate::audio::Sound::PacmanDeath);
|
audio.0.play(*sound);
|
||||||
} else {
|
} else {
|
||||||
debug!(
|
debug!(
|
||||||
disabled = audio.0.is_disabled(),
|
disabled = audio.0.is_disabled(),
|
||||||
muted = audio_state.muted,
|
muted = audio_state.muted,
|
||||||
"Skipping death sound due to audio state"
|
"Skipping sound due to audio state"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use bevy_ecs::{
|
|||||||
};
|
};
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
|
use crate::audio::Sound;
|
||||||
use crate::{
|
use crate::{
|
||||||
constants,
|
constants,
|
||||||
systems::{movement::Position, AudioEvent, DyingSequence, FruitSprites, GameStage, Ghost, ScoreResource, SpawnTrigger},
|
systems::{movement::Position, AudioEvent, DyingSequence, FruitSprites, GameStage, Ghost, ScoreResource, SpawnTrigger},
|
||||||
@@ -165,8 +166,8 @@ pub fn ghost_collision_observer(
|
|||||||
ghost_type,
|
ghost_type,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Play eat sound
|
// Play ghost eaten sound
|
||||||
events.write(AudioEvent::PlayEat);
|
events.write(AudioEvent::PlaySound(Sound::Ghost));
|
||||||
} else if matches!(*ghost_state, GhostState::Normal) {
|
} else if matches!(*ghost_state, GhostState::Normal) {
|
||||||
// Pac-Man dies
|
// Pac-Man dies
|
||||||
warn!(ghost = ?ghost_type, "Pacman hit by normal ghost, player 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
|
// Trigger audio if appropriate
|
||||||
if entity_type.is_collectible() {
|
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
|
// Make non-eaten ghosts frightened when power pellet is collected
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ pub struct PlayerAnimation(pub DirectionalAnimation);
|
|||||||
#[derive(Resource, Clone)]
|
#[derive(Resource, Clone)]
|
||||||
pub struct PlayerDeathAnimation(pub LinearAnimation);
|
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.
|
/// A resource to track the overall stage of the game from a high-level perspective.
|
||||||
#[derive(Resource, Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Resource, Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
pub enum GameStage {
|
pub enum GameStage {
|
||||||
@@ -183,6 +187,7 @@ pub fn stage_system(
|
|||||||
player: Single<(Entity, &mut Position), With<PlayerControlled>>,
|
player: Single<(Entity, &mut Position), With<PlayerControlled>>,
|
||||||
mut item_query: Query<(Entity, &EntityType), With<ItemCollider>>,
|
mut item_query: Query<(Entity, &EntityType), With<ItemCollider>>,
|
||||||
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>)>,
|
||||||
|
mut intro_played: ResMut<IntroPlayed>,
|
||||||
) {
|
) {
|
||||||
let old_state = *game_state;
|
let old_state = *game_state;
|
||||||
let mut new_state_opt: Option<GameStage> = None;
|
let mut new_state_opt: Option<GameStage> = None;
|
||||||
@@ -229,6 +234,11 @@ pub fn stage_system(
|
|||||||
}
|
}
|
||||||
GameStage::Starting(sequence) => match sequence {
|
GameStage::Starting(sequence) => match sequence {
|
||||||
StartupSequence::TextOnly { remaining_ticks } => {
|
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 {
|
if remaining_ticks > 0 {
|
||||||
GameStage::Starting(StartupSequence::TextOnly {
|
GameStage::Starting(StartupSequence::TextOnly {
|
||||||
remaining_ticks: remaining_ticks.saturating_sub(1),
|
remaining_ticks: remaining_ticks.saturating_sub(1),
|
||||||
@@ -354,7 +364,7 @@ pub fn stage_system(
|
|||||||
.insert((Dying, player_death_animation.0.clone()));
|
.insert((Dying, player_death_animation.0.clone()));
|
||||||
|
|
||||||
// Play the death sound
|
// Play the death sound
|
||||||
audio_events.write(AudioEvent::PlayDeath);
|
audio_events.write(AudioEvent::PlaySound(crate::audio::Sound::PacmanDeath));
|
||||||
}
|
}
|
||||||
(_, GameStage::PlayerDying(DyingSequence::Hidden { .. })) => {
|
(_, GameStage::PlayerDying(DyingSequence::Hidden { .. })) => {
|
||||||
// Pac-Man's death animation is complete, so he should be hidden just like the ghosts.
|
// 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() {
|
for entity in blinking_query.iter_mut() {
|
||||||
commands.entity(entity).remove::<Frozen>();
|
commands.entity(entity).remove::<Frozen>();
|
||||||
}
|
}
|
||||||
|
// Reset intro flag for the next round
|
||||||
|
intro_played.0 = false;
|
||||||
}
|
}
|
||||||
(_, GameStage::GameOver) => {
|
(_, GameStage::GameOver) => {
|
||||||
// Freeze blinking
|
// Freeze blinking
|
||||||
|
|||||||
Reference in New Issue
Block a user