Java swing双缓冲 [英] Java swing double buffering

查看:243
本文介绍了Java swing双缓冲的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚开始使用双缓冲,一切正常,直到我想在屏幕上添加一个JScrollPane,以便我以后可以做一些相机移动。一切都很好(我的精灵)除了JScrollPane的ScrollBars之外。我希望它们能够显示出来!

i just started using double buffering and everything worked just fine until i wanted to add a JScrollPane to the screen so i later on can do some camera movements. Everything shows up fine (my sprites) EXCEPT the ScrollBars of the JScrollPane. And i want them to be shown!

然而,如果我调整窗口大小,滚动条会闪烁,所以我知道它们在那里!如果我足够快,我甚至可以使用它们。他们怎么没有出现在渲染? :(

However if i resize the window, the scrollbars flicker, so i know they are there! I can even use them if i'm fast enough. How come they dont show up at rendering? :(

以下是问题的SSCCE:

Here is a SSCCE of the problem:

public class BufferStrategyDemo extends JFrame
{
    private BufferStrategy bufferStrategy;
    private JPanel gameField;
    private JScrollPane scroll;

    public BufferStrategyDemo()
    {

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);        
        this.getContentPane().setPreferredSize(new Dimension(800, 600));
        this.pack();
        this.createBufferStrategy(2);

        this.gameField = new JPanel();
        this.gameField.setPreferredSize(new Dimension( 1400, 600 ));

        this.scroll = new JScrollPane( gameField );
        this.add( scroll, BorderLayout.CENTER );

        this.setVisible( true );
        this.bufferStrategy = this.getBufferStrategy();

        setupGameFieldContent();

        new Renderer( this, scroll , bufferStrategy ).start();
    }

    // Add some contents to gameField that shows up fine
    private void setupGameFieldContent()
    {
        // For ( all my sprites )
        //      gameField.add( sprite );

    }

    private class Renderer extends Thread
    {
        private BufferStrategy bufferStrategy;
        private JFrame window;
        private JScrollPane scroll;
        private Image imageOfGameField;

        public Renderer( JFrame window, JScrollPane scroll, BufferStrategy bufferStrategy )
        {
            this.bufferStrategy = bufferStrategy;
            this.window = window;
            this.scroll = scroll;
        }

        public void run()
        {
            while ( true )
            {
                Graphics g = null;
                try 
                {
                    g = bufferStrategy.getDrawGraphics();
                    drawSprites(g);
                } 
                finally { g.dispose(); }

                bufferStrategy.show(); // Shows everything except the scrollbars..
                Toolkit.getDefaultToolkit().sync(); 

                try { Thread.sleep( 1000/60 ) ; } 
                catch(InterruptedException ie) {}
            }
        }

        private void drawSprites( Graphics g )
        {
            Graphics2D g2d=(Graphics2D)g;

            //Also tried using the JPanels (gameField) createImage with same result
            imageOfGameField = this.scroll.createImage(800, 600 ); 
            Graphics screen = (Graphics2D)imageOfGameField.getGraphics();

            /**
             * For all my sprites, draw on the image
            for ( Sprite current : sprites )
                screen.drawImage( current.getImage(), current.getX(), current.getY(), current.getWidth() , current.getHeight(), null);
            */

            g2d.drawImage( imageOfGameField, 0, 0 , null );

        }
    }
}


推荐答案

绘图是在整个框架区域上进行的,而不是在 gameField 上进行的。实际上,出现临时 JScrollPane 的唯一原因是它在鼠标拖动处理程序中的某处调用 paintImmediately

Drawing is performed on whole frame area, not on gameField. Actually, the only cause of temporary JScrollPane appearing is that it calls paintImmediately somewhere in mouse-drag handler.

首先想到的是用 Canvas JPanel >,如I_Love_Thinking所写,然后从该画布实例获取 bufferStategy 并将其用于渲染。但不幸的是,它没有按预期工作。轻量级 JScrollPane 无法正确驱动重量级 Canvas 。 Canvas拒绝在区域外绘制内容(0,0,viewport_width,viewport_height)。这是众所周知的错误,它不会修复。将重量级组件与轻量级混合使用是不错的主意。

The first thing that comes to mind is to replace JPanel with Canvas, as I_Love_Thinking wrote, then get bufferStategy from that canvas instance and use it for rendering. But unfortunately it not works as expected. Lightweight JScrollPane can't properly drive heavyweight Canvas. Canvas refuses to draw content outside area (0, 0, viewport_width, viewport_height). This is known bug and it will not fixed. Mixing mixing heavyweight components with lightweight is bad idea.

用重量级替换 JScrollPane ScrollPane 似乎正在运作。几乎。在某些情况下,滚动条上会出现明显的渲染瑕疵。

