Java JPanel闪烁问题

Java JPanel闪烁问题,java,swing,jpanel,Java,Swing,Jpanel,对于我的游戏,我在java.awt.Image上绘制图像,然后将图像绘制到JPanel上。我这样做有两个原因,主要是因为我不想让游戏渲染占用EDT上的cpu周期,以及为了可移植性 出现了一个问题,在使用时导致java.awt.JPanel上闪烁 图形#绘图图像(图像img、int x、int y、int width、int height、, 图像观察者(观察员) 但是, 图形#绘图图像(图像img、int x、int y、ImageObserver) 没有导致此问题 这是我的密码: Sandb

对于我的游戏,我在
java.awt.Image
上绘制图像,然后将图像绘制到JPanel上。我这样做有两个原因,主要是因为我不想让游戏渲染占用EDT上的cpu周期,以及为了可移植性

出现了一个问题,在使用时导致
java.awt.JPanel
上闪烁

图形#绘图图像(图像img、int x、int y、int width、int height、, 图像观察者(观察员)

但是,

图形#绘图图像(图像img、int x、int y、ImageObserver)

没有导致此问题

这是我的密码:

Sandbox.java

public class Sandbox implements Paintable {

public static void main(String[] args) throws Exception {
    GameWindow window = new GameWindow("Test", 800, 600);
    GameScreen screen = new GameScreen(800, 600);
    Sandbox sandbox = new Sandbox();

    window.add(screen);
    window.setVisible(true);

    boolean running = true;
    while(running) {
        sandbox.update();
        screen.getPaintBuffer().clear();
        screen.getPaintBuffer().paint(sandbox);
        screen.repaint();
        Thread.sleep(1000 / 60);
    }

}

private int x = 0, y = 0;

public void update() {
    x++;
    y++;
}

public void paint(Graphics g) {
    g.drawRect(x, y, 50, 50);
}

}
GameWindow.java

public class GameWindow extends JFrame {

public GameWindow(String title, int width, int height) {
    setTitle(title);
    setSize(width, height);
    setResizable(false);
    setLocationByPlatform(true);
    setDefaultCloseOperation(3);
}

}
GameScreen.java

public class GameScreen extends JPanel {

private ImageBuffer buffer;

public GameScreen(int width, int height) {
    buffer = new ImageBuffer(width, height);
}

public void paint(Graphics g) {
    g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), null);
}

public ImageBuffer getPaintBuffer() {
    return buffer;
}

}
Paintable.java

public interface Paintable {

public void paint(Graphics g);

}
ImageBuffer.java

public class ImageBuffer {

private final Image buffer;
private int width, height;

public ImageBuffer(int width, int height) {
    buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    this.width = width;
    this.height = height;
}

public void paint(Paintable paintable) {
    paintable.paint(buffer.getGraphics());
}

public void clear() {
    buffer.getGraphics().clearRect(0, 0, width, height);
}

public Image getBuffer() {
    return buffer;
}

}

GameScreen
paint
方法更改为

  • 替代
    paintComponent
  • 调用
    super.paintComponent
    ,以维护油漆链的合同
  • 将此
    作为
    ImageObsever
    参数传递给
    drawImage
  • 例如

    public class GameScreen extends JPanel {
    
        private ImageBuffer buffer;
    
        public GameScreen(int width, int height) {
            buffer = new ImageBuffer(width, height);
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
        }
    
        public ImageBuffer getPaintBuffer() {
            return buffer;
        }
    
    }
    
    查看和了解有关绘画工作原理的更多详细信息

    已更新

    基本问题是缩放问题

    您用来绘制的图像是
    800x600
    ,但
    游戏屏幕实际上是
    794x572
    (在我的电脑上),这会导致图像缩放。现在,Swing的默认缩放并不漂亮,通常是基于速度而不是质量

    现在,有很多方法可以改进这一点,但是一个快速的方法是应用一些更高级的渲染提示,例如

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        g2d.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
        g2d.dispose();
    }
    
    现在,我已经做得太过火了,所以你可能想删除一些,看看有什么变化

    已更新

    渲染提示可能会减慢渲染过程,因此更好的解决方案可能是覆盖
    GameScreen
    getPreferredSize
    方法并返回预期大小

    public static class GameScreen extends JPanel {
    
        private ImageBuffer buffer;
        private Dimension size;
    
        public GameScreen(int width, int height) {
            buffer = new ImageBuffer(width, height);
            this.size = new Dimension(width, height);
        }
    
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(800, 600);
        }
    
    然后,只需调用
    pack

    GameWindow window = new GameWindow("Test");
    GameScreen screen = new GameScreen(800, 600);
    Sandbox sandbox = new Sandbox();
    
    window.add(screen);
    window.pack();
    window.setVisible(true);
    

    这样,每个人的大小都相同

    GameScreen
    paint
    方法更改为

  • 替代
    paintComponent
  • 调用
    super.paintComponent
    ,以维护油漆链的合同
  • 将此
    作为
    ImageObsever
    参数传递给
    drawImage
  • 例如

    public class GameScreen extends JPanel {
    
        private ImageBuffer buffer;
    
        public GameScreen(int width, int height) {
            buffer = new ImageBuffer(width, height);
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
        }
    
        public ImageBuffer getPaintBuffer() {
            return buffer;
        }
    
    }
    
    查看和了解有关绘画工作原理的更多详细信息

    已更新

    基本问题是缩放问题

    您用来绘制的图像是
    800x600
    ,但
    游戏屏幕实际上是
    794x572
    (在我的电脑上),这会导致图像缩放。现在,Swing的默认缩放并不漂亮,通常是基于速度而不是质量

    现在,有很多方法可以改进这一点,但是一个快速的方法是应用一些更高级的渲染提示,例如

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        g2d.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
        g2d.dispose();
    }
    
    现在,我已经做得太过火了,所以你可能想删除一些,看看有什么变化

    已更新

    渲染提示可能会减慢渲染过程,因此更好的解决方案可能是覆盖
    GameScreen
    getPreferredSize
    方法并返回预期大小

    public static class GameScreen extends JPanel {
    
        private ImageBuffer buffer;
        private Dimension size;
    
        public GameScreen(int width, int height) {
            buffer = new ImageBuffer(width, height);
            this.size = new Dimension(width, height);
        }
    
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(800, 600);
        }
    
    然后,只需调用
    pack

    GameWindow window = new GameWindow("Test");
    GameScreen screen = new GameScreen(800, 600);
    Sandbox sandbox = new Sandbox();
    
    window.add(screen);
    window.pack();
    window.setVisible(true);
    

    这样,每个人都是一样大小的

    我会说这是一个侥幸,一个成功了,另一个没有发生过一次,它们不容易复制,这个错误是。伙计,如果你对此有信心,那么提交一份错误报告我会说这是一个侥幸,一个成功了,另一个没有发生过一次,它们不容易复制,这个错误是。伙计,如果您对此很有信心,那么就提交一份bug报告。我尝试过使用paintComponent,但它不会改变任何事情。我不想使用ImageObserver,这与任何方面都无关。另外,我不调用super.paint(g),因为我不打算向这个类添加任何组件。不管怎样,这些更改都不相关,也无法解决我的问题。@ChrisGray绘制比“绘制”组件更复杂。你有责任维护油漆链,否则奇怪和美妙的事情可能会出错。
    ImageObsever
    向API提供了一些相关信息,以便在
    Image
    发生更改时组件可以自行更新。这是一个很好的实践,让他们都明白这一点。我在秋千和AWT上画画已经有一段时间了。现在,这个图像被用作缓冲区,我不希望它在完成之前被显示或重新绘制到屏幕上。每次更改图像时更新JPanel都是对处理能力的浪费,没有必要这样做。有些时候,那些“良好的实践”应该被忽略。当然,只要你不介意什么时候出错。你应该考虑使用<代码> BufferStrategy <代码>,并对绘画过程进行完全控制,这就是它的设计目的。无论如何,您的问题不是bug,而是理解错误或看得太多,两个
    drawImage
    方法之间的差异,以及可能忘记了
    JFrame
    在框架的边界内绘制窗口装饰,使其内容小于实际窗口边界。你可以通过在
    游戏屏幕中使用
    getPreferredSize
    来解决这个问题,我知道,我选择不使用画布和缓冲策略(我不想在评论中列出我的所有原因)。我还可以调用getContentPane().setPreferredSize(新维度(宽度、高度);然后调用pack().但为了简单起见,所以我可以很快写出来,我选择了退出。我尝试过使用paintComponent,但它不会改变任何东西。我不想使用ImageObserver,这与任何方面都不相关。此外,我也不叫super.paint(g)be