Added visuals to RoundPhase

This commit is contained in:
Urban Modig
2025-09-01 20:36:54 +02:00
parent be3c4deb3c
commit fcd686fad0
8 changed files with 124 additions and 21 deletions

View File

@ -219,4 +219,8 @@ public class Ghost extends BaseAnimated {
public Point getPosition() {
return new Point((int) position.x, (int) position.y);
}
public void resetModes() {
mode = GhostMode.CHASE;
}
}

View File

@ -109,6 +109,11 @@ public class PacMan extends BaseAnimated {
position = startPosition;
}
public void reset() {
resetPosition();
aniIndex = 0; // reset animation to start
}
public Image getLifeIcon() {
return spriteSheets[0][1];
}

View File

@ -253,7 +253,7 @@ public class GameMap {
public long numberOfDots() {
//return this.numberOfDots;
return 10;
return 50;
}
public void reset() {

View File

@ -19,7 +19,7 @@ public class GameStateManager {
setState(GameStateType.PLAYING);
}
private void setState(GameStateType type) {
public void setState(GameStateType type) {
currentState = states.get(type);
}

View File

@ -1,5 +1,5 @@
package se.urmo.game.state;
public enum GameStateType {
PLAYING,
PLAYING, GAME_OVER,
}

View File

@ -22,7 +22,6 @@ import java.util.List;
@Slf4j
public class GhostManager {
public static final int SPRITE_SHEET_ROWS = 10;
public static final int MAX_SPRITE_FRAMES = 4;
public static final int BLINKY_ANIMATION = 0;
public static final int PINKY_ANIMATION = 2;
@ -90,10 +89,12 @@ public class GhostManager {
}
public void setFrightMode() {
this.setMode(GhostMode.FRIGHTENED);
setMode(GhostMode.FRIGHTENED);
}
public void reset() {
phaseIndex = 0;
setMode(GhostMode.SCATTER);
ghosts.forEach(Ghost::resetPosition);
}
}

View File

@ -19,20 +19,35 @@ import java.io.InputStream;
@Slf4j
public class PlayingState implements GameState {
public static final int REMAINING_LIVES = 0;
private final Game game;
private final GameStateManager gameStateManager;
private final GhostManager ghostManager;
private final Font arcadeFont;
// Core components
private final GhostManager ghostManager;
private final FruitManager fruitManager;
private final LevelManager levelManager;
private final AnimationManager animationManager;
private PacMan pacman;
@Getter
private GameMap map;
private int score;
// Durations (tune to taste)
private static final int READY_MS = 1500;
private static final int LEVEL_COMPLETE_MS= 1500;
private static final int LIFE_LOST_MS = 2000;
// Score/Lives
private int score = 0;
private int lives = 3;
private int dotsEaten = 0;
// Phase + timers
private RoundPhase phase = RoundPhase.PLAYING;
private long phaseStartMs = System.currentTimeMillis();
private boolean deathInProgress;
public PlayingState(Game game, GameStateManager gameStateManager) {
this.game = game;
this.gameStateManager = gameStateManager;
@ -48,12 +63,59 @@ public class PlayingState implements GameState {
@Override
public void update() {
animationManager.updateAll();
pacman.update();
ghostManager.update(pacman, map);
fruitManager.update(pacman, this);
checkCollisions();
handleDots();
switch (phase) {
case READY -> {
// Freeze everything during READY
if (phaseElapsed() >= READY_MS) {
setPhase(RoundPhase.PLAYING);
}
}
case PLAYING -> {
animationManager.updateAll();
pacman.update();
ghostManager.update(pacman, map);
fruitManager.update(pacman, this);
checkCollisions();
handleDots();
}
case LEVEL_COMPLETE -> {
// Freeze, then advance level
if (phaseElapsed() >= LEVEL_COMPLETE_MS) {
advanceLevel();
setPhase(RoundPhase.READY);
}
}
case LIFE_LOST -> {
// Freeze, then reset round (keep dot state)
if (phaseElapsed() >= LIFE_LOST_MS) {
resetAfterLifeLost();
setPhase(RoundPhase.READY);
}
}
}
}
private void resetAfterLifeLost() {
pacman.reset(); // to start tile, direction stopped
ghostManager.reset(); // to house
}
private void advanceLevel() {
levelManager.nextLevel();
map.reset();
ghostManager.reset();
fruitManager.reset();
pacman.reset();
dotsEaten = 0;
}
private void setPhase(RoundPhase next) {
phase = next;
phaseStartMs = System.currentTimeMillis();
}
private long phaseElapsed() {
return System.currentTimeMillis() - phaseStartMs;
}
private void handleDots() {
@ -68,12 +130,7 @@ public class PlayingState implements GameState {
fruitManager.dotEaten(dotsEaten);
score+=tile.getTileType().getScore();
if(dotsEaten == map.numberOfDots()){
levelManager.nextLevel();
map.reset();
ghostManager.reset();
fruitManager.reset();
pacman.resetPosition();
dotsEaten = 0;
setPhase(RoundPhase.LEVEL_COMPLETE);
}
}
}
@ -85,6 +142,23 @@ public class PlayingState implements GameState {
ghostManager.draw(g);
fruitManager.draw(g);
drawUI(g);
// Phase overlays
switch (phase) {
case READY -> drawCenterText(g, "READY!");
case LEVEL_COMPLETE -> drawCenterText(g, "LEVEL COMPLETE!");
case LIFE_LOST -> drawCenterText(g, "LIFE LOST");
default -> { /* no overlay */ }
}
}
private void drawCenterText(Graphics2D g, String text) {
g.setFont(arcadeFont.deriveFont(18F));
g.setColor(Color.YELLOW);
var fm = g.getFontMetrics();
int cx = GameMap.OFFSET_X + map.columns() * GameMap.MAP_TILESIZE/2;
int cy = GameMap.OFFSET_Y + map.rows() * GameMap.MAP_TILESIZE/2;
g.drawString(text, cx - fm.stringWidth(text)/2, cy);
}
private void drawUI(Graphics2D g) {
@ -106,6 +180,7 @@ public class PlayingState implements GameState {
@Override
public void keyPressed(KeyEvent e) {
if (phase != RoundPhase.PLAYING) return;
switch (e.getKeyCode()) {
case KeyEvent.VK_W -> move(Direction.UP);
case KeyEvent.VK_S -> move(Direction.DOWN);
@ -126,6 +201,7 @@ public class PlayingState implements GameState {
private void checkCollisions() {
for (Ghost ghost : ghostManager.getGhosts()) {
if (deathInProgress) return; // guard
double dist = pacman.distanceTo(ghost.getPosition());
if (dist < GameMap.MAP_TILESIZE / 2.0) {
if (ghost.isFrightened()) {
@ -134,15 +210,23 @@ public class PlayingState implements GameState {
ghost.resetPosition();
ghost.setMode(GhostMode.CHASE); // end frightend
} else {
deathInProgress = true;
// Pac-Man loses a life
lives--;
if(lives == 0)System.exit(1);
else pacman.resetPosition();
if(lives >= REMAINING_LIVES)
setPhase(RoundPhase.LIFE_LOST);
else
endGame();
}
}
}
}
private void endGame() {
setPhase(RoundPhase.GAME_OVER);
gameStateManager.setState(GameStateType.GAME_OVER);
}
private Font loadArcadeFont() {
try (InputStream is = getClass().getResourceAsStream("/fonts/PressStart2P-Regular.ttf")) {
return Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(16f);

View File

@ -0,0 +1,9 @@
package se.urmo.game.state;
public enum RoundPhase {
READY, // "READY!" shown briefly before play starts
PLAYING, // normal gameplay
LEVEL_COMPLETE, // all dots eaten; freeze briefly then advance level
LIFE_LOST, // Pac-Man hit; freeze briefly then reset positions
GAME_OVER
}