如何修复位于另一个预绘 JPanel 之上的 JPanel 上动态移动图像的闪烁?

How to fix flickering of dynamic moving images over a JPanel that is on top of another pre-painted JPanel?

我在 JFrame 中制作了 2 个 JPanels、Panel 和 BackGround。我在 10 毫秒后动态绘制面板(使用计时器),但背景仅在游戏开始时绘制一次。 Panel负责战机(飞船)、弹丸和外星人的展示。 BackGround 负责显示非动态的背景场景。 paintComponent(Graphics) 方法确实绘制了战斗机和射弹,但在更新时会闪烁。谁能找到原因。

这是我的相框:

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Frame extends JFrame {
    private static final long serialVersionUID = 1L;

    public static final int WIDTH = 1280;
    public static final int HEIGHT = 720;

    public static final int DELAY = 10;

    private Panel panel;
    private Background bg;

    public Frame() {

        panel = new Panel();
        bg = new Background();

        initComponents();
    }

    public void initComponents() {

        this.setSize(WIDTH, HEIGHT);

        this.add(bg);
        this.add(panel);

        this.setLocationRelativeTo(null);
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        this.addKeyListener(new KeyAdapter() {

            @Override
            public void keyPressed(KeyEvent e) {
                if(e.getKeyCode() == KeyEvent.VK_LEFT) panel.moveLeft();
                else if(e.getKeyCode() == KeyEvent.VK_RIGHT) panel.moveRight();
            }

        });

    }

    public static void main(String args[]) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Frame().setVisible(true);
            }
        });

    }

}

这是我的面板:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Panel extends JPanel implements ActionListener {
    private static final long serialVersionUID = 1L;

    public static final int DELAY = Frame.DELAY;

    private Timer timer;
    private BufferedImage fighter;

    int x, y;

    public Panel() {

        timer = new Timer(DELAY, this);
        try {
            fighter = ImageIO.read(this.getClass().getResource("fighter.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        initComponents();

        timer.start();
    }

    public void initComponents() {

        this.setSize(Frame.WIDTH, Frame.HEIGHT);
        this.setDoubleBuffered(true);
        this.setBackground(new Color(0, 0, 0, 0));

        x = 150;
        y = 200;

    }


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

        Graphics2D g2d = (Graphics2D) g;
        doDrawing(g2d);

    }



    private void doDrawing(Graphics2D g2d) {

        g2d.drawImage(fighter, x, y, null);

    }

    @Override
    public void actionPerformed(ActionEvent arg0) {

        this.repaint();
    }

    public void moveLeft() {
        x -= 10;
    }

    public void moveRight() {
        x += 10;
    }

}

这是背景:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.Timer;


public class Background extends JPanel implements ActionListener{
    private static final long serialVersionUID = 1L;

    private BufferedImage backGround;
    private Timer timer;
    public Background() {

        this.setSize(Frame.WIDTH, Frame.HEIGHT);
        this.setBackground(new Color(0, 0, 0, 0));

        timer = new Timer(Frame.DELAY, this);

        try {
            backGround = ImageIO.read(this.getClass().getResource("background.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        timer.start();

    }

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

        g.drawImage(backGround, 0, 0, null);

    }

    @Override
    public void actionPerformed(ActionEvent e) {

        this.repaint();

    }

}

我希望精灵不会闪烁并且不会滞后(经过大量试验后不会发生)。

  1. 不要在绘画方法中调用重绘
  2. 摆脱背景 class 并在 one JPanel 中完成所有绘图。例如:
  3. 参见下面的示例以及 MCVE 设计
    • 整个事情可以 copy/pasted 变成 IDE 和 运行
    • 使用所有在线可用的图像,而不是磁盘上的图像
  4. 我还会删除简单调用 repaint() 的计时器,而是
    • 从 KeyListener 中调用重绘
    • 或使用计时器来执行实际的精灵移动代码(使用 repaint())。如果你想要连续移动,这将很有用

MCVE 示例:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.*;

public class Frame1 extends JFrame {
    // Image attribution:
    // By Adam Evans - M31, the Andromeda Galaxy (now with h-alpha)
    // Uploaded by NotFromUtrecht, CC BY 2.0,
    // https://commons.wikimedia.org/w/index.php?curid=12654493
    public static final String ANDROMEDA_IMAGE = "https://upload.wikimedia.org/wikipedia/commons/"
            + "thumb/9/98/Andromeda_Galaxy_%28with_h-alpha%29.jpg/"
            + "1280px-Andromeda_Galaxy_%28with_h-alpha%29.jpg";
    public static final String SPRITE_IMAGE = "https://upload.wikimedia.org/wikipedia/commons/"
            + "thumb/a/a1/Glossy_3d_blue_blue2.png/" + "120px-Glossy_3d_blue_blue2.png";

    private static final long serialVersionUID = 1L;
    public static final int WIDTH = 1280;
    public static final int HEIGHT = 720;
    public static final int DELAY = 10;
    private Panel1 panel;
    // private Background bg;

    public Frame1() {
        panel = new Panel1();
        // bg = new Background();
        initComponents();
    }

    public void initComponents() {
        this.setSize(WIDTH, HEIGHT);
        // this.add(bg);
        this.add(panel);
        this.setLocationRelativeTo(null);
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_LEFT)
                    panel.moveLeft();
                else if (e.getKeyCode() == KeyEvent.VK_RIGHT)
                    panel.moveRight();
            }
        });
    }

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Frame1().setVisible(true);
            }
        });
    }
}

class Panel1 extends JPanel implements ActionListener {
    private static final long serialVersionUID = 1L;
    public static final int DELAY = Frame1.DELAY;
    private Timer timer;
    private BufferedImage fighter;
    private BufferedImage background;
    int x, y;

    public Panel1() {
        timer = new Timer(DELAY, this);
        try {
            URL url = new URL(Frame1.SPRITE_IMAGE);
            // fighter = ImageIO.read(this.getClass().getResource("fighter.png"));
            fighter = ImageIO.read(url);

            url = new URL(Frame1.ANDROMEDA_IMAGE);
            background = ImageIO.read(url);

        } catch (IOException e) {
            e.printStackTrace();
        }
        initComponents();
        timer.start();
    }

    public void initComponents() {
        this.setSize(Frame1.WIDTH, Frame1.HEIGHT);
        this.setDoubleBuffered(true);
        this.setBackground(new Color(0, 0, 0, 0));
        x = 150;
        y = 200;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(background, 0, 0, this);
        g.drawImage(fighter, x, y, this);
    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
        this.repaint();
    }

    public void moveLeft() {
        x -= 10;
    }

    public void moveRight() {
        x += 10;
    }
}