Replacing JScrollPane with heavyweight ScrollPane seems working. Almost. There are noticeable rendering artifacts on scroll bars under some circumstances.

此时,我决定停止敲打墙并放弃。滚动窗格是众多问题的根源,不值得使用延迟滚动。是时候从另一个视图看滚动了。 Canvas 不是游戏领域本身,而是我们用来观察游戏世界的窗口。这是gamedev中众所周知的渲染策略。画布的大小与可观察区域完全相同。当需要滚动时,我们只是用一些偏移来渲染世界。 offset的值可以从某个外部滚动条中获取。

At this point, I've decided to stop beating the wall and give up. Scroll panes are the source of numerous problems and not worth to use for lazy scrolling. It is time to look on scrolling from another view. The Canvas is not game field itself, but window that we use to observe game world. This is well-known rendering strategy in gamedev. The size of canvas is exactly as observable area. When scrolling needed, we just render the world with some offset. The value for offset may be taken from some external scrollbar.

我建议接下来的更改:


  1. 扔掉 JScrollPane

  2. 直接将画布添加到画面。

  3. 添加单曲水平 JScrollBar 在画布下。

  4. 使用滚动条的值来移动渲染。

  1. Throw away JScrollPane
  2. Add canvas directly to frame.
  3. Add single horizontal JScrollBar under canvas.
  4. Use value of scrollbar to shift rendering.

该示例基于您的代码。此实现不考虑帧的大小调整。在现实生活中,一些地方需要与视口的大小同步(滚动窗格之前为我们所做的)。此外,示例违反了Swing线程规则并从呈现线程中读取滚动条的值。

The example is based on your code. This implementation not respects resizing of frame. In real life some places need to be synchronized with size of viewport (that what scrollpane did before for us). Also, example violates Swing threading rules and reads value of scrollbar from rendering thread.

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JScrollBar;

public class BufferStrategyDemo extends JFrame {
    private BufferStrategy bufferStrategy;
    private Canvas gameField;
    private JScrollBar scroll;

    public BufferStrategyDemo() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().setLayout(new BorderLayout());
        getContentPane().setPreferredSize(new Dimension(800, 600));

        gameField = new Canvas();
        gameField.setIgnoreRepaint(true);
        gameField.setPreferredSize(new Dimension(800, 580));
        getContentPane().add(gameField, BorderLayout.CENTER);

        scroll = new JScrollBar(JScrollBar.HORIZONTAL);
        scroll.setPreferredSize(new Dimension(800, 20));
        scroll.setMaximum(1400 - 800); // image width - viewport width
        getContentPane().add(scroll, BorderLayout.SOUTH);

        this.pack();

        gameField.createBufferStrategy(2);
        bufferStrategy = gameField.getBufferStrategy();

        new Renderer().start();
    }

    private class Renderer extends Thread {
        private BufferedImage imageOfGameField;

        public Renderer() {
            // NOTE: image size is fixed now, but better to bind image size to the size of viewport
            imageOfGameField = new BufferedImage(1400, 580, BufferedImage.TYPE_INT_ARGB);
        }

        public void run() {
            while (true) {
                Graphics g = null;
                try {
                    g = bufferStrategy.getDrawGraphics();
                    drawSprites(g);
                } finally {
                    g.dispose();
                }

                bufferStrategy.show(); 
                Toolkit.getDefaultToolkit().sync();

                try {
                    Thread.sleep(1000 / 60);
                } catch (InterruptedException ie) {
                }
            }
        }

        private void drawSprites(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;

            Graphics g2d2 = imageOfGameField.createGraphics();
            g2d2.setColor(Color.YELLOW); // clear background
            g2d2.fillRect(0, 0, 1400, 580); // again, fixed width/height only for SSCCE
            g2d2.setColor(Color.BLACK);

            int shift = -scroll.getValue(); // here it is - get shift value

            g2d2.fillRect(100 + shift, 100, 20, 20); // i am ugly black sprite
            g2d2.fillRect(900 + shift, 100, 20, 20); // i am other black sprite
                                                     // located outside of default view

            g2d.drawImage(imageOfGameField, 0, 0, null);

            g2d2.dispose();
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                BufferStrategyDemo mf = new BufferStrategyDemo();
                mf.setVisible(true);
            }
        });
    }
}

另一个例子,主要基于来自 Java Games:Active Rendering 文章。它与 JScrollBar 的值以及 Canvas 的大小正确同步。此外,它直接绘制到 Graphics ,从 BufferStrategy 实例获取(而不是中间 BuffereImage object)。结果是这样的:

And another example, mostly based on code from Java Games: Active Rendering article. It properly synchronized with value of JScrollBar and size of Canvas. Also, it does painting directly to Graphics, obtained from BufferStrategy instance (instead of intermediate BuffereImage object). The result is something like this:

这篇关于Java swing双缓冲的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