Compare commits

...

4 Commits

Author SHA1 Message Date
5b2626ad5d Added last pellets 2025-09-07 17:28:47 +02:00
c65787dfc9 Implemented sound - first rev. 2025-09-07 17:26:09 +02:00
fd85d8cd0f Added TUNNEL TileType 2025-09-07 12:15:29 +02:00
32e884ec36 minor fix 2025-09-06 21:57:25 +02:00
33 changed files with 97 additions and 11 deletions

View File

@ -49,8 +49,12 @@ public final class ScorePopup {
int x = sx - fm.stringWidth(text) / 2; int x = sx - fm.stringWidth(text) / 2;
int baseline = sy; // tune if you want it a tad above the exact point int baseline = sy; // tune if you want it a tad above the exact point
// g.setColor(Color.WHITE);
g.setColor(Color.BLACK);
g.drawString(text, x + 1, baseline + 1);
g.setColor(Color.WHITE); g.setColor(Color.WHITE);
g.drawString(text, x, baseline); g.drawString(text, x, baseline);
g.drawString(text, x, baseline);
g.setComposite(oldComp); g.setComposite(oldComp);
g.setFont(oldFont); g.setFont(oldFont);

View File

@ -62,6 +62,7 @@ public enum TileType {
TILE_54(54, true, SpriteLocation.MAP, 4, 9, false ,0), TILE_54(54, true, SpriteLocation.MAP, 4, 9, false ,0),
TILE_55(55, true, SpriteLocation.MAP, 4, 10, false ,0), TILE_55(55, true, SpriteLocation.MAP, 4, 10, false ,0),
LARGE_PELLET(56, false, SpriteLocation.ITEM, 1 ,1,true, 50), LARGE_PELLET(56, false, SpriteLocation.ITEM, 1 ,1,true, 50),
TUNNEL(57, false, SpriteLocation.ITEM, 1, 0, true, 10),
EMPTY(99, false, SpriteLocation.NONE, 0, 0, false, 0); // No sprite associated with empty tiles EMPTY(99, false, SpriteLocation.NONE, 0, 0, false, 0); // No sprite associated with empty tiles
private final int value; private final int value;

View File

@ -0,0 +1,5 @@
package se.urmo.game.sound;
public enum SoundEffect {
START, SIREN, MUNCH1, MUNCH2, FRUIT, GHOST_EATEN, EXTRA_LIFE, DEATH
}

View File

@ -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<SoundEffect, Clip> 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);
}
}

View File

