import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.metal.MetalLookAndFeel;
/**
- 程序入口:设置系统外观并启动扫雷界面
*/
public class MinesweeperApp {
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(new MetalLookAndFeel());
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(() -> new MinesweeperUI().setVisible(true));
}
}
三、项目三:星际射击游戏
3.1 需求分析
在扫雷的回合制基础上,升级为 实时动作游戏:玩家控制飞船,自动/手动发射子弹,消灭下落的敌人,支持粒子爆炸特效、暂停、难度梯度。
3.2 架构设计
采用 双 Timer 架构:
逻辑 Timer(16ms):更新实体位置、碰撞检测、生成敌人
渲染 Timer(16ms):触发 repaint(),与逻辑解耦
实体采用 抽象基类 + 匿名内部类 实现多态绘制。
3.3 程序界面
屏幕截图 2026-07-02 230114
屏幕截图 2026-07-02 230027
屏幕截图 2026-07-02 230012
3.4 完整代码
GameEntity.java
import java.awt.*;
import java.io.Serializable;
/**
游戏实体基类:玩家、敌人、子弹都继承此类
*/
public abstract class GameEntity implements Serializable {
private static final long serialVersionUID = 1L;protected double x, y;
protected int width, height;
protected double speedX, speedY;
protected boolean alive = true;
protected Color color;
protected String type;public GameEntity(double x, double y, int width, int height, double speedX, double speedY, Color color, String type) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.speedX = speedX;
this.speedY = speedY;
this.color = color;
this.type = type;
}public void update() {
x += speedX;
y += speedY;
}public Rectangle getBounds() {
return new Rectangle((int)x, (int)y, width, height);
}public boolean intersects(GameEntity other) {
return getBounds().intersects(other.getBounds());
}public abstract void draw(Graphics2D g2d);
public double getX() { return x; }
public double getY() { return y; }
public int getWidth() { return width; }
public int getHeight() { return height; }
public boolean isAlive() { return alive; }
public void setAlive(boolean alive) { this.alive = alive; }
public String getType() { return type; }
public void setX(double x) { this.x = x; }
public void setY(double y) { this.y = y; }
public void setSpeedX(double speedX) { this.speedX = speedX; }
public void setSpeedY(double speedY) { this.speedY = speedY; }
}
ShootingGame.java
import java.awt.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
射击游戏核心:管理所有实体、碰撞检测、分数、难度、存档
*/
public class ShootingGame implements Serializable {
private static final long serialVersionUID = 1L;public enum Difficulty { EASY, MEDIUM, HARD }
private GameEntity player;
private int playerLives = 3;
private int maxLives = 3;private List enemies;
private List bullets;
private List particles;private boolean running = false;
private boolean paused = false;
private boolean gameOver = false;
private int score = 0;
private int killCount = 0;
private int shotCount = 0;
private int difficulty = 1;
private String difficultyName = “简单”;private transient long startTime;
private int elapsedTime = 0;private int enemySpawnTimer = 0;
private int enemySpawnInterval = 60;
private int enemySpeedBase = 2;
private Random random = new Random();private int width = 600;
private int height = 700;public void init(int width, int height, Difficulty diff) {
this.width = width;
this.height = height;
this.difficulty = diff.ordinal() + 1;
this.difficultyName = diff.name().equals(“EASY”) ? “简单” : diff.name().equals(“MEDIUM”) ? “中等” : “困难”;switch (diff) { case EASY: enemySpawnInterval = 80; enemySpeedBase = 1; maxLives = 5; break; case MEDIUM: enemySpawnInterval = 50; enemySpeedBase = 2; maxLives = 3; break; case HARD: enemySpawnInterval = 30; enemySpeedBase = 3; maxLives = 2; break; } playerLives = maxLives; score = 0; killCount = 0; shotCount = 0; elapsedTime = 0; gameOver = false; paused = false; player = new GameEntity(width / 2 - 20, height - 80, 40, 40, 0, 0, new Color(50, 130, 220), "player") { @Override public void draw(Graphics2D g2d) { g2d.setColor(color); int[] xs = {(int)x + width/2, (int)x, (int)x + width}; int[] ys = {(int)y, (int)y + height, (int)y + height}; g2d.fillPolygon(xs, ys, 3); g2d.setColor(Color.CYAN); g2d.fillOval((int)x + 15, (int)y + 20, 10, 10); } }; enemies = new ArrayList<>(); bullets = new ArrayList<>(); particles = new ArrayList<>(); running = true; startTime = System.currentTimeMillis();}
public void update() {
if (!running || paused || gameOver) return;elapsedTime = (int)((System.currentTimeMillis() - startTime) / 1000); player.update(); if (player.getX() < 0) player.setX(0); if (player.getX() > width - player.getWidth()) player.setX(width - player.getWidth()); enemySpawnTimer++; if (enemySpawnTimer >= enemySpawnInterval) { enemySpawnTimer = 0; spawnEnemy(); } Iterator<GameEntity> bit = bullets.iterator(); while (bit.hasNext()) { GameEntity b = bit.next(); b.update(); if (b.getY() < -10) b.setAlive(false); } Iterator<GameEntity> eit = enemies.iterator(); while (eit.hasNext()) { GameEntity e = eit.next(); e.update(); if (e.getY() > height) { e.setAlive(false); playerLives--; createExplosion(e.getX() + e.getWidth()/2, e.getY() + e.getHeight(), Color.RED); if (playerLives <= 0) { gameOver = true; running = false; } } } Iterator<GameEntity> pit = particles.iterator(); while (pit.hasNext()) { GameEntity p = pit.next(); p.update(); if (p.getY() > p.getY() + 50 || !p.isAlive()) pit.remove(); } for (GameEntity b : bullets) { if (!b.isAlive()) continue; for (GameEntity e : enemies) { if (!e.isAlive()) continue; if (b.intersects(e)) { b.setAlive(false); e.setAlive(false); score += 10; killCount++; createExplosion(e.getX() + e.getWidth()/2, e.getY() + e.getHeight()/2, e.color); break; } } } for (GameEntity e : enemies) { if (!e.isAlive()) continue; if (e.intersects(player)) { e.setAlive(false); playerLives--; createExplosion(player.getX() + 20, player.getY(), Color.ORANGE); if (playerLives <= 0) { gameOver = true; running = false; } } } bullets.removeIf(b -> !b.isAlive()); enemies.removeIf(e -> !e.isAlive());}
private void spawnEnemy() {
int w = 30 + random.nextInt(20);
int h = 30 + random.nextInt(20);
int ex = random.nextInt(width - w);
int speed = enemySpeedBase + random.nextInt(2);
Color[] colors = {Color.RED, Color.MAGENTA, Color.ORANGE, new Color(200, 50, 50)};
Color c = colors[random.nextInt(colors.length)];GameEntity enemy = new GameEntity(ex, -h, w, h, 0, speed, c, "enemy") { @Override public void draw(Graphics2D g2d) { g2d.setColor(color); g2d.fillOval((int)x, (int)y, width, height); g2d.setColor(Color.WHITE); g2d.fillOval((int)x + 5, (int)y + 5, width - 10, height / 3); } }; enemies.add(enemy);}
public void shoot() {
if (!running || paused || gameOver) return;
shotCount++;
GameEntity bullet = new GameEntity(
player.getX() + player.getWidth()/2 - 3,
player.getY() - 10,
6, 12, 0, -8, new Color(255, 220, 50), “bullet”
) {
@Override
public void draw(Graphics2D g2d) {
g2d.setColor(color);
g2d.fillRect((int)x, (int)y, width, height);
g2d.setColor(Color.WHITE);
g2d.fillRect((int)x + 2, (int)y, 2, 4);
}
};
bullets.add(bullet);
}public void movePlayer(int dx) {
if (player != null) {
player.setX(player.getX() + dx);
}
}public void setPlayerX(double x) {
if (player != null) player.setX(x - player.getWidth()/2);
}private void createExplosion(double cx, double cy, Color c) {
for (int i = 0; i < 8; i++) {
double angle = Math.random() * Math.PI * 2;
double speed = 1 + Math.random() * 3;
final double sx = Math.cos(angle) * speed;
final double sy = Math.sin(angle) * speed;
final Color pc = c;
GameEntity p = new GameEntity(cx, cy, 4, 4, sx, sy, pc, “particle”) {
private int life = 20;
@Override
public void update() {
super.update();
life–;
if (life <= 0) setAlive(false);
}
@Override
public void draw(Graphics2D g2d) {
g2d.setColor(pc);
g2d.fillOval((int)x, (int)y, width, height);
}
};
particles.add§;
}
}public void draw(Graphics2D g2d) {
g2d.setColor(new Color(20, 20, 40));
g2d.fillRect(0, 0, width, height);g2d.setColor(new Color(255, 255, 255, 80)); for (int i = 0; i < 50; i++) { int sx = (i * 37 + 13) % width; int sy = (i * 23 + 7) % height; g2d.fillOval(sx, (sy + elapsedTime * 10) % height, 2, 2); } for (GameEntity p : particles) p.draw(g2d); for (GameEntity e : enemies) e.draw(g2d); for (GameEntity b : bullets) b.draw(g2d); if (player != null && playerLives > 0) player.draw(g2d); g2d.setColor(Color.WHITE); g2d.setFont(new Font("微软雅黑", Font.BOLD, 16)); g2d.drawString("分数: " + score, 15, 25); g2d.drawString("时间: " + elapsedTime + "秒", 150, 25); g2d.drawString("击杀: " + killCount, 280, 25); g2d.drawString("发射: " + shotCount, 400, 25); g2d.drawString("生命: ", 15, 50); for (int i = 0; i < maxLives; i++) { if (i < playerLives) { g2d.setColor(Color.RED); g2d.fillOval(60 + i * 20, 38, 12, 12); } else { g2d.setColor(Color.GRAY); g2d.drawOval(60 + i * 20, 38, 12, 12); } } g2d.setColor(Color.YELLOW); g2d.drawString("难度: " + difficultyName, width - 100, 25); if (gameOver) { g2d.setColor(new Color(0, 0, 0, 180)); g2d.fillRect(0, 0, width, height); g2d.setColor(Color.WHITE); g2d.setFont(new Font("微软雅黑", Font.BOLD, 40)); String msg = playerLives <= 0 ? "游戏结束" : "胜利"; int msgW = g2d.getFontMetrics().stringWidth(msg); g2d.drawString(msg, (width - msgW) / 2, height / 2 - 40); g2d.setFont(new Font("微软雅黑", Font.PLAIN, 20)); String info = "最终得分: " + score + " 击杀: " + killCount + " 用时: " + elapsedTime + "秒"; int infoW = g2d.getFontMetrics().stringWidth(info); g2d.drawString(info, (width - infoW) / 2, height / 2 + 10); g2d.setFont(new Font("微软雅黑", Font.PLAIN, 16)); String hint = "按 R 重新开始 或 空格 发射"; int hintW = g2d.getFontMetrics().stringWidth(hint); g2d.drawString(hint, (width - hintW) / 2, height / 2 + 50); } if (paused && !gameOver) { g2d.setColor(new Color(0, 0, 0, 120)); g2d.fillRect(0, 0, width, height); g2d.setColor(Color.YELLOW); g2d.setFont(new Font("微软雅黑", Font.BOLD, 36)); String msg = "暂 停"; int msgW = g2d.getFontMetrics().stringWidth(msg); g2d.drawString(msg, (width - msgW) / 2, height / 2); g2d.setFont(new Font("微软雅黑", Font.PLAIN, 16)); g2d.drawString("按 P 继续", (width - 60) / 2, height / 2 + 40); }}
public boolean isRunning() { return running; }
public boolean isPaused() { return paused; }
public boolean isGameOver() { return gameOver; }
public int getScore() { return score; }
public int getKillCount() { return killCount; }
public int getShotCount() { return shotCount; }
public int getElapsedTime() { return elapsedTime; }
public int getPlayerLives() { return playerLives; }
public int getDifficulty() { return difficulty; }
public String getDifficultyName() { return difficultyName; }
public int getWidth() { return width; }
public int getHeight() { return height; }public void setPaused(boolean paused) {
this.paused = paused;
if (!paused) {
startTime = System.currentTimeMillis() - elapsedTime * 1000L;
}
}
public void setRunning(boolean running) { this.running = running; }
public void setGameOver(boolean gameOver) { this.gameOver = gameOver; }
public void setScore(int score) { this.score = score; }
public void setKillCount(int killCount) { this.killCount = killCount; }
public void setShotCount(int shotCount) { this.shotCount = shotCount; }
public void setElapsedTime(int elapsedTime) { this.elapsedTime = elapsedTime; }
public void setPlayerLives(int playerLives) { this.playerLives = playerLives; }
public void setDifficulty(int difficulty) { this.difficulty = difficulty; }
public void setDifficultyName(String difficultyName) { this.difficultyName = difficultyName; }
public void setStartTime(long startTime) { this.startTime = startTime; }public List getEnemies() { return enemies; }
public List getBullets() { return bullets; }
public List getParticles() { return particles; }
public GameEntity getPlayer() { return player; }
public void setPlayer(GameEntity player) { this.player = player; }
public void setEnemies(List enemies) { this.enemies = enemies; }
public void setBullets(List bullets) { this.bullets = bullets; }
public void setParticles(List particles) { this.particles = particles; }
public void setMaxLives(int maxLives) { this.maxLives = maxLives; }
public int getMaxLives() { return maxLives; }
public void setEnemySpawnInterval(int interval) { this.enemySpawnInterval = interval; }
public void setEnemySpeedBase(int speed) { this.enemySpeedBase = speed; }
public int getEnemySpawnInterval() { return enemySpawnInterval; }
public int getEnemySpeedBase() { return enemySpeedBase; }
}
GameState.java(射击)
import java.io.Serializable;
import java.util.List;
/**
射击游戏存档状态
*/
public class GameState implements Serializable {
private static final long serialVersionUID = 1L;private List enemies, bullets, particles;
private GameEntity player;
private int playerLives, maxLives;
private int score, killCount, shotCount, elapsedTime;
private boolean running, paused, gameOver;
private int difficulty, enemySpawnInterval, enemySpeedBase;
private String difficultyName;
private int width, height;public GameState(ShootingGame game) {
this.enemies = game.getEnemies();
this.bullets = game.getBullets();
this.particles = game.getParticles();
this.player = game.getPlayer();
this.playerLives = game.getPlayerLives();
this.maxLives = game.getMaxLives();
this.score = game.getScore();
this.killCount = game.getKillCount();
this.shotCount = game.getShotCount();
this.elapsedTime = game.getElapsedTime();
this.running = game.isRunning();
this.paused = game.isPaused();
this.gameOver = game.isGameOver();
this.difficulty = game.getDifficulty();
this.difficultyName = game.getDifficultyName();
this.enemySpawnInterval = game.getEnemySpawnInterval();
this.enemySpeedBase = game.getEnemySpeedBase();
this.width = game.getWidth();
this.height = game.getHeight();
}public List getEnemies() { return enemies; }
public List getBullets() { return bullets; }
public List getParticles() { return particles; }
public GameEntity getPlayer() { return player; }
public int getPlayerLives() { return playerLives; }
public int getMaxLives() { return maxLives; }
public int getScore() { return score; }