diff --git a/src/main/java/se/urmo/game/sound/SoundEffect.java b/src/main/java/se/urmo/game/sound/SoundEffect.java new file mode 100644 index 0000000..af9d509 --- /dev/null +++ b/src/main/java/se/urmo/game/sound/SoundEffect.java @@ -0,0 +1,5 @@ +package se.urmo.game.sound; + +public enum SoundEffect { + START, SIREN, MUNCH1, MUNCH2, FRUIT, GHOST_EATEN, EXTRA_LIFE, DEATH +} diff --git a/src/main/java/se/urmo/game/sound/SoundManager.java b/src/main/java/se/urmo/game/sound/SoundManager.java new file mode 100644 index 0000000..f1adc78 --- /dev/null +++ b/src/main/java/se/urmo/game/sound/SoundManager.java @@ -0,0 +1,62 @@ +package se.urmo.game.sound; + +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import java.util.EnumMap; +import java.util.Map; + +public class SoundManager { + private final Map clips = new EnumMap<>(SoundEffect.class); + private SoundEffect lastMunch = SoundEffect.MUNCH1; + + public SoundManager() { + load(SoundEffect.START, "/sounds/start.wav"); + load(SoundEffect.SIREN, "/sounds/siren0.wav"); + load(SoundEffect.MUNCH1, "/sounds/eat_dot_0_fast.wav"); + load(SoundEffect.MUNCH2, "/sounds/eat_dot_1_fast.wav"); + load(SoundEffect.FRUIT, "/sounds/eat_fruit.wav"); + load(SoundEffect.GHOST_EATEN, "/sounds/eat_ghost.wav"); + load(SoundEffect.EXTRA_LIFE, "/sounds/extend.wav"); + load(SoundEffect.DEATH, "/sounds/death_0.wav"); + } + + private void load(SoundEffect id, String path) { + try (AudioInputStream ais = AudioSystem.getAudioInputStream(getClass().getResource(path))) { + Clip clip = AudioSystem.getClip(); + clip.open(ais); + clips.put(id, clip); + } catch (Exception e) { + throw new RuntimeException("Failed to load sound: " + path, e); + } + } + + public void play(SoundEffect id) { + Clip clip = clips.get(id); + if (clip == null) return; + if (clip.isRunning()) clip.stop(); + clip.setFramePosition(0); + clip.start(); + } + + public void loop(SoundEffect id) { + Clip clip = clips.get(id); + if (clip == null) return; + if (!clip.isRunning()) { + clip.setFramePosition(0); + clip.loop(Clip.LOOP_CONTINUOUSLY); + } + } + + public void stop(SoundEffect id) { + Clip clip = clips.get(id); + if (clip != null && clip.isRunning()) clip.stop(); + } + + // For dot munch alternation + public void playMunch() { + lastMunch = (lastMunch == SoundEffect.MUNCH1 ? SoundEffect.MUNCH2 : SoundEffect.MUNCH1); + play(lastMunch); + } +} + diff --git a/src/main/java/se/urmo/game/state/PlayingState.java b/src/main/java/se/urmo/game/state/PlayingState.java index 72babeb..300dc82 100644 --- a/src/main/java/se/urmo/game/state/PlayingState.java +++ b/src/main/java/se/urmo/game/state/PlayingState.java @@ -16,6 +16,8 @@ import se.urmo.game.main.ScorePopupManager; import se.urmo.game.map.GameMap; import se.urmo.game.map.MapTile; import se.urmo.game.map.TileType; +import se.urmo.game.sound.SoundEffect; +import se.urmo.game.sound.SoundManager; import se.urmo.game.util.Direction; import se.urmo.game.util.GameFonts; import se.urmo.game.util.GameStateType; @@ -36,12 +38,12 @@ public class PlayingState implements GameState { private final FruitManager fruitManager; private final LevelManager levelManager; private final AnimationManager animationManager; - private PacMan pacman; + private final PacMan pacman; @Getter - private GameMap map; + private final GameMap map; // Durations (tune to taste) - private static final int READY_MS = 1500; + private static final int READY_MS = 3000; private static final int LEVEL_COMPLETE_MS = 1500; private static final int LIFE_LOST_MS = 2000; @@ -51,13 +53,14 @@ public class PlayingState implements GameState { private int dotsEaten = 0; // Phase + timers - private RoundPhase phase = RoundPhase.PLAYING; + private RoundPhase phase = RoundPhase.READY; private long phaseStartMs = System.currentTimeMillis(); private boolean deathInProgress; - private int frightMultiplier; private final ScorePopupManager scorePopups = new ScorePopupManager(); - private final Font scorePopupFont = GameFonts.arcade(16F); // or your arcade font + private final Font scorePopupFont = GameFonts.arcade(16F); + + private final SoundManager sound = new SoundManager(); public PlayingState(GameStateManager gameStateManager, GameOverState gameOverState) { this.gameStateManager = gameStateManager; @@ -69,6 +72,7 @@ public class PlayingState implements GameState { this.animationManager.register(pacman); this.ghostManager = new GhostManager(new GhostCollisionChecker(map), animationManager, levelManager); this.fruitManager = new FruitManager(levelManager); + sound.play(SoundEffect.START); } @Override @@ -82,6 +86,7 @@ public class PlayingState implements GameState { } } case PLAYING -> { + sound.loop(SoundEffect.SIREN); animationManager.updateAll(); pacman.update(); ghostManager.update(pacman, map); @@ -137,9 +142,9 @@ public class PlayingState implements GameState { boolean wasRemoved = map.removeTileImage(pacmanScreenPos); if (wasRemoved && tile.getTileType() == TileType.LARGE_PELLET) { ghostManager.setFrightMode(); - frightMultiplier = 1; } if (wasRemoved) { + sound.playMunch(); dotsEaten++; fruitManager.dotEaten(dotsEaten); score += tile.getTileType().getScore(); @@ -228,12 +233,14 @@ public class PlayingState implements GameState { if (ghost.isEaten()) return; if (ghost.isFrightened()) { log.debug("Pacman eats ghost"); + sound.play(SoundEffect.GHOST_EATEN); int pts = 200 * (1 << (ghostManager.getGhosts().size() - ghostManager.isFrightened())); score += pts; scorePopups.spawn(pts); ghost.setMode(GhostMode.EATEN); } else { log.debug("Pacman loses a life"); + sound.play(SoundEffect.DEATH); ghostManager.setFrozen(true); pacman.setState(PacMan.PacmanState.DYING); deathInProgress = true; diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index b58186e..b383597 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -5,7 +5,14 @@ - + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/sounds/credit.wav b/src/main/resources/sounds/credit.wav new file mode 100644 index 0000000..38d46bd Binary files /dev/null and b/src/main/resources/sounds/credit.wav differ diff --git a/src/main/resources/sounds/death_0.wav b/src/main/resources/sounds/death_0.wav new file mode 100644 index 0000000..7fdd9b3 Binary files /dev/null and b/src/main/resources/sounds/death_0.wav differ diff --git a/src/main/resources/sounds/death_1.wav b/src/main/resources/sounds/death_1.wav new file mode 100644 index 0000000..228c080 Binary files /dev/null and b/src/main/resources/sounds/death_1.wav differ diff --git a/src/main/resources/sounds/eat_dot_0.wav b/src/main/resources/sounds/eat_dot_0.wav new file mode 100644 index 0000000..305ce53 Binary files /dev/null and b/src/main/resources/sounds/eat_dot_0.wav differ diff --git a/src/main/resources/sounds/eat_dot_0_fast.wav b/src/main/resources/sounds/eat_dot_0_fast.wav new file mode 100644 index 0000000..e52fd32 Binary files /dev/null and b/src/main/resources/sounds/eat_dot_0_fast.wav differ diff --git a/src/main/resources/sounds/eat_dot_1.wav b/src/main/resources/sounds/eat_dot_1.wav new file mode 100644 index 0000000..3aa4937 Binary files /dev/null and b/src/main/resources/sounds/eat_dot_1.wav differ diff --git a/src/main/resources/sounds/eat_dot_1_fast.wav b/src/main/resources/sounds/eat_dot_1_fast.wav new file mode 100644 index 0000000..1f3ef21 Binary files /dev/null and b/src/main/resources/sounds/eat_dot_1_fast.wav differ diff --git a/src/main/resources/sounds/eat_fruit.wav b/src/main/resources/sounds/eat_fruit.wav new file mode 100644 index 0000000..fe3035f Binary files /dev/null and b/src/main/resources/sounds/eat_fruit.wav differ diff --git a/src/main/resources/sounds/eat_ghost.wav b/src/main/resources/sounds/eat_ghost.wav new file mode 100644 index 0000000..e2040ad Binary files /dev/null and b/src/main/resources/sounds/eat_ghost.wav differ diff --git a/src/main/resources/sounds/extend.wav b/src/main/resources/sounds/extend.wav new file mode 100644 index 0000000..9cf27aa Binary files /dev/null and b/src/main/resources/sounds/extend.wav differ diff --git a/src/main/resources/sounds/eyes.wav b/src/main/resources/sounds/eyes.wav new file mode 100644 index 0000000..f918b54 Binary files /dev/null and b/src/main/resources/sounds/eyes.wav differ diff --git a/src/main/resources/sounds/eyes_firstloop.wav b/src/main/resources/sounds/eyes_firstloop.wav new file mode 100644 index 0000000..e856922 Binary files /dev/null and b/src/main/resources/sounds/eyes_firstloop.wav differ diff --git a/src/main/resources/sounds/fright.wav b/src/main/resources/sounds/fright.wav new file mode 100644 index 0000000..45fde41 Binary files /dev/null and b/src/main/resources/sounds/fright.wav differ diff --git a/src/main/resources/sounds/fright_firstloop.wav b/src/main/resources/sounds/fright_firstloop.wav new file mode 100644 index 0000000..bb7a59a Binary files /dev/null and b/src/main/resources/sounds/fright_firstloop.wav differ diff --git a/src/main/resources/sounds/intermission.wav b/src/main/resources/sounds/intermission.wav new file mode 100644 index 0000000..a8028ae Binary files /dev/null and b/src/main/resources/sounds/intermission.wav differ diff --git a/src/main/resources/sounds/siren0.wav b/src/main/resources/sounds/siren0.wav new file mode 100644 index 0000000..2ce8173 Binary files /dev/null and b/src/main/resources/sounds/siren0.wav differ diff --git a/src/main/resources/sounds/siren0_firstloop.wav b/src/main/resources/sounds/siren0_firstloop.wav new file mode 100644 index 0000000..1be885a Binary files /dev/null and b/src/main/resources/sounds/siren0_firstloop.wav differ diff --git a/src/main/resources/sounds/siren1.wav b/src/main/resources/sounds/siren1.wav new file mode 100644 index 0000000..926ce9b Binary files /dev/null and b/src/main/resources/sounds/siren1.wav differ diff --git a/src/main/resources/sounds/siren1_firstloop.wav b/src/main/resources/sounds/siren1_firstloop.wav new file mode 100644 index 0000000..3ff19ee Binary files /dev/null and b/src/main/resources/sounds/siren1_firstloop.wav differ diff --git a/src/main/resources/sounds/siren2.wav b/src/main/resources/sounds/siren2.wav new file mode 100644 index 0000000..2e7f933 Binary files /dev/null and b/src/main/resources/sounds/siren2.wav differ diff --git a/src/main/resources/sounds/siren2_firstloop.wav b/src/main/resources/sounds/siren2_firstloop.wav new file mode 100644 index 0000000..36e61bd Binary files /dev/null and b/src/main/resources/sounds/siren2_firstloop.wav differ diff --git a/src/main/resources/sounds/siren3.wav b/src/main/resources/sounds/siren3.wav new file mode 100644 index 0000000..f918c12 Binary files /dev/null and b/src/main/resources/sounds/siren3.wav differ diff --git a/src/main/resources/sounds/siren3_firstloop.wav b/src/main/resources/sounds/siren3_firstloop.wav new file mode 100644 index 0000000..80224b9 Binary files /dev/null and b/src/main/resources/sounds/siren3_firstloop.wav differ diff --git a/src/main/resources/sounds/siren4.wav b/src/main/resources/sounds/siren4.wav new file mode 100644 index 0000000..3db307d Binary files /dev/null and b/src/main/resources/sounds/siren4.wav differ diff --git a/src/main/resources/sounds/siren4_firstloop.wav b/src/main/resources/sounds/siren4_firstloop.wav new file mode 100644 index 0000000..5ef2af3 Binary files /dev/null and b/src/main/resources/sounds/siren4_firstloop.wav differ diff --git a/src/main/resources/sounds/start.wav b/src/main/resources/sounds/start.wav new file mode 100644 index 0000000..b222b54 Binary files /dev/null and b/src/main/resources/sounds/start.wav differ