@ -16,6 +16,8 @@ import se.urmo.game.main.ScorePopupManager;
import se.urmo.game.map.GameMap; import se.urmo.game.map.GameMap;
import se.urmo.game.map.MapTile; import se.urmo.game.map.MapTile;
import se.urmo.game.map.TileType; 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.Direction;
import se.urmo.game.util.GameFonts; import se.urmo.game.util.GameFonts;
import se.urmo.game.util.GameStateType; import se.urmo.game.util.GameStateType;
@ -36,12 +38,12 @@ public class PlayingState implements GameState {
private final FruitManager fruitManager; private final FruitManager fruitManager;
private final LevelManager levelManager; private final LevelManager levelManager;
private final AnimationManager animationManager; private final AnimationManager animationManager;
private PacMan pacman; private final PacMan pacman;
@Getter @Getter
private GameMap map; private final GameMap map;
// Durations (tune to taste) // 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 LEVEL_COMPLETE_MS = 1500;
private static final int LIFE_LOST_MS = 2000; private static final int LIFE_LOST_MS = 2000;
@ -51,13 +53,14 @@ public class PlayingState implements GameState {
private int dotsEaten = 0; private int dotsEaten = 0;
// Phase + timers // Phase + timers
private RoundPhase phase = RoundPhase.PLAYING; private RoundPhase phase = RoundPhase.READY;
private long phaseStartMs = System.currentTimeMillis(); private long phaseStartMs = System.currentTimeMillis();
private boolean deathInProgress; private boolean deathInProgress;
private int frightMultiplier;
private final ScorePopupManager scorePopups = new ScorePopupManager(); 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) { public PlayingState(GameStateManager gameStateManager, GameOverState gameOverState) {
this.gameStateManager = gameStateManager; this.gameStateManager = gameStateManager;
@ -69,6 +72,7 @@ public class PlayingState implements GameState {
this.animationManager.register(pacman); this.animationManager.register(pacman);
this.ghostManager = new GhostManager(new GhostCollisionChecker(map), animationManager, levelManager); this.ghostManager = new GhostManager(new GhostCollisionChecker(map), animationManager, levelManager);
this.fruitManager = new FruitManager(levelManager); this.fruitManager = new FruitManager(levelManager);
sound.play(SoundEffect.START);
} }
@Override @Override
@ -82,6 +86,7 @@ public class PlayingState implements GameState {
} }
} }
case PLAYING -> { case PLAYING -> {
sound.loop(SoundEffect.SIREN);
animationManager.updateAll(); animationManager.updateAll();
pacman.update(); pacman.update();
ghostManager.update(pacman, map); ghostManager.update(pacman, map);
@ -137,9 +142,9 @@ public class PlayingState implements GameState {
boolean wasRemoved = map.removeTileImage(pacmanScreenPos); boolean wasRemoved = map.removeTileImage(pacmanScreenPos);
if (wasRemoved && tile.getTileType() == TileType.LARGE_PELLET) { if (wasRemoved && tile.getTileType() == TileType.LARGE_PELLET) {
ghostManager.setFrightMode(); ghostManager.setFrightMode();
frightMultiplier = 1;
} }
if (wasRemoved) { if (wasRemoved) {
sound.playMunch();
dotsEaten++; dotsEaten++;
fruitManager.dotEaten(dotsEaten); fruitManager.dotEaten(dotsEaten);
score += tile.getTileType().getScore(); score += tile.getTileType().getScore();
@ -228,12 +233,14 @@ public class PlayingState implements GameState {
if (ghost.isEaten()) return; if (ghost.isEaten()) return;
if (ghost.isFrightened()) { if (ghost.isFrightened()) {
log.debug("Pacman eats ghost"); log.debug("Pacman eats ghost");
sound.play(SoundEffect.GHOST_EATEN);
int pts = 200 * (1 << (ghostManager.getGhosts().size() - ghostManager.isFrightened())); int pts = 200 * (1 << (ghostManager.getGhosts().size() - ghostManager.isFrightened()));
score += pts; score += pts;
scorePopups.spawn(pts); scorePopups.spawn(pts);
ghost.setMode(GhostMode.EATEN); ghost.setMode(GhostMode.EATEN);
} else { } else {
log.debug("Pacman loses a life"); log.debug("Pacman loses a life");
sound.play(SoundEffect.DEATH);
ghostManager.setFrozen(true); ghostManager.setFrozen(true);
pacman.setState(PacMan.PacmanState.DYING); pacman.setState(PacMan.PacmanState.DYING);
deathInProgress = true; deathInProgress = true;

View File

@ -5,7 +5,14 @@
</encoder> </encoder>
</appender> </appender>
<root level="DEBUG"> <!--root level="DEBUG">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</root> </root -->
<root level="OFF"/>
<!-- Enable entire package -->
<logger name="se.urmo.myapp" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
</configuration> </configuration>

View File

@ -11,7 +11,7 @@
99,99,99,99,99,22, 0,15,17,99,99,99,99,99,99,99,99,99,99,15,17, 0,20,99,99,99,99,99 99,99,99,99,99,22, 0,15,17,99,99,99,99,99,99,99,99,99,99,15,17, 0,20,99,99,99,99,99
99,99,99,99,99,22, 0,15,17,99,34,35,36,37,38,35,35,39,99,15,17, 0,20,99,99,99,99,99 99,99,99,99,99,22, 0,15,17,99,34,35,36,37,38,35,35,39,99,15,17, 0,20,99,99,99,99,99
32,32,32,32,32,33, 0,26,28,99,40,99,99,99,99,99,99,41,99,26,28, 0,31,32,32,32,32,32 32,32,32,32,32,33, 0,26,28,99,40,99,99,99,99,99,99,41,99,26,28, 0,31,32,32,32,32,32
0, 0, 0, 0, 0, 0, 0, 0, 0,99,40,99,99,99,99,99,99,41,99, 0, 0, 0, 0, 0, 0, 0, 0, 0 57,57,57,57,57,57, 0, 0, 0,99,40,99,99,99,99,99,99,41,99, 0, 0,57,57,57,57,57,57,57
10,10,10,10,10,11, 0, 4, 6,99,40,99,99,99,99,99,99,41,99, 4, 6, 0, 9,10,10,10,10,10 10,10,10,10,10,11, 0, 4, 6,99,40,99,99,99,99,99,99,41,99, 4, 6, 0, 9,10,10,10,10,10
99,99,99,99,99,22, 0,15,17,99,42,43,43,43,43,43,43,44,99,15,17, 0,20,99,99,99,99,99 99,99,99,99,99,22, 0,15,17,99,42,43,43,43,43,43,43,44,99,15,17, 0,20,99,99,99,99,99
99,99,99,99,99,22, 0,15,17,99,99,99,99,99,99,99,99,99,99,15,17, 0,20,99,99,99,99,99 99,99,99,99,99,22, 0,15,17,99,99,99,99,99,99,99,99,99,99,15,17, 0,20,99,99,99,99,99
@ -25,6 +25,6 @@
49,27,28, 0,26,28, 0,15,17, 0,26,27,27, 7, 8,27,27,28, 0,15,17, 0,26,28, 0,26,27,50 49,27,28, 0,26,28, 0,15,17, 0,26,27,27, 7, 8,27,27,28, 0,15,17, 0,26,28, 0,26,27,50
12, 0, 0, 0, 0, 0, 0,15,17, 0, 0, 0, 0,15,17, 0, 0, 0, 0,15,17, 0, 0, 0, 0, 0, 0,14 12, 0, 0, 0, 0, 0, 0,15,17, 0, 0, 0, 0,15,17, 0, 0, 0, 0,15,17, 0, 0, 0, 0, 0, 0,14
12, 0, 4, 5, 5, 5, 5,18,19, 5, 5, 6, 0,15,17, 0, 4, 5, 5,18,19, 5, 5, 5, 5, 6, 0,14 12, 0, 4, 5, 5, 5, 5,18,19, 5, 5, 6, 0,15,17, 0, 4, 5, 5,18,19, 5, 5, 5, 5, 6, 0,14
12, 0,26,27,27,27,27,27,27,27,27,28, 0,26,28, 0,26,27,27,27,27,27,27,27,27,28, 0,14 12,56,26,27,27,27,27,27,27,27,27,28, 0,26,28, 0,26,27,27,27,27,27,27,27,27,28,56,14
12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,14 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,14
23,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,25 23,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,25
1 1 2 2 2 2 2 2 2 2 2 2 2 2 45 46 2 2 2 2 2 2 2 2 2 2 2 2 3
11 99 99 99 99 99 22 0 15 17 99 99 99 99 99 99 99 99 99 99 15 17 0 20 99 99 99 99 99
12 99 99 99 99 99 22 0 15 17 99 34 35 36 37 38 35 35 39 99 15 17 0 20 99 99 99 99 99
13 32 32 32 32 32 33 0 26 28 99 40 99 99 99 99 99 99 41 99 26 28 0 31 32 32 32 32 32
14 0 57 0 57 0 57 0 57 0 57 0 57 0 0 0 99 40 99 99 99 99 99 99 41 99 0 0 0 57 0 57 0 57 0 57 0 57 0 57 0 57
15 10 10 10 10 10 11 0 4 6 99 40 99 99 99 99 99 99 41 99 4 6 0 9 10 10 10 10 10
16 99 99 99 99 99 22 0 15 17 99 42 43 43 43 43 43 43 44 99 15 17 0 20 99 99 99 99 99
17 99 99 99 99 99 22 0 15 17 99 99 99 99 99 99 99 99 99 99 15 17 0 20 99 99 99 99 99
25 49 27 28 0 26 28 0 15 17 0 26 27 27 7 8 27 27 28 0 15 17 0 26 28 0 26 27 50
26 12 0 0 0 0 0 0 15 17 0 0 0 0 15 17 0 0 0 0 15 17 0 0 0 0 0 0 14
27 12 0 4 5 5 5 5 18 19 5 5 6 0 15 17 0 4 5 5 18 19 5 5 5 5 6 0 14
28 12 0 56 26 27 27 27 27 27 27 27 27 28 0 26 28 0 26 27 27 27 27 27 27 27 27 28 0 56 14
29 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 14
30 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.