ScorePopup working

This commit is contained in:
Urban Modig
2025-09-06 21:53:18 +02:00
parent 4b262362be
commit 5f653d3252
3 changed files with 110 additions and 1 deletions

View File

@ -0,0 +1,58 @@
package se.urmo.game.entities;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
public final class ScorePopup {
private final double startX, startY; // world px (not screen)
private final String text;
private final long startNs;
private final long lifeNs; // e.g. 1_000_000_000L (1s)
// simple motion params
private final double risePixels; // e.g. 16 px total rise
public ScorePopup(double x, double y, String text, long lifeNs, double risePixels) {
this.startX = x;
this.startY = y;
this.text = text;
this.lifeNs = lifeNs;
this.risePixels = risePixels;
this.startNs = System.nanoTime();
}
public boolean isAlive() {
return (System.nanoTime() - startNs) < lifeNs;
}
public void draw(Graphics2D g, int offsetX, int offsetY, Font font) {
long dt = System.nanoTime() - startNs;
double t = Math.min(1.0, dt / (double) lifeNs); // 0..1
// motion: ease-out upward (quadratic)
double y = startY - (risePixels * (1 - (1 - t) * (1 - t)));
// fade: alpha 1 → 0
float alpha = (float) (1.0 - t);
var oldComp = g.getComposite();
var oldFont = g.getFont();
g.setFont(font);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
// draw centered on tile center
var fm = g.getFontMetrics();
int sx = offsetX + (int) Math.round(startX);
int sy = offsetY + (int) Math.round(y);
int x = sx - fm.stringWidth(text) / 2;
int baseline = sy; // tune if you want it a tad above the exact point
g.setColor(Color.WHITE);
g.drawString(text, x, baseline);
g.setComposite(oldComp);
g.setFont(oldFont);
}
}

View File

@ -0,0 +1,41 @@
package se.urmo.game.main;
import se.urmo.game.entities.ScorePopup;
import java.awt.Font;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
public final class ScorePopupManager {
private final List<ScorePopup> popups = new ArrayList<>();
// convenience defaults
private static final long LIFE_NS = 1_000_000_000L; // 1s
private static final double RISE_PX = 16.0;
private static final double worldX = GamePanel.SCREEN_WIDTH / 2.0 - 15;
private static final double worldY = 230;
public void spawn(String text) {
popups.add(new ScorePopup(worldX, worldY, text, LIFE_NS, RISE_PX));
}
public void spawn(int score) {
spawn(String.valueOf(score));
}
public void update() {
// time-based; no per-tick math needed, just prune dead ones
popups.removeIf(p -> !p.isAlive());
}
public void draw(Graphics2D g, int offsetX, int offsetY, Font font) {
for (ScorePopup p : popups) {
p.draw(g, offsetX, offsetY, font);
}
}
public void clear() {
popups.clear();
}
}

View File

@ -12,6 +12,7 @@ import se.urmo.game.main.FruitManager;
import se.urmo.game.main.GameStateManager;
import se.urmo.game.main.GhostManager;
import se.urmo.game.main.LevelManager;
import se.urmo.game.main.ScorePopupManager;
import se.urmo.game.map.GameMap;
import se.urmo.game.map.MapTile;
import se.urmo.game.map.TileType;
@ -20,6 +21,7 @@ import se.urmo.game.util.GameFonts;
import se.urmo.game.util.GameStateType;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyEvent;
@ -54,6 +56,9 @@ public class PlayingState implements GameState {
private boolean deathInProgress;
private int frightMultiplier;
private final ScorePopupManager scorePopups = new ScorePopupManager();
private final Font scorePopupFont = GameFonts.arcade(16F); // or your arcade font
public PlayingState(GameStateManager gameStateManager, GameOverState gameOverState) {
this.gameStateManager = gameStateManager;
this.gameOverState = gameOverState;
@ -105,6 +110,7 @@ public class PlayingState implements GameState {
pacman.update();
}
}
scorePopups.update();
}
private void advanceLevel() {
@ -150,6 +156,7 @@ public class PlayingState implements GameState {
pacman.draw(g);
fruitManager.draw(g);
drawUI(g);
scorePopups.draw(g, GameMap.OFFSET_X, GameMap.OFFSET_Y, scorePopupFont);
// Phase overlays
switch (phase) {
@ -221,7 +228,9 @@ public class PlayingState implements GameState {
if (ghost.isEaten()) return;
if (ghost.isFrightened()) {
log.debug("Pacman eats ghost");
score += 200 * (1 << (ghostManager.getGhosts().size() - ghostManager.isFrightened()));
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");
@ -251,6 +260,7 @@ public class PlayingState implements GameState {
}
public void setScore(int score) {
scorePopups.spawn(score);
this.score += score;
}
}