Java实现项目1——弹射球游戏

项目:弹射球游戏

项目描述:

类似于乒乓球的游戏,游戏可以播放背景音乐,可以更换背景图,当小球碰到下面的挡板后会反弹,当小球碰到方块后会增加分数,当小球掉落会导致游戏失败,按下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);
        }
    }
}

代码架构与原理分析

1. 游戏初始化与资源加载
  • 屏幕自适应

    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/目录),最后创建纯色后备纹理。
    • 使用Guava的Resources.getResource()简化资源路径处理。

2. 音频系统设计
  • 多格式支持

    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);  
    

    通过对数转换实现自然音量调节。


3. 游戏逻辑与并发控制
  • 动态对象生成

    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()快速检测矩形碰撞,效率高于像素级检测。


4. 菜单与用户交互
  • 模态对话框

    private void initMenu() {  
        menuDialog = new JDialog((Frame)null, "游戏设置", true);  
        // 添加音量滑块、背景选择器等组件  
    }  
    

    模态对话框确保焦点锁定,防止游戏后台运行。

  • 背景热切换

    backgroundSelector.addActionListener(e -> {  
        String selected = (String) backgroundSelector.getSelectedItem();  
        changeBackground(selected);  
    });  
    

    通过Guava缓存即时切换背景,无需重启游戏。


5. 渲染与性能优化
  • 双缓冲技术

    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);  
    

    基于屏幕尺寸计算字体大小和位置,确保高分辨率下可读性。


6. 实体类设计
  • 统一绘制接口

    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;  
    }  
    

    实体类独立处理移动,游戏循环仅调用接口。


关键设计思想总结

  1. 性能优先

    • Guava缓存减少IO开销
    • 并发集合保证线程安全
    • 矩形碰撞检测替代复杂计算
  2. 扩展性设计

    • 背景/音频资源动态加载
    • 实体类与游戏逻辑分离
  3. 用户体验优化

    • 全屏自适应布局
    • 音量/背景实时切换
    • 暂停菜单防止误操作
  4. 健壮性保障

    • 资源加载失败时生成后备纹理
    • 音频格式转换兼容不同系统
    • 显式同步避免多线程冲突

此架构通过分层设计(资源层、逻辑层、渲染层)实现高内聚低耦合,适合扩展为更复杂游戏。完整代码可参考https://www.528045.com/article/b05051cd36.html和http://mp.weixin.qq.com/s?__biz=MzAxNjE2MTcyNQ==&mid=2450448537&idx=2&sn=8ab7f51ee62e382e0eaa9bb175c024cf。

1. dx 和 dy 的含义与小球的移动控制

dxdy 是球体运动的核心控制变量:

private int dx, dy; // Ball类中的运动速度分量

public void move() {
    x += dx; // 每帧水平方向移动dx个像素
    y += dy; // 每帧垂直方向移动dy个像素
}
工作原理:
  1. dx 控制水平运动方向

    • dx > 0:球向右移动
    • dx < 0:球向左移动
    • dx = 0:球水平静止
  2. 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
);

2. 音乐播放控制机制

核心实现:
private Clip currentMusicClip; // 当前播放的音乐剪辑
private float volume = 0.7f; // 音量值 (0.0~1.0)
private boolean musicEnabled = true; // 音乐开关
private List<File> musicFiles; // 音乐文件列表
private long pausePosition; // 暂停位置记录
控制流程:
  1. 播放控制

    private void playNextMusic() {
        musicExecutor.execute(() -> {
            // 加载音频文件
            currentMusicClip.open(pcmStream);
            currentMusicClip.start(); // 开始播放
        });
    }
    
  2. 暂停与恢复

    private void pauseMusic() {
        pausePosition = currentMusicClip.getMicrosecondPosition();
        currentMusicClip.stop();
    }
    
    private void resumeMusic() {
        currentMusicClip.setMicrosecondPosition(pausePosition);
        currentMusicClip.start();
    }
    
  3. 音量控制

    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);
    }
    
  4. 自动切歌

    currentMusicClip.addLineListener(event -> {
        if (event.getType() == LineEvent.Type.STOP) {
            // 自然播放结束后切换到下一首
            currentMusicIndex = (currentMusicIndex + 1) % musicFiles.size();
            playNextMusic();
        }
    });
    

3. 通过窗口更换背景图

实现流程:
用户 背景选择器 游戏主类 Guava缓存 资源加载器 界面渲染 paintComponent 选择背景名称 触发changeBackground事件 获取背景图 检查背景是否存在 返回BufferedImage 返回缓存图片 调用repaint() 重绘背景 用户 背景选择器 游戏主类 Guava缓存 资源加载器 界面渲染 paintComponent
关键代码:
  1. 背景选择器设置

    // 使用Guava的ImmutableList填充下拉框
    backgroundSelector = new JComboBox<>(BACKGROUND_NAMES.toArray(new String[0]));
    
    // 添加选择事件监听器
    backgroundSelector.addActionListener(e -> {
        String selected = (String) backgroundSelector.getSelectedItem();
        changeBackground(selected); // 更换背景
    });
    
  2. 背景更换实现

    private void changeBackground(String bgName) {
        try {
            // 从Guava缓存获取背景图
            backgroundImage = backgroundCache.get(bgName);
            backgroundIndex = BACKGROUND_NAMES.indexOf(bgName);
            repaint(); // 触发界面重绘
        } catch (ExecutionException e) {
            // 异常处理...
        }
    }
    
  3. 背景缓存加载

    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("背景图不存在");
            }
        });
    
  4. 最终渲染

    protected void paintComponent(Graphics g) {
        // 绘制缓存中的背景图
        g.drawImage(backgroundImage, 0, 0, WIDTH, HEIGHT, this);
        // ...其他渲染逻辑
    }
    

总结特点

  1. 小球移动控制

    • 基于增量运动模型(dx/dy)
    • 物理模拟通过速度分量反转实现
    • 速度值根据屏幕尺寸动态计算
  2. 音乐控制系统

    • 使用独立线程播放避免阻塞
    • 通过Clip位置记录实现精确暂停/继续
    • 对数计算实现自然音量调节
  3. 背景切换机制

    • Guava缓存实现高效资源管理
    • 多来源加载策略(resources → 文件系统)
    • 实时刷新机制(repaint触发即时更新)

这些机制共同实现了游戏的核心交互功能,同时保证了代码的可维护性和性能优化。

你可能感兴趣的:(Java实战项目,java,游戏,windows)