类似于乒乓球的游戏,游戏可以播放背景音乐,可以更换背景图,当小球碰到下面的挡板后会反弹,当小球碰到方块后会增加分数,当小球掉落会导致游戏失败,按下esc键游戏会暂停,音乐会停止播放,运行时会新建一个music文件夹,文件夹内放入任何音频文件都将作为背景音乐播放
package org.example;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader;
import lombok.Getter;
import lombok.Setter;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import javax.imageio.ImageIO;
import javax.sound.sampled.*;
public class BreakoutGame extends JPanel implements KeyListener {
// 获取显示器尺寸作为游戏窗口大小
private static final Dimension SCREEN_SIZE = Toolkit.getDefaultToolkit().getScreenSize();
public static final int WIDTH = SCREEN_SIZE.width;
public static final int HEIGHT = SCREEN_SIZE.height;
// 游戏常量 - 根据屏幕尺寸动态计算
private Clip currentMusicClip;
private float volume = 0.7f;
private boolean musicEnabled = true;
private List<File> musicFiles = new ArrayList<>();
private int currentMusicIndex = 0;
private ExecutorService musicExecutor;
private long pausePosition = 0; // 记录音乐暂停位置
private boolean musicPaused = false; // 音乐暂停状态标志
static final int PADDLE_WIDTH = WIDTH / 15; // 动态计算挡板宽度
static final int PADDLE_HEIGHT = HEIGHT / 60; // 动态计算挡板高度
public static final int BALL_SIZE = WIDTH / 80; // 动态计算球的大小
private static final int BRICK_COLS = 20; // 增加砖块列数以适应更大屏幕
private static final int BRICK_FALL_SPEED = HEIGHT / 200; // 动态计算下落速度
// 使用Guava的ImmutableList定义背景列表(不可变集合)
private static final ImmutableList<String> BACKGROUND_NAMES = ImmutableList.of(
"default", "stars", "ocean", "beach", "forest"
);
private int backgroundIndex = 0;
// 使用Guava缓存加载背景图片(核心优化)
private final LoadingCache<String, BufferedImage> backgroundCache = CacheBuilder.newBuilder()
.maximumSize(10) // 最多缓存10张背景图
.expireAfterAccess(10, TimeUnit.MINUTES) // 10分钟未访问则过期
.build(new CacheLoader<>() {
@Override
public BufferedImage load(String bgName) throws Exception {
return loadBackgroundImage(bgName);
}
});
// 游戏状态
private int score = 0;
private volatile boolean gameRunning = true;
private volatile boolean gamePaused = false;
// 使用同步集合防止并发修改异常
private final List<Brick> bricks = Collections.synchronizedList(new ArrayList<>());
private final List<Brick> fallingBricks = Collections.synchronizedList(new ArrayList<>());
// 游戏对象
private Ball ball;
private Paddle paddle;
// 纹理资源
private BufferedImage backgroundImage;
private BufferedImage ballTexture;
private BufferedImage paddleTexture;
private BufferedImage brickTexture;
// 菜单系统
private JDialog menuDialog;
private JSlider volumeSlider;
private JComboBox<String> backgroundSelector;
// 线程池
private final ExecutorService executor = Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder().setNameFormat("game-loop-%d").build()
);
private int brickSpawnCounter = 0;
private final Random random = new Random();
public BreakoutGame() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
loadTextures();
initAudio(); // 修复音频加载逻辑
initMenu();
initGame();
startGameLoop();
addKeyListener(this);
setFocusable(true);
requestFocus();
addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent e) {
if (!gamePaused) {
paddle.setX(e.getX() - PADDLE_WIDTH / 2);
repaint();
}
}
});
}
// 纹理加载
private void loadTextures() {
try {
// 使用Guava缓存获取背景图
backgroundImage = backgroundCache.get(BACKGROUND_NAMES.get(backgroundIndex));
ballTexture = loadImageResource("ball_texture.png");
paddleTexture = loadImageResource("paddle_texture.png");
brickTexture = loadImageResource("brick_texture.png");
} catch (Exception e) {
System.err.println("纹理加载失败: " + Throwables.getStackTraceAsString(e));
createFallbackTextures();
}
}
// 加载背景图片(使用Guava的Files类)
private BufferedImage loadBackgroundImage(String bgName) throws IOException {
// 优先从resources加载
URL resourceUrl = Resources.getResource(bgName + ".jpg");
if (resourceUrl != null) {
return ImageIO.read(resourceUrl);
}
// 尝试从文件系统加载
File bgFile = new File("backgrounds/" + bgName + ".jpg");
if (bgFile.exists()) {
return ImageIO.read(bgFile);
}
// 使用Guava的Files类创建目录
Files.createParentDirs(bgFile);
throw new IOException("Background image not found: " + bgName);
}
private BufferedImage loadImageResource(String path) throws IOException {
URL url = Resources.getResource(path);
return url != null ? ImageIO.read(url) : null;
}
private void createFallbackTextures() {
ballTexture = new BufferedImage(BALL_SIZE, BALL_SIZE, BufferedImage.TYPE_INT_RGB);
paddleTexture = new BufferedImage(PADDLE_WIDTH, PADDLE_HEIGHT, BufferedImage.TYPE_INT_RGB);
brickTexture = new BufferedImage(WIDTH / 20, HEIGHT / 40, BufferedImage.TYPE_INT_RGB); // 动态计算砖块大小
}
// 修复音频加载(支持文件夹自动创建和MP3格式)
private void initAudio() {
// 创建音乐线程池
musicExecutor = Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder().setNameFormat("music-player-%d").build()
);
File musicDir = new File("music");
if (!musicDir.exists()) {
boolean created = musicDir.mkdir();
System.out.println(created ? "创建music文件夹成功" : "创建music文件夹失败");
}
// 扫描music文件夹中的音频文件(支持多种格式)
File[] files = musicDir.listFiles((dir, name) -> {
String lower = name.toLowerCase();
return lower.endsWith(".wav") || lower.endsWith(".mp3") ||
lower.endsWith(".aiff") || lower.endsWith(".au") ||
lower.endsWith(".ogg") || lower.endsWith(".flac");
});
if (files == null || files.length == 0) {
System.err.println("music文件夹为空,请添加音频文件");
return;
}
// 添加到播放列表
musicFiles = Arrays.asList(files);
System.out.println("找到音频文件: " + musicFiles.size() + " 个");
// 开始播放音乐
playNextMusic();
}
// 播放下一首音乐(支持MP3)
private void playNextMusic() {
if (musicFiles.isEmpty() || !musicEnabled) return;
musicExecutor.execute(() -> {
try {
File musicFile = musicFiles.get(currentMusicIndex);
System.out.println("播放音频: " + musicFile.getName());
// 1. 加载音频流
AudioInputStream audioIn;
if (musicFile.getName().toLowerCase().endsWith(".mp3")) {
audioIn = new MpegAudioFileReader().getAudioInputStream(musicFile);
} else {
audioIn = AudioSystem.getAudioInputStream(musicFile);
}
// 2. 转换为PCM格式(修复兼容性问题)
AudioFormat baseFormat = audioIn.getFormat();
AudioFormat targetFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
44100, // 固定采样率
16, // 16位深度
baseFormat.getChannels(),
baseFormat.getChannels() * 2,
44100, // 帧率
false
);
AudioInputStream pcmStream = AudioSystem.getAudioInputStream(targetFormat, audioIn);
// 3. 释放旧资源(关键!)
if (currentMusicClip != null) {
currentMusicClip.removeLineListener(null); // 移除旧监听器
currentMusicClip.close();
}
// 4. 创建新Clip
currentMusicClip = AudioSystem.getClip();
currentMusicClip.open(pcmStream);
setVolume(volume);
// 5. 修复监听器逻辑(核心修复点)
currentMusicClip.addLineListener(event -> {
if (event.getType() == LineEvent.Type.STOP) {
// 仅当播放自然结束时切换歌曲(非暂停且播放位置已达末尾)
if (!musicPaused && currentMusicClip.getFramePosition() >= currentMusicClip.getFrameLength()) {
currentMusicIndex = (currentMusicIndex + 1) % musicFiles.size();
playNextMusic();
}
}
});
// 6. 开始播放
currentMusicClip.start();
musicPaused = false; // 重置暂停状态
} catch (Exception e) {
System.err.println("播放失败: " + Throwables.getStackTraceAsString(e));
// 失败时延迟重试
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
currentMusicIndex = (currentMusicIndex + 1) % musicFiles.size();
playNextMusic();
}
});
}
// 暂停音乐
private void pauseMusic() {
if (currentMusicClip != null && currentMusicClip.isRunning()) {
pausePosition = currentMusicClip.getMicrosecondPosition();
currentMusicClip.stop();
musicPaused = true;
System.out.println("音乐已暂停");
}
}
// 恢复音乐
private void resumeMusic() {
if (currentMusicClip != null && musicPaused && musicEnabled) {
currentMusicClip.setMicrosecondPosition(pausePosition);
currentMusicClip.start();
musicPaused = false;
System.out.println("音乐已恢复");
} else if (currentMusicClip == null && musicEnabled) {
// 如果没有音乐在播放但音乐启用,开始播放
playNextMusic();
}
}
// 设置音量
private void setVolume(float volume) {
this.volume = volume;
if (currentMusicClip != null && currentMusicClip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
FloatControl gainControl = (FloatControl) currentMusicClip.getControl(FloatControl.Type.MASTER_GAIN);
float dB = (float) (Math.log(volume) / Math.log(10.0) * 20.0);
gainControl.setValue(dB);
}
}
private void initMenu() {
menuDialog = new JDialog((Frame)null, "游戏设置", true);
menuDialog.setSize(300, 250);
menuDialog.setLayout(new GridLayout(5, 1, 10, 10));
menuDialog.setLocationRelativeTo(null);
// 音量控制
JPanel volumePanel = new JPanel();
volumePanel.add(new JLabel("音量控制:"));
volumeSlider = new JSlider(0, 100, (int)(volume * 100));
volumeSlider.addChangeListener(e -> {
volume = volumeSlider.getValue() / 100f;
setVolume(volume);
});
volumePanel.add(volumeSlider);
menuDialog.add(volumePanel);
// 背景选择 - 使用Guava的ImmutableList填充下拉框
JPanel bgPanel = new JPanel();
bgPanel.add(new JLabel("背景选择:"));
backgroundSelector = new JComboBox<>(BACKGROUND_NAMES.toArray(new String[0]));
backgroundSelector.setSelectedIndex(backgroundIndex);
backgroundSelector.addActionListener(e -> {
String selected = (String) backgroundSelector.getSelectedItem();
changeBackground(selected);
});
bgPanel.add(backgroundSelector);
menuDialog.add(bgPanel);
// 音乐开关
JCheckBox musicToggle = new JCheckBox("启用背景音乐", musicEnabled);
musicToggle.addActionListener(e -> {
musicEnabled = musicToggle.isSelected();
if (musicEnabled) {
resumeMusic(); // 恢复音乐
} else {
pauseMusic(); // 暂停音乐
}
});
menuDialog.add(musicToggle);
// 返回游戏按钮
JButton backButton = new JButton("返回游戏");
backButton.addActionListener(e -> {
menuDialog.setVisible(false);
gamePaused = false;
resumeMusic(); // 返回游戏时恢复音乐
requestFocus();
});
menuDialog.add(backButton);
// 关闭处理
menuDialog.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
gamePaused = false;
resumeMusic(); // 关闭菜单时恢复音乐
}
});
}
private void changeBackground(String bgName) {
try {
// 使用Guava缓存获取背景图
backgroundImage = backgroundCache.get(bgName);
backgroundIndex = BACKGROUND_NAMES.indexOf(bgName);
repaint(); // 重绘游戏界面
System.out.println("更换背景: " + bgName);
} catch (ExecutionException e) {
System.err.println("背景加载失败: " + Throwables.getStackTraceAsString(e));
}
}
// 游戏初始化
private void initGame() {
resetGame();
}
private void resetGame() {
score = 0;
gameRunning = true;
gamePaused = false;
bricks.clear();
fallingBricks.clear();
brickSpawnCounter = 0;
resumeMusic(); // 游戏重置时恢复音乐
paddle = new Paddle(
WIDTH / 2 - PADDLE_WIDTH / 2,
HEIGHT - 50,
PADDLE_WIDTH,
PADDLE_HEIGHT,
paddleTexture
);
ball = new Ball(
WIDTH / 2,
HEIGHT / 2,
WIDTH / 200, // 动态计算球速
-HEIGHT / 200,
BALL_SIZE,
ballTexture
);
}
// 游戏循环
private void startGameLoop() {
executor.execute(() -> {
while (gameRunning && !Thread.currentThread().isInterrupted()) {
if (!gamePaused) {
try {
updateGame();
SwingUtilities.invokeLater(this::repaint);
} catch (Exception e) {
System.err.println("游戏循环错误: " + e.getMessage());
}
}
try {
Thread.sleep(16);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
}
// 生成新方块行
private void spawnBrickRow() {
if (random.nextFloat() > 0.3f) return;
int brickWidth = WIDTH / BRICK_COLS;
int brickHeight = HEIGHT / 40;
for (int col = 0; col < BRICK_COLS; col++) {
if (random.nextFloat() > 0.7f) continue;
fallingBricks.add(new Brick(
col * brickWidth,
-30,
brickWidth,
brickHeight,
brickTexture
));
}
}
// 修复并发修改问题
private void updateGame() {
// 生成新方块
if (brickSpawnCounter++ >= 60) {
spawnBrickRow();
brickSpawnCounter = 0;
}
// 使用同步块保护集合操作
synchronized (fallingBricks) {
Iterator<Brick> iterator = fallingBricks.iterator();
while (iterator.hasNext()) {
Brick brick = iterator.next();
brick.setY(brick.getY() + BRICK_FALL_SPEED);
if (brick.getY() > HEIGHT) {
iterator.remove();
} else if (brick.getY() > 50) {
bricks.add(brick);
iterator.remove();
}
}
}
// 球移动
ball.move();
// 边界碰撞检测
if (ball.getX() <= 0 || ball.getX() >= WIDTH - ball.getSize())
ball.reverseX();
if (ball.getY() <= 0)
ball.reverseY();
// 挡板碰撞检测
if (ball.getBounds().intersects(paddle.getBounds()))
ball.reverseY();
// 游戏结束检测
if (ball.getY() > HEIGHT) {
gameRunning = false;
showGameOverDialog("游戏结束! 得分: " + score);
return;
}
// 砖块碰撞检测
synchronized (bricks) {
Iterator<Brick> brickIterator = bricks.iterator();
while (brickIterator.hasNext()) {
Brick brick = brickIterator.next();
if (brick.isVisible() && ball.getBounds().intersects(brick.getBounds())) {
brick.setVisible(false);
ball.reverseY();
score += 10;
brickIterator.remove();
}
}
}
// 检查砖块堆积
synchronized (bricks) {
for (Brick brick : bricks) {
if (brick.getY() + brick.getHeight() >= HEIGHT - 50) {
gameRunning = false;
showGameOverDialog("砖块堆积到屏幕底部! 得分: " + score);
return;
}
}
}
}
// 游戏结束对话框
private void showGameOverDialog(String message) {
SwingUtilities.invokeLater(() -> {
int option = JOptionPane.showOptionDialog(
this, message, "游戏结束",
JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE,
null, new Object[]{"重新开始", "退出"}, "重新开始"
);
if (option == JOptionPane.YES_OPTION) {
resetGame();
startGameLoop();
} else {
System.exit(0);
}
});
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// 绘制背景
if (backgroundImage != null) {
// 使用高质量图像缩放
g.drawImage(backgroundImage, 0, 0, WIDTH, HEIGHT, this);
} else {
g.setColor(Color.BLACK);
g.fillRect(0, 0, WIDTH, HEIGHT);
}
// 同步绘制防止并发修改异常
List<Brick> fallingCopy;
List<Brick> bricksCopy;
synchronized (fallingBricks) {
fallingCopy = new ArrayList<>(fallingBricks);
}
synchronized (bricks) {
bricksCopy = new ArrayList<>(bricks);
}
// 绘制下落中的方块
for (Brick brick : fallingCopy) {
brick.draw(g);
}
// 绘制砖块
for (Brick brick : bricksCopy) {
if (brick.isVisible()) brick.draw(g);
}
// 绘制挡板和小球
paddle.draw(g);
ball.draw(g);
// 绘制分数和状态 - 增大字体以适应高分辨率
g.setColor(Color.WHITE);
g.setFont(new Font("Msyh", Font.BOLD, WIDTH / 40)); // 动态计算字体大小
g.drawString("得分: " + score, WIDTH / 40, HEIGHT / 20); // 动态调整位置
if (gamePaused) {
g.setColor(new Color(255, 255, 255, 180));
int pauseWidth = WIDTH / 4;
int pauseHeight = HEIGHT / 10;
g.fillRect(WIDTH/2 - pauseWidth/2, HEIGHT/2 - pauseHeight/2, pauseWidth, pauseHeight);
g.setColor(Color.BLUE);
g.drawString("游戏暂停 - 按ESC返回", WIDTH/2 - pauseWidth/3, HEIGHT/2 + pauseHeight/4);
}
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
gamePaused = !gamePaused;
if (gamePaused) {
pauseMusic(); // 暂停音乐
menuDialog.setVisible(true);
} else {
menuDialog.setVisible(false);
resumeMusic(); // 恢复音乐
}
requestFocus();
}
}
@Override public void keyTyped(KeyEvent e) {}
@Override public void keyReleased(KeyEvent e) {}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("高级打砖块游戏");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 设置全屏模式
frame.setUndecorated(true); // 移除窗口边框
frame.setExtendedState(JFrame.MAXIMIZED_BOTH); // 最大化窗口
frame.setResizable(false); // 禁止调整大小
frame.getContentPane().add(new BreakoutGame());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
// ===== 游戏实体类 =====
@Getter
class Ball {
@Setter
private int x;
@Setter
private int y;
private int dx, dy;
private final int size;
private final BufferedImage texture;
public Ball(int x, int y, int dx, int dy, int size, BufferedImage texture) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.size = size;
this.texture = texture;
}
public void move() {
x += dx;
y += dy;
}
public void reverseX() { dx = -dx; }
public void reverseY() { dy = -dy; }
public Rectangle getBounds() {
return new Rectangle(x, y, size, size);
}
public void draw(Graphics g) {
if (texture != null) {
// 使用高质量图像缩放
g.drawImage(texture, x, y, size, size, null);
} else {
g.setColor(Color.RED);
g.fillOval(x, y, size, size);
}
}
}
@Getter
class Paddle {
private int x;
private final int y;
private final int width, height;
private final BufferedImage texture;
public Paddle(int x, int y, int width, int height, BufferedImage texture) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.texture = texture;
}
public Rectangle getBounds() {
return new Rectangle(x, y, width, height);
}
public void setX(int x) {
this.x = Math.max(0, Math.min(x, BreakoutGame.WIDTH - width));
}
public void draw(Graphics g) {
if (texture != null) {
// 使用高质量图像缩放
g.drawImage(texture, x, y, width, height, null);
} else {
g.setColor(Color.GREEN);
g.fillRect(x, y, width, height);
}
}
}
@Getter
class Brick {
private final int x;
@Setter
private int y;
private final int width, height;
@Setter
private boolean visible = true;
private final BufferedImage texture;
public Brick(int x, int y, int width, int height, BufferedImage texture) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.texture = texture;
}
public Rectangle getBounds() {
return new Rectangle(x, y, width, height);
}
public void draw(Graphics g) {
if (!visible) return;
if (texture != null) {
// 使用高质量图像缩放
g.drawImage(texture, x, y, width, height, null);
} else {
g.setColor(new Color(
(x * 23) % 256,
(y * 37) % 256,
(x * y) % 256));
g.fillRect(x, y, width, height);
g.setColor(Color.WHITE);
g.drawRect(x, y, width, height);
}
}
}
屏幕自适应:
private static final Dimension SCREEN_SIZE = Toolkit.getDefaultToolkit().getScreenSize();
public static final int WIDTH = SCREEN_SIZE.width;
public static final int HEIGHT = SCREEN_SIZE.height;
动态获取屏幕尺寸,确保游戏在不同分辨率下自适应。
Guava缓存优化:
private final LoadingCache<String, BufferedImage> backgroundCache = CacheBuilder.newBuilder()
.maximumSize(10)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(new CacheLoader<>() {
public BufferedImage load(String bgName) throws Exception {
return loadBackgroundImage(bgName);
}
});
使用Guava缓存背景图,减少重复IO操作,提升性能(maximumSize
限制缓存数量,expireAfterAccess
自动清理闲置资源)。
纹理加载机制:
resources
加载图片,失败则尝试文件系统(如backgrounds/
目录),最后创建纯色后备纹理。Resources.getResource()
简化资源路径处理。多格式支持:
File[] files = musicDir.listFiles((dir, name) -> {
String lower = name.toLowerCase();
return lower.endsWith(".wav") || lower.endsWith(".mp3") || ...;
});
支持MP3/WAV等格式,通过MpegAudioFileReader
解析MP3(需javazoom
库)。
线程安全播放逻辑:
musicExecutor = Executors.newSingleThreadExecutor(...);
musicExecutor.execute(() -> {
// 解码、转换PCM格式、播放
});
单线程执行音频任务,避免阻塞主线程。LineListener
监听播放结束事件,自动切歌。
音量控制:
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float dB = (float) (Math.log(volume) / Math.log(10.0) * 20.0);
gainControl.setValue(dB);
通过对数转换实现自然音量调节。
动态对象生成:
private void spawnBrickRow() {
if (random.nextFloat() > 0.3f) return;
// 按概率生成新砖块行
}
砖块生成频率和位置随机化,增加游戏难度。
并发集合操作:
private final List<Brick> bricks = Collections.synchronizedList(new ArrayList<>());
synchronized (bricks) {
Iterator<Brick> brickIterator = bricks.iterator();
// 安全删除砖块
}
使用同步集合和显式锁,避免多线程修改冲突。
碰撞检测优化:
if (ball.getBounds().intersects(paddle.getBounds()))
ball.reverseY();
Rectangle.intersects()
快速检测矩形碰撞,效率高于像素级检测。
模态对话框:
private void initMenu() {
menuDialog = new JDialog((Frame)null, "游戏设置", true);
// 添加音量滑块、背景选择器等组件
}
模态对话框确保焦点锁定,防止游戏后台运行。
背景热切换:
backgroundSelector.addActionListener(e -> {
String selected = (String) backgroundSelector.getSelectedItem();
changeBackground(selected);
});
通过Guava缓存即时切换背景,无需重启游戏。
双缓冲技术:
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// 先绘制到内存缓冲,再一次性渲染到屏幕
}
避免画面闪烁(Swing默认双缓冲,此处显式优化)。
动态字体与布局:
g.setFont(new Font("Msyh", Font.BOLD, WIDTH / 40));
g.drawString("得分: " + score, WIDTH / 40, HEIGHT / 20);
基于屏幕尺寸计算字体大小和位置,确保高分辨率下可读性。
统一绘制接口:
public void draw(Graphics g) {
if (texture != null) {
g.drawImage(texture, x, y, width, height, null);
} else {
// 绘制纯色图形
}
}
Ball
/Paddle
/Brick
均实现draw()
方法,封装渲染细节。
运动逻辑解耦:
public void move() {
x += dx;
y += dy;
}
实体类独立处理移动,游戏循环仅调用接口。
性能优先:
扩展性设计:
用户体验优化:
健壮性保障:
此架构通过分层设计(资源层、逻辑层、渲染层)实现高内聚低耦合,适合扩展为更复杂游戏。完整代码可参考https://www.528045.com/article/b05051cd36.html和http://mp.weixin.qq.com/s?__biz=MzAxNjE2MTcyNQ==&mid=2450448537&idx=2&sn=8ab7f51ee62e382e0eaa9bb175c024cf。
dx
和 dy
是球体运动的核心控制变量:
private int dx, dy; // Ball类中的运动速度分量
public void move() {
x += dx; // 每帧水平方向移动dx个像素
y += dy; // 每帧垂直方向移动dy个像素
}
dx 控制水平运动方向
dx > 0
:球向右移动dx < 0
:球向左移动dx = 0
:球水平静止dy 控制垂直运动方向
dy > 0
:球向下移动dy < 0
:球向上移动dy = 0
:球垂直静止// 碰到左右边界时反转水平方向
public void reverseX() {
dx = -dx;
}
// 碰到上下边界或挡板时反转垂直方向
public void reverseY() {
dy = -dy;
}
// 根据屏幕尺寸动态计算初始速度
ball = new Ball(
WIDTH / 2,
HEIGHT / 2,
WIDTH / 200, // 水平速度 (dx)
-HEIGHT / 200, // 垂直速度 (dy) 负值表示向上
BALL_SIZE,
ballTexture
);
private Clip currentMusicClip; // 当前播放的音乐剪辑
private float volume = 0.7f; // 音量值 (0.0~1.0)
private boolean musicEnabled = true; // 音乐开关
private List<File> musicFiles; // 音乐文件列表
private long pausePosition; // 暂停位置记录
播放控制:
private void playNextMusic() {
musicExecutor.execute(() -> {
// 加载音频文件
currentMusicClip.open(pcmStream);
currentMusicClip.start(); // 开始播放
});
}
暂停与恢复:
private void pauseMusic() {
pausePosition = currentMusicClip.getMicrosecondPosition();
currentMusicClip.stop();
}
private void resumeMusic() {
currentMusicClip.setMicrosecondPosition(pausePosition);
currentMusicClip.start();
}
音量控制:
private void setVolume(float volume) {
FloatControl gainControl = (FloatControl)
currentMusicClip.getControl(FloatControl.Type.MASTER_GAIN);
float dB = (float) (Math.log(volume) / Math.log(10.0) * 20.0);
gainControl.setValue(dB);
}
自动切歌:
currentMusicClip.addLineListener(event -> {
if (event.getType() == LineEvent.Type.STOP) {
// 自然播放结束后切换到下一首
currentMusicIndex = (currentMusicIndex + 1) % musicFiles.size();
playNextMusic();
}
});
背景选择器设置:
// 使用Guava的ImmutableList填充下拉框
backgroundSelector = new JComboBox<>(BACKGROUND_NAMES.toArray(new String[0]));
// 添加选择事件监听器
backgroundSelector.addActionListener(e -> {
String selected = (String) backgroundSelector.getSelectedItem();
changeBackground(selected); // 更换背景
});
背景更换实现:
private void changeBackground(String bgName) {
try {
// 从Guava缓存获取背景图
backgroundImage = backgroundCache.get(bgName);
backgroundIndex = BACKGROUND_NAMES.indexOf(bgName);
repaint(); // 触发界面重绘
} catch (ExecutionException e) {
// 异常处理...
}
}
背景缓存加载:
private final LoadingCache<String, BufferedImage> backgroundCache = CacheBuilder.newBuilder()
.maximumSize(10)
.build(new CacheLoader<>() {
public BufferedImage load(String bgName) throws Exception {
// 尝试从多种来源加载背景
URL resourceUrl = Resources.getResource(bgName + ".jpg");
if (resourceUrl != null) return ImageIO.read(resourceUrl);
File bgFile = new File("backgrounds/" + bgName + ".jpg");
if (bgFile.exists()) return ImageIO.read(bgFile);
throw new IOException("背景图不存在");
}
});
最终渲染:
protected void paintComponent(Graphics g) {
// 绘制缓存中的背景图
g.drawImage(backgroundImage, 0, 0, WIDTH, HEIGHT, this);
// ...其他渲染逻辑
}
小球移动控制:
音乐控制系统:
背景切换机制:
这些机制共同实现了游戏的核心交互功能,同时保证了代码的可维护性和性能优化。