Java Swing двойная буферизация - PullRequest
3 голосов
/ 04 марта 2012

Я только начал использовать двойную буферизацию, и все работало очень хорошо, пока я не захотел добавить JScrollPane на экран, чтобы позже я мог делать некоторые движения камеры. Все показывает хорошо (мои спрайты) за исключением полос прокрутки JScrollPane. И я хочу, чтобы их показали!

Однако, если я изменю размер окна, полосы прокрутки мерцают, поэтому я знаю, что они есть! Я даже могу использовать их, если я достаточно быстр. Почему они не появляются при рендеринге? (

Вот 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 );

        }
    }
}

Ответы [ 3 ]

5 голосов
/ 04 марта 2012

Рисование выполняется по всей области кадра, а не по gameField.На самом деле, единственная причина появления временного JScrollPane заключается в том, что он вызывает paintImmediately где-то в обработчике перетаскивания мышью.

Первое, что приходит на ум - заменить JPanel на CanvasI_Love_Thinking написал, затем получите bufferStategy из этого экземпляра canvas и используйте его для рендеринга.Но, к сожалению, это не работает, как ожидалось.Легкий вес JScrollPane не может нормально управлять тяжелым весом Canvas.Canvas отказывается рисовать контент вне области (0, 0, viewport_width, viewport_height).Это известная ошибка 1012 *, и она не будет исправлена.Смешивать смешанные тяжелые компоненты с легкими - плохая идея.

Замена JScrollPane на тяжелые ScrollPane, кажется, работает.Почти.При некоторых обстоятельствах на полосах прокрутки появляются заметные артефакты рендеринга.

На данный момент я решил прекратить бить стену и сдаться.Панели прокрутки являются источником многочисленных проблем, и их не стоит использовать для ленивой прокрутки.Пришло время взглянуть на прокрутку из другого ракурса.Canvas не само игровое поле , а окно, которое мы используем для наблюдения за игровым миром.Это хорошо известная стратегия рендеринга в gamedev.Размер холста точно такой же, как наблюдаемая область.Когда нужна прокрутка, мы просто визуализируем мир с некоторым смещением.Значение для смещения может быть взято с некоторой внешней полосы прокрутки.

Я предлагаю следующие изменения:

  1. Выброс JScrollPane
  2. Добавление холста непосредственно в рамку.
  3. Добавьте одну горизонтальную JScrollBar под холст.
  4. Используйте значение полосы прокрутки для сдвига рендеринга.

Пример основан на вашем коде.Эта реализация не учитывает изменение размера кадра.В реальной жизни некоторые места необходимо синхронизировать с размером области просмотра (то, что раньше делала для нас панель прокрутки).Также, пример нарушает правила потоков 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);
            }
        });
    }
}

И еще один пример , в основном на основе кода из Java Games: Active Rendering статьи.Он правильно синхронизирован со значением JScrollBar и размером Canvas.Кроме того, он рисует непосредственно в Graphics, полученный из BufferStrategy экземпляра (вместо промежуточного BuffereImage объекта).Результат примерно такой:

Active Rendering Demo

1 голос
/ 04 марта 2012

Ваш код манипулирует объектами Swing в фоновом потоке. Это не будет работать.

Читать Поток отправки событий и Параллелизм в Swing .

1 голос
/ 04 марта 2012

Ваша игра закрашивает вашу область прокрутки
изменение размера окна показывает вашу панель прокрутки на один момент, потому что при этом вызывается обычный метод рисования (без двойной буферизации)
у вас есть некоторые возможности ...второй красивее, но это ваш выбор, и я не знаю, что именно вы хотите с ним делать ...

Если вы хотите сами контролировать все компоненты в вашем jframe:

  • переопределите метод paint (Graphics g) и просто дайте ему опустеть
  • включите рисунок прокрутки в ваш цикл рисования (метод 'run')

Если вы хотите использовать double-буферизация на другом компоненте, который вам не нужно управлять другими компонентами напрямую с двойной буферизацией.
Лучшим компонентом для этого является Canvas.У него уже есть методы для двойной буферизации, которые вы можете использовать.
Давным-давно я сделал это, поэтому я не могу точно сказать вам, как это работает.Но просто посмотрите, это не сложно.

...