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? 🙁
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 );
}
}
}
Drawing is performed on whole frame area, not on
gameField. Actually, the only cause of temporaryJScrollPaneappearing is that it callspaintImmediatelysomewhere in mouse-drag handler.The first thing that comes to mind is to replace
JPanelwithCanvas, as I_Love_Thinking wrote, then getbufferStategyfrom that canvas instance and use it for rendering. But unfortunately it not works as expected. LightweightJScrollPanecan’t properly drive heavyweightCanvas. 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.Replacing
JScrollPanewith heavyweightScrollPaneseems working. 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
Canvasis 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.I suggest next changes:
JScrollPaneJScrollBarunder canvas.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.
And another example, mostly based on code from Java Games: Active Rendering article. It properly synchronized with value of
JScrollBarand size ofCanvas. Also, it does painting directly toGraphics, obtained fromBufferStrategyinstance (instead of intermediateBuffereImageobject). The result is something like this: