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) {
|
public GameStateManager(Game game) {
|
||||||
this.game = game;
|
this.game = game;
|
||||||
states.put(GameStateType.PLAYING, new PlayingState(game, this));
|
GameOverState gameOverState = new GameOverState(this, new HighScoreManager());
|
||||||
setState(GameStateType.PLAYING);
|
states.put(GameStateType.PLAYING, new PlayingState(game, this, gameOverState));
|
||||||
|
states.put(GameStateType.GAME_OVER, gameOverState);
|
||||||
|
setState(GameStateType.GAME_OVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setState(GameStateType type) {
|
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.MapTile;
|
||||||
import se.urmo.game.map.TileType;
|
import se.urmo.game.map.TileType;
|
||||||
import se.urmo.game.util.Direction;
|
import se.urmo.game.util.Direction;
|
||||||
|
import se.urmo.game.util.GameFonts;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class PlayingState implements GameState {
|
public class PlayingState implements GameState {
|
||||||
public static final int REMAINING_LIVES = 0;
|
public static final int REMAINING_LIVES = 0;
|
||||||
private final Game game;
|
private final Game game;
|
||||||
private final GameStateManager gameStateManager;
|
private final GameStateManager gameStateManager;
|
||||||
private final Font arcadeFont;
|
private final GameOverState gameOverState;
|
||||||
|
|
||||||
// Core components
|
// Core components
|
||||||
private final GhostManager ghostManager;
|
private final GhostManager ghostManager;
|
||||||
@ -34,9 +34,9 @@ public class PlayingState implements GameState {
|
|||||||
private GameMap map;
|
private GameMap map;
|
||||||
|
|
||||||
// Durations (tune to taste)
|
// Durations (tune to taste)
|
||||||
private static final int READY_MS = 1500;
|
private static final int READY_MS = 1500;
|
||||||
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;
|
||||||
|
|
||||||
// Score/Lives
|
// Score/Lives
|
||||||
private int score = 0;
|
private int score = 0;
|
||||||
@ -48,9 +48,10 @@ public class PlayingState implements GameState {
|
|||||||
private long phaseStartMs = System.currentTimeMillis();
|
private long phaseStartMs = System.currentTimeMillis();
|
||||||
private boolean deathInProgress;
|
private boolean deathInProgress;
|
||||||
|
|
||||||
public PlayingState(Game game, GameStateManager gameStateManager) {
|
public PlayingState(Game game, GameStateManager gameStateManager, GameOverState gameOverState) {
|
||||||
this.game = game;
|
this.game = game;
|
||||||
this.gameStateManager = gameStateManager;
|
this.gameStateManager = gameStateManager;
|
||||||
|
this.gameOverState = gameOverState;
|
||||||
this.map = new GameMap("maps/map1.csv");
|
this.map = new GameMap("maps/map1.csv");
|
||||||
this.animationManager = new AnimationManager();
|
this.animationManager = new AnimationManager();
|
||||||
this.levelManager = new LevelManager();
|
this.levelManager = new LevelManager();
|
||||||
@ -58,7 +59,6 @@ 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);
|
||||||
this.arcadeFont = loadArcadeFont();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -88,16 +88,20 @@ public class PlayingState implements GameState {
|
|||||||
case LIFE_LOST -> {
|
case LIFE_LOST -> {
|
||||||
// Freeze, then reset round (keep dot state)
|
// Freeze, then reset round (keep dot state)
|
||||||
if (phaseElapsed() >= LIFE_LOST_MS) {
|
if (phaseElapsed() >= LIFE_LOST_MS) {
|
||||||
|
deathInProgress = false;
|
||||||
resetAfterLifeLost();
|
resetAfterLifeLost();
|
||||||
setPhase(RoundPhase.READY);
|
setPhase(RoundPhase.READY);
|
||||||
|
if (lives <= 0) {
|
||||||
|
endGame();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetAfterLifeLost() {
|
private void resetAfterLifeLost() {
|
||||||
pacman.reset(); // to start tile, direction stopped
|
pacman.reset(); // to start tile, direction stopped
|
||||||
ghostManager.reset(); // to house
|
ghostManager.reset(); // to house
|
||||||
}
|
}
|
||||||
|
|
||||||
private void advanceLevel() {
|
private void advanceLevel() {
|
||||||
@ -122,14 +126,14 @@ public class PlayingState implements GameState {
|
|||||||
Point pacmanScreenPos = pacman.getPosition();
|
Point pacmanScreenPos = pacman.getPosition();
|
||||||
MapTile tile = map.getTile(pacmanScreenPos);
|
MapTile tile = map.getTile(pacmanScreenPos);
|
||||||
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();
|
||||||
}
|
}
|
||||||
if(wasRemoved){
|
if (wasRemoved) {
|
||||||
dotsEaten++;
|
dotsEaten++;
|
||||||
fruitManager.dotEaten(dotsEaten);
|
fruitManager.dotEaten(dotsEaten);
|
||||||
score+=tile.getTileType().getScore();
|
score += tile.getTileType().getScore();
|
||||||
if(dotsEaten == map.numberOfDots()){
|
if (dotsEaten == map.numberOfDots()) {
|
||||||
setPhase(RoundPhase.LEVEL_COMPLETE);
|
setPhase(RoundPhase.LEVEL_COMPLETE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,17 +157,17 @@ public class PlayingState implements GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void drawCenterText(Graphics2D g, String text) {
|
private void drawCenterText(Graphics2D g, String text) {
|
||||||
g.setFont(arcadeFont.deriveFont(18F));
|
g.setFont(GameFonts.arcade(18F));
|
||||||
g.setColor(Color.YELLOW);
|
g.setColor(Color.YELLOW);
|
||||||
var fm = g.getFontMetrics();
|
var fm = g.getFontMetrics();
|
||||||
int cx = GameMap.OFFSET_X + map.columns() * GameMap.MAP_TILESIZE/2;
|
int cx = GameMap.OFFSET_X + map.columns() * GameMap.MAP_TILESIZE / 2;
|
||||||
int cy = GameMap.OFFSET_Y + map.rows() * GameMap.MAP_TILESIZE/2;
|
int cy = GameMap.OFFSET_Y + map.rows() * GameMap.MAP_TILESIZE / 2;
|
||||||
g.drawString(text, cx - fm.stringWidth(text)/2, cy);
|
g.drawString(text, cx - fm.stringWidth(text) / 2, cy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawUI(Graphics2D g) {
|
private void drawUI(Graphics2D g) {
|
||||||
g.setColor(Color.WHITE);
|
g.setColor(Color.WHITE);
|
||||||
g.setFont(arcadeFont);
|
g.setFont(GameFonts.arcade(18F));
|
||||||
|
|
||||||
// Score (above map, left)
|
// Score (above map, left)
|
||||||
g.drawString("Your Score", 48, 48);
|
g.drawString("Your Score", 48, 48);
|
||||||
@ -202,6 +206,7 @@ public class PlayingState implements GameState {
|
|||||||
private void checkCollisions() {
|
private void checkCollisions() {
|
||||||
for (Ghost ghost : ghostManager.getGhosts()) {
|
for (Ghost ghost : ghostManager.getGhosts()) {
|
||||||
if (deathInProgress) return; // guard
|
if (deathInProgress) return; // guard
|
||||||
|
//if(overlap(pacman, ghost)
|
||||||
double dist = pacman.distanceTo(ghost.getPosition());
|
double dist = pacman.distanceTo(ghost.getPosition());
|
||||||
if (dist < GameMap.MAP_TILESIZE / 2.0) {
|
if (dist < GameMap.MAP_TILESIZE / 2.0) {
|
||||||
if (ghost.isFrightened()) {
|
if (ghost.isFrightened()) {
|
||||||
@ -213,26 +218,24 @@ public class PlayingState implements GameState {
|
|||||||
deathInProgress = true;
|
deathInProgress = true;
|
||||||
// Pac-Man loses a life
|
// Pac-Man loses a life
|
||||||
lives--;
|
lives--;
|
||||||
if(lives >= REMAINING_LIVES)
|
setPhase(RoundPhase.LIFE_LOST);
|
||||||
setPhase(RoundPhase.LIFE_LOST);
|
|
||||||
else
|
|
||||||
endGame();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endGame() {
|
// private boolean overlaps(PacMan p, Ghost g) {
|
||||||
setPhase(RoundPhase.GAME_OVER);
|
// // center-distance or AABB; center distance keeps the arcade feel
|
||||||
gameStateManager.setState(GameStateType.GAME_OVER);
|
// 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() {
|
private void endGame() {
|
||||||
try (InputStream is = getClass().getResourceAsStream("/fonts/PressStart2P-Regular.ttf")) {
|
gameOverState.setScore(score);
|
||||||
return Font.createFont(Font.TRUETYPE_FONT, is).deriveFont(16f);
|
gameOverState.setLevel(levelManager.getCurrentLevel().getLevel());
|
||||||
} catch (Exception e) {
|
gameStateManager.setState(GameStateType.GAME_OVER);
|
||||||
return new Font("Monospaced", Font.BOLD, 16);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setScore(int score) {
|
public void setScore(int score) {
|
||||||
|
|||||||
@ -4,6 +4,5 @@ public enum RoundPhase {
|
|||||||
READY, // "READY!" shown briefly before play starts
|
READY, // "READY!" shown briefly before play starts
|
||||||
PLAYING, // normal gameplay
|
PLAYING, // normal gameplay
|
||||||
LEVEL_COMPLETE, // all dots eaten; freeze briefly then advance level
|
LEVEL_COMPLETE, // all dots eaten; freeze briefly then advance level
|
||||||
LIFE_LOST, // Pac-Man hit; freeze briefly then reset positions
|
LIFE_LOST // Pac-Man hit; freeze briefly then reset positions}
|
||||||
GAME_OVER
|
|
||||||
}
|
}
|
||||||
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