GameOverState WIP
This commit is contained in:
94
src/main/java/se/urmo/game/state/GameOverState.java
Normal file
94
src/main/java/se/urmo/game/state/GameOverState.java
Normal file
@ -0,0 +1,94 @@
|
||||
package se.urmo.game.state;
|
||||
|
||||
import se.urmo.game.main.GamePanel;
|
||||
import se.urmo.game.util.GameFonts;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
public class GameOverState implements GameState {
|
||||
private final GameStateManager gsm;
|
||||
private int finalScore = 0;
|
||||
private int finalLevel = 0;
|
||||
private final HighScoreManager highScores; // optional
|
||||
|
||||
private boolean saved = false;
|
||||
private long startMs = System.currentTimeMillis();
|
||||
|
||||
public GameOverState(GameStateManager gsm, HighScoreManager highScores) {
|
||||
this.gsm = gsm;
|
||||
this.highScores = highScores;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
if (!saved) {
|
||||
if (highScores != null) highScores.submit(finalScore);
|
||||
saved = true;
|
||||
}
|
||||
// could auto-return to menu after N seconds if you like
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Graphics2D g) {
|
||||
// Clear / dim background
|
||||
g.setColor(new Color(0,0,0,200));
|
||||
g.fillRect(0,0, GamePanel.SCREEN_WIDTH, GamePanel.SCREEN_HEIGHT);
|
||||
|
||||
// Title
|
||||
g.setColor(Color.RED);
|
||||
g.setFont(GameFonts.arcade(32f));
|
||||
drawCentered(g, "GAME OVER", GamePanel.SCREEN_HEIGHT/2 - 40);
|
||||
|
||||
// Stats
|
||||
g.setColor(Color.WHITE);
|
||||
g.setFont(GameFonts.arcade(18f));
|
||||
drawCentered(g, "SCORE: " + finalScore, GamePanel.SCREEN_HEIGHT/2);
|
||||
drawCentered(g, "LEVEL: " + finalLevel, GamePanel.SCREEN_HEIGHT/2 + 24);
|
||||
|
||||
// Prompt
|
||||
g.setFont(GameFonts.arcade(12f));
|
||||
drawCentered(g, "Press..", GamePanel.SCREEN_HEIGHT/2 + 64);
|
||||
drawCentered(g, "ENTER to restart • ESC for menu", GamePanel.SCREEN_HEIGHT/2 + 78);
|
||||
}
|
||||
|
||||
private void drawCentered(Graphics2D g, String text, int y) {
|
||||
var fm = g.getFontMetrics();
|
||||
int x = (GamePanel.SCREEN_WIDTH - fm.stringWidth(text)) / 2;
|
||||
g.drawString(text, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_ENTER -> restartNewGame();
|
||||
case KeyEvent.VK_ESCAPE -> goToMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
|
||||
}
|
||||
|
||||
private void restartNewGame() {
|
||||
// Build a fresh PlayingState with everything reset
|
||||
gsm.setState(GameStateType.PLAYING);
|
||||
}
|
||||
|
||||
private void goToMenu() {
|
||||
// MenuState menu = new MenuState(gsm);
|
||||
// gsm.register(StateId.MENU, menu);
|
||||
// gsm.set(StateId.MENU);
|
||||
}
|
||||
|
||||
public void setScore(int score) {
|
||||
this.finalScore = score;
|
||||
}
|
||||
|
||||
public void setLevel(int level) {
|
||||
this.finalLevel = level;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,8 +15,10 @@ public class GameStateManager {
|
||||
|
||||
public GameStateManager(Game game) {
|
||||
this.game = game;
|
||||
states.put(GameStateType.PLAYING, new PlayingState(game, this));
|
||||
setState(GameStateType.PLAYING);
|
||||
GameOverState gameOverState = new GameOverState(this, new HighScoreManager());
|
||||
states.put(GameStateType.PLAYING, new PlayingState(game, this, gameOverState));
|
||||
states.put(GameStateType.GAME_OVER, gameOverState);
|
||||
setState(GameStateType.GAME_OVER);
|
||||
}
|
||||
|
||||
public void setState(GameStateType type) {
|
||||
|
||||
7
src/main/java/se/urmo/game/state/HighScoreManager.java
Normal file
7
src/main/java/se/urmo/game/state/HighScoreManager.java
Normal file
@ -0,0 +1,7 @@
|
||||
package se.urmo.game.state;
|
||||
|
||||
public class HighScoreManager {
|
||||
public void submit(int finalScore) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -12,17 +12,17 @@ import se.urmo.game.map.GameMap;
|
||||
import se.urmo.game.map.MapTile;
|
||||
import se.urmo.game.map.TileType;
|
||||
import se.urmo.game.util.Direction;
|
||||
import se.urmo.game.util.GameFonts;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
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 Font arcadeFont;
|
||||
private final GameOverState gameOverState;
|
||||
|
||||
// Core components
|
||||
private final GhostManager ghostManager;
|
||||
@ -34,9 +34,9 @@ public class PlayingState implements GameState {
|
||||
private GameMap map;
|
||||
|
||||
// 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;
|
||||
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;
|
||||
@ -48,9 +48,10 @@ public class PlayingState implements GameState {
|
||||
private long phaseStartMs = System.currentTimeMillis();
|
||||
private boolean deathInProgress;
|
||||
|
||||
public PlayingState(Game game, GameStateManager gameStateManager) {
|
||||
public PlayingState(Game game, GameStateManager gameStateManager, GameOverState gameOverState) {
|
||||
this.game = game;
|
||||
this.gameStateManager = gameStateManager;
|
||||
this.gameOverState = gameOverState;
|
||||
this.map = new GameMap("maps/map1.csv");
|
||||
this.animationManager = new AnimationManager();
|
||||
this.levelManager = new LevelManager();
|
||||
@ -58,7 +59,6 @@ public class PlayingState implements GameState {
|
||||
this.animationManager.register(pacman);
|
||||
this.ghostManager = new GhostManager(new GhostCollisionChecker(map), animationManager, levelManager);
|
||||
this.fruitManager = new FruitManager(levelManager);
|
||||
this.arcadeFont = loadArcadeFont();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -88,16 +88,20 @@ public class PlayingState implements GameState {
|
||||
case LIFE_LOST -> {
|
||||
// Freeze, then reset round (keep dot state)
|
||||
if (phaseElapsed() >= LIFE_LOST_MS) {
|
||||
deathInProgress = false;
|
||||
resetAfterLifeLost();
|
||||
setPhase(RoundPhase.READY);
|
||||
if (lives <= 0) {
|
||||
endGame();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetAfterLifeLost() {
|
||||
pacman.reset(); // to start tile, direction stopped
|
||||
ghostManager.reset(); // to house
|
||||
pacman.reset(); // to start tile, direction stopped
|
||||
ghostManager.reset(); // to house
|
||||
}
|
||||
|
||||
private void advanceLevel() {
|
||||
@ -122,14 +126,14 @@ public class PlayingState implements GameState {
|
||||
Point pacmanScreenPos = pacman.getPosition();
|
||||
MapTile tile = map.getTile(pacmanScreenPos);
|
||||
boolean wasRemoved = map.removeTileImage(pacmanScreenPos);
|
||||
if(wasRemoved && tile.getTileType() == TileType.LARGE_PELLET){
|
||||
if (wasRemoved && tile.getTileType() == TileType.LARGE_PELLET) {
|
||||
ghostManager.setFrightMode();
|
||||
}
|
||||
if(wasRemoved){
|
||||
if (wasRemoved) {
|
||||
dotsEaten++;
|
||||
fruitManager.dotEaten(dotsEaten);
|
||||
score+=tile.getTileType().getScore();
|
||||
if(dotsEaten == map.numberOfDots()){
|
||||
score += tile.getTileType().getScore();
|
||||
if (dotsEaten == map.numberOfDots()) {
|
||||
setPhase(RoundPhase.LEVEL_COMPLETE);
|
||||
}
|
||||
}
|
||||
@ -153,17 +157,17 @@ public class PlayingState implements GameState {
|
||||
}
|
||||
|
||||
private void drawCenterText(Graphics2D g, String text) {
|
||||
g.setFont(arcadeFont.deriveFont(18F));
|
||||
g.setFont(GameFonts.arcade(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);
|
||||
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) {
|
||||
g.setColor(Color.WHITE);
|
||||
g.setFont(arcadeFont);
|
||||
g.setFont(GameFonts.arcade(18F));
|
||||
|
||||
// Score (above map, left)
|
||||
g.drawString("Your Score", 48, 48);
|
||||
@ -202,6 +206,7 @@ public class PlayingState implements GameState {
|
||||
private void checkCollisions() {
|
||||
for (Ghost ghost : ghostManager.getGhosts()) {
|
||||
if (deathInProgress) return; // guard
|
||||
//if(overlap(pacman, ghost)
|
||||
double dist = pacman.distanceTo(ghost.getPosition());
|
||||
if (dist < GameMap.MAP_TILESIZE / 2.0) {
|
||||
if (ghost.isFrightened()) {
|
||||
@ -213,26 +218,24 @@ public class PlayingState implements GameState {
|
||||
deathInProgress = true;
|
||||
// Pac-Man loses a life
|
||||
lives--;
|
||||
if(lives >= REMAINING_LIVES)
|
||||
setPhase(RoundPhase.LIFE_LOST);
|
||||
else
|
||||
endGame();
|
||||
setPhase(RoundPhase.LIFE_LOST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void endGame() {
|
||||
setPhase(RoundPhase.GAME_OVER);
|
||||
gameStateManager.setState(GameStateType.GAME_OVER);
|
||||
}
|
||||
// private boolean overlaps(PacMan p, Ghost g) {
|
||||
// // center-distance or AABB; center distance keeps the arcade feel
|
||||
// double dx = p.getCenterX() - g.getCenterX();
|
||||
// double dy = p.getCenterY() - g.getCenterY();
|
||||
// double r = map.getTileSize() * 0.45; // tune threshold
|
||||
// return (dx*dx + dy*dy) <= r*r;
|
||||
// }
|
||||
|
||||
private Font loadArcadeFont() {
|
||||
try (InputStream is = getClass().getResourceAsStream("/fonts/PressStart2P-Regular.ttf")) {
|
||||
return Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(16f);
|
||||
} catch (Exception e) {
|
||||
return new Font("Monospaced", Font.BOLD, 16);
|
||||
}
|
||||
private void endGame() {
|
||||
gameOverState.setScore(score);
|
||||
gameOverState.setLevel(levelManager.getCurrentLevel().getLevel());
|
||||
gameStateManager.setState(GameStateType.GAME_OVER);
|
||||
}
|
||||
|
||||
public void setScore(int score) {
|
||||
|
||||
@ -4,6 +4,5 @@ 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
|
||||
}
|
||||
LIFE_LOST // Pac-Man hit; freeze briefly then reset positions}
|
||||
}
|
||||
26
src/main/java/se/urmo/game/util/GameFonts.java
Normal file
26
src/main/java/se/urmo/game/util/GameFonts.java
Normal file
@ -0,0 +1,26 @@
|
||||
package se.urmo.game.util;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.io.InputStream;
|
||||
|
||||
public enum GameFonts {
|
||||
ARCADE_FONT("/fonts/PressStart2P-Regular.ttf");
|
||||
|
||||
private final Font font;
|
||||
|
||||
GameFonts(String s) {
|
||||
font = loadArcadeFont(s);
|
||||
}
|
||||
|
||||
private static Font loadArcadeFont(String name) {
|
||||
try (InputStream is = GameFonts.class.getResourceAsStream(name)) {
|
||||
return Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(16f);
|
||||
} catch (Exception e) {
|
||||
return new Font("Monospaced", Font.BOLD, 16);
|
||||
}
|
||||
}
|
||||
|
||||
public static Font arcade(float textSize) {
|
||||
return ARCADE_FONT.font.deriveFont(textSize);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user