如何消除 paint() 方法中的闪烁

How do you remove the flickering in the paint() method

我的闪烁问题已经有一段时间了,想知道有没有简单的方法可以解决这个问题。我知道您可以使用 BufferStrategy,但我找不到适用于 .png 图像的良好更新源。当我移动角色或调用 repaint() 方法时出现闪烁。

import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.ImageIcon;
import javax.swing.JFrame;

public class Game extends JFrame implements Runnable, KeyListener {

    private static final long serialVersionUID = 1L;

    Image back = new ImageIcon(Game.class.getResource("LEVEL1.png")).getImage();
    Image player = new ImageIcon(Game.class.getResource("freddyD.png")).getImage();
    Image studio = new ImageIcon(Game.class.getResource("studio0.png")).getImage();

    public int xPos = 100;
    public int yPos = 300;
    public boolean D = false;
    public boolean Q = false;

    public Game() {

        addKeyListener(this);
        setSize(900, 700);
        setLocationRelativeTo(null);
        setVisible(true);

    }

    public void paint(Graphics g) {

        g.drawImage(back, 0, 0, 6000, 6000, this);
        g.drawImage(studio, 100, 100, 340, 310, this);
        g.drawImage(player, xPos, yPos, 60, 104, this);

    }

    public void keyTyped(KeyEvent e) {

    }

    public void keyPressed(KeyEvent e) {

        int k = e.getKeyCode();

        if (k == KeyEvent.VK_D) {
            D = true;
        }

        if (k == KeyEvent.VK_Q) {
            Q = true;
        }
    }

    public void keyReleased(KeyEvent e) {

        int k = e.getKeyCode();

        if (k == KeyEvent.VK_D) {
            D = false;
        }

        if (k == KeyEvent.VK_Q) {
            Q = false;
        }

    }

    public void run() {

        while (true) {

            if (D) {

                xPos += 2;
                repaint();
            }

            if (Q) {

                xPos -= 2;
                repaint();
            }

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String args[]) {
        new Game().start();
    }

    public void start() {
        new Thread(this).start();
    }
}

我经常做的不需要那么多时间的事情就是增加 Thread.sleep() 的时间。我发现在低时工作的黄金数字是 15 毫秒;任何低于此的值都会开始闪烁。在您的代码中,您使用 10 毫秒,这可能是问题所在。如果您不想这样做(出于速度原因或其他原因),请使用 BufferedImage。它相当复杂,但如果你想了解更多,这里有 good site。最主要的是BufferedImage.

当您使用单个缓冲区进行绘图时,可能会出现此类问题。 检查双缓冲,它应该可以解决您的问题:https://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html

这是一个相对普遍的问题。顶级容器,如 JFrame,不是双缓冲的。这意味着对图形上下文所做的每个更改都会立即应用,这会导致闪烁。

根据一般经验,您不应该:

  • 覆盖 paint 任何组件
  • 直接绘制到顶层容器

JFrame实际上是一个复合组件,意味着它有几个其他子组件用于生成总体布局,并且由于绘制子系统的工作方式,可以绘制这些子组件独立(或不通知)框架。

首先通读 Performing Custom Painting and Painting in AWT and Swing 以了解有关 Swing 中绘画的工作原理以及您应该如何使用它的更多详细信息。

作为一般建议,您应该从 JPanel 开始并覆盖它的 paintComponent 方法(不要忘记调用 super.paintComponent

请记住,Swing 不是线程安全的,因此您需要考虑到这一点。如果帧速率不是非常关键,您可以考虑使用 Swing Timer 之类的东西而不是 Thread。 Swing Timer 将确保所有 "tick" 事件都在事件调度线程中发生,从而更安全地更新 UI(以及 UI 所依赖的状态)。

如果您需要更直接的控制,那么您需要考虑使用 BufferStrategy,它可以让您直接控制什么时候绘制,让您更好地控制绘制过程。

出于多种原因,

KeyListener 对于此类任务通常是一个糟糕的选择。相反,您应该使用 Key Bindings API 来解决这些缺点。

例如...

这只是一个简单的绘画示例,可以帮助您入门...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Game {

    public static void main(String[] args) {
        new Game();
    }

    public Game() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Rectangle hBox;
        private Rectangle vBox;

        private int hDelta = 1;
        private int vDelta = 1;

        public TestPane() {
            hBox = new Rectangle(0, 0, 10, 10);
            vBox = new Rectangle(0, 0, 10, 10);
            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    hBox.x += hDelta;
                    hBox.y = (getHeight() - 10) / 2;

                    if (hBox.x + hBox.width >= getWidth()) {
                        hBox.x = getWidth() - hBox.width;
                        hDelta *= -1;
                    } else if (hBox.x <= 0) {
                        hBox.x = 0;
                        hDelta *= -1;
                    }

                    vBox.y += vDelta;
                    vBox.x = (getWidth() - vBox.width) / 2;
                    if (vBox.y + vBox.height >= getHeight()) {
                        vBox.y = getHeight() - vBox.height;
                        vDelta *= -1;
                    } else if (vBox.y <= 0) {
                        vBox.y = 0;
                        vDelta *= -1;
                    }
                    repaint();
                }
            });
            timer.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

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

            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.RED);
            g2d.fill(hBox);
            g2d.setColor(Color.BLUE);
            g2d.fill(vBox);
            g2d.dispose();
        }

    }
}