You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java新增图像实例致动画卡顿闪烁问题求助

问题诊断与优化方案

核心问题分析

你遇到的动画变慢、闪烁问题,根源在这几个点:

  • 实时图像缩放的性能开销:每次绘制苹果时都调用getScaledInstance,这个方法会实时计算缩放逻辑,苹果数量增多后会严重拖慢渲染速度。
  • 非线程安全集合的并发访问:ArrayList不是线程安全的,添加苹果的线程和更新/渲染的线程同时操作集合,会引发遍历异常和性能损耗。
  • 不必要的同步锁:Apple的animation方法加了synchronized,但单个苹果的状态更新不需要跨线程同步,锁会增加额外性能开销。
  • 初始化逻辑错误:Apple构造函数中y = -height,此时height还未赋值,导致初始位置错误,可能引发不必要的渲染判断。

具体优化步骤

  1. 提前预缩放苹果图像
    在GamePanel初始化时就把苹果图像缩放到目标大小,避免每次绘制时重复计算。

  2. 使用线程安全的集合
    用CopyOnWriteArrayList替代ArrayList,它适合读多写少的场景,能保证多线程下集合访问的安全性。

  3. 移除不必要的同步锁
    去掉Apple类中animation方法的synchronized修饰符,单个苹果的状态更新不需要跨线程同步。

  4. 修正初始化逻辑
    在Apple构造时直接使用预缩放图像的尺寸初始化y坐标,避免初始值错误。

  5. 优化游戏循环与对象管理

    • 不在update方法中重复设置苹果速度,初始化时设置一次即可。
    • 用增强for循环遍历集合,简化代码同时避免索引操作错误。

修改后的代码

GamePanel 类

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

public class GamePanel extends JPanel {

    private final int FPS = 60;
    private final int originalSize = 16;
    private final int scale = 3;
    private final int tileSize = originalSize * scale;
    private final int maxScreenCol = 16;
    private final int maxScreenRow = 12;
    private final int screenWidth = tileSize * maxScreenCol;
    private final int screenHeight = tileSize * maxScreenRow;
    private BufferedImage scaledAppleImage; // 预缩放的苹果图像
    private Thread gameThread;
    private boolean running = true;
    private CopyOnWriteArrayList<Apple> apples; // 线程安全集合

    public GamePanel() throws IOException {
        setPanelCharacteristics();
        // 预加载并缩放苹果图像
        BufferedImage originalImage = ImageIO.read(new File("C:\\Users\\Gebruiker\\IdeaProjects\\AppleCatcher\\src\\apple.png"));
        scaledAppleImage = scaleImage(originalImage, 50, 50);
        initApples();
        startGameLoop();
    }

    private BufferedImage scaleImage(BufferedImage original, int width, int height) {
        BufferedImage scaled = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = scaled.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.drawImage(original, 0, 0, width, height, null);
        g2d.dispose();
        return scaled;
    }

    private void startGameLoop() {
        gameThread = new Thread(() -> {
            double targetTime = 1000000000.0 / FPS;
            double delta = 0;
            long lastTime = System.nanoTime();
            long currentTime;

            while (running) {
                currentTime = System.nanoTime();
                delta += (currentTime - lastTime) / targetTime;
                lastTime = currentTime;

                if (delta >= 1) {
                    update();
                    delta--;
                    repaint();
                }
            }
        });
        gameThread.start();
    }

    private void initApples() {
        apples = new CopyOnWriteArrayList<>();
        new Thread(() -> {
            while (running) {
                addApple();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    running = false;
                }
            }
        }).start();
    }

    private void setPanelCharacteristics() {
        setPreferredSize(new Dimension(screenWidth, screenHeight));
        setDoubleBuffered(true);
        setBackground(Color.WHITE); // 设置背景色,避免闪烁
    }

    private void addApple() {
        Random random = new Random();
        int randomX = random.nextInt(screenWidth - scaledAppleImage.getWidth());
        // 直接使用预缩放的图像,初始化时设置好尺寸和位置
        Apple apple = new Apple(screenWidth, screenHeight, scaledAppleImage);
        apple.setLocationX(randomX);
        apples.add(apple);
    }

    private void update() {
        for (Apple apple : apples) {
            apple.animation();
            if (apple.getIsRemoved()) {
                apples.remove(apple);
            }
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;

        for (Apple apple : apples) {
            apple.drawImage(g2d);
        }

        g2d.dispose();
    }
}

Apple 类

import java.awt.*;
import java.awt.image.BufferedImage;

public class Apple {

    private final BufferedImage appleImage;
    private final int parentHeight;
    private final int parentWidth;
    private final int width;
    private final int height;
    private int yVelocity = 2;
    private int x;
    private int y;
    private boolean isRemoved = false;

    public Apple(int parentWidth, int parentHeight, BufferedImage scaledImage) {
        this.parentHeight = parentHeight;
        this.parentWidth = parentWidth;
        this.appleImage = scaledImage;
        this.width = scaledImage.getWidth();
        this.height = scaledImage.getHeight();
        this.y = -height; // 初始化时y在屏幕上方,避免一开始就可见
    }

    public void setLocationX(int x) {
        this.x = x;
    }

    public int getLocationX() {
        return x;
    }

    public void setVelocity(int yVelocity) {
        this.yVelocity = yVelocity;
    }

    public int getWidth() {
        return width;
    }

    public void drawImage(Graphics2D g2d) {
        g2d.drawImage(appleImage, x, y, null);
    }

    public boolean getIsRemoved() {
        return isRemoved;
    }

    public void animation() {
        y += yVelocity;
        if (y >= parentHeight) {
            isRemoved = true;
        }
    }
}

额外说明

  • 双缓冲:你已经开启了setDoubleBuffered(true),这能有效减少闪烁,保留这个设置即可。
  • 线程安全:CopyOnWriteArrayList适合读多写少的场景,每次添加元素时会复制底层数组,遍历操作不会被阻塞,适合游戏中苹果添加频率低、遍历频繁的情况。
  • 图像缩放:提前用Graphics2D手动缩放,比getScaledInstance性能更好,而且可以设置渲染提示优化图像质量。

内容的提问来源于stack exchange,提问作者vasilis tomaras

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 01:07:03