简体   繁体   English

Java swing双缓冲

[英]Java swing double buffering

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. 我刚刚开始使用双缓冲,一切正常,直到我想在屏幕上添加一个JScrollPane,以便我以后可以做一些相机移动。 Everything shows up fine (my sprites) EXCEPT the ScrollBars of the JScrollPane. 一切都很好(我的精灵)除了JScrollPane的ScrollBars之外。 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? 他们怎么没有出现在渲染? :( :(

Here is a SSCCE of the problem: 这是问题的SSCCE:

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 );

        }
    }
}

Drawing is performed on whole frame area, not on gameField . 绘图在整个框架区域上执行,而不是在gameFieldgameField Actually, the only cause of temporary JScrollPane appearing is that it calls paintImmediately somewhere in mouse-drag handler. 实际上,临时JScrollPane出现的唯一原因是它在鼠标拖动处理程序中的某处调用paintImmediately

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. 我想到的第一件事是用Canvas替换JPanel ,就像I_Love_Thinking写的那样,然后从该画布实例获取bufferStategy并将其用于渲染。 But unfortunately it not works as expected. 但不幸的是,它不能按预期工作。 Lightweight JScrollPane can't properly drive heavyweight Canvas . 轻量级JScrollPane无法正常驱动重量级Canvas Canvas refuses to draw content outside area (0, 0, viewport_width, viewport_height). Canvas拒绝在区域外绘制内容(0,0,viewport_width,viewport_height)。 This is known bug and it will not fixed. 这是已知的错误 ,它不会修复。 Mixing mixing heavyweight components with lightweight is bad idea. 混合重量级组件和轻量级是不好的主意。

Replacing JScrollPane with heavyweight ScrollPane seems working. 用重量级ScrollPane替换JScrollPane似乎有效。 Almost. 几乎。 There are noticeable rendering artifacts on scroll bars under some circumstances. 在某些情况下,滚动条上会出现明显的渲染瑕疵。

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. Canvas本身不是游戏领域,而是用于观察游戏世界的窗口。 This is well-known rendering strategy in gamedev. 这是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. offset的值可以从某个外部滚动条获取。

I suggest next changes: 我建议接下来的改变:

  1. Throw away JScrollPane 扔掉JScrollPane
  2. Add canvas directly to frame. 将画布直接添加到框架。
  3. Add single horizontal JScrollBar under canvas. 在画布下添加单个水平JScrollBar
  4. Use value of scrollbar to shift rendering. 使用滚动条的值来移动渲染。

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. 此外,示例违反了Swing线程规则并从渲染线程中读取滚动条的值。

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);
            }
        });
    }
}

And another example , mostly based on code from Java Games: Active Rendering article. 另一个例子 ,主要基于Java Games:Active Rendering文章中的代码。 It properly synchronized with value of JScrollBar and size of Canvas . 它与JScrollBar值和Canvas大小正确同步。 Also, it does painting directly to Graphics , obtained from BufferStrategy instance (instead of intermediate BuffereImage object). 此外,它直接绘制到Graphics ,从BufferStrategy实例(而不是中间BuffereImage对象)获取。 The result is something like this: 结果是这样的:

主动渲染演示

Your game paints over your scroll pane 您的游戏涂在滚动窗格上
the resizing of the window shows your scroll pane for one moment because it the normal paint-method is called (no double-buffering) 窗口的大小调整显示您的滚动窗格片刻,因为它调用了普通的paint-method(没有双缓冲)
you have some possibilities... the second one is prettier but it's your choice and i dont know what exactly you wanna do with it... 你有一些可能性......第二个更漂亮,但它是你的选择,我不知道你究竟想用它做什么......

If you wanna control all the components in your jframe yourself: 如果你想自己控制你的jframe中的所有组件:

  • override the paint(Graphics g) method and just let it empty 覆盖paint(Graphics g)方法,让它为空
  • include the scrollpane-drawing in your paint-loop (method 'run') 在paint-loop中包含scrollpane-drawing(方法'run')

If you wanna use double-buffering on another component that you dont have to manage other components directly with the double buffering. 如果你想在另一个组件上使用双缓冲,那么你不必使用双缓冲来直接管理其他组件。
The best component for this is the Canvas. 最好的组件是Canvas。 It already has methods for double-buffering you can use. 它已经有你可以使用的双缓冲方法。
Long time ago I made this, so i can't exactly tell you how it works. 很久以前我做了这个,所以我不能确切地告诉你它是如何工作的。 But just look it up, it isn't hard. 但是看看它,并不难。

Your code is manipulating Swing objects on a background thread. 您的代码正在操作后台线程上的Swing对象。 This will not work. 这不行。

Read The Event Dispatch Thread and Concurrency in Swing . 阅读Swing中事件调度线程并发

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM