Java Double Buffering
If you want more control over when the window is updated and to take advantage of hardware page flipping (if available), you can use the BufferStrategy
class.
Your Draw
method would then look something like this:
@Override
protected void Draw() {
BufferStrategy bs = getBufferStrategy();
Graphics g = bs.getDrawGraphics(); // acquire the graphics
// draw stuff here
bs.show(); // swap buffers
}
The downside is that this approach does not mix well with event-driven rendering. You generally have to choose one or the other. Also getBufferStrategy
is implemented only in Canvas
and Window
making it incompatible with Swing components.
Tutorials can be found here, here and here.
Double Buffer a JFrame
Override the JPanel's paintComponent() Method and paint the content into a BufferedImage image first. Once done, copy the content of the BufferedImage into the graphics context you get from paintComponent().
protected void paintComponent(Graphics g)
{
BufferedImage bufferedImage = new BufferedImage(500, 500, BufferedImage.TYPE_ARGB);
Graphics2D g2d = bufferedImage.createGraphics();
//paint using g2d ...
Graphics2D g2dComponent = (Graphics2D) g;
g2dComponent.drawImage(bufferedImage, null, 0, 0);
}
Double Buffering in Java
The basic idea of Double Buffering is to create the image off screen then display it all at once.
From the java tutorials found here
The code you have there first creates an image on first way through to be your "Back Buffer" with this bit, i is likely a field such as
private Image i;
private Graphics graph;
if(i==null)
{
i=createImage(getWidth(), getHeight());
graph=i.getGraphics();
}
Then Paints the background color onto the image with this
graph.setColor(getBackground());
graph.fillRect(0, 0, getWidth(),getHeight());
Then sets the front ready for drawing.
graph.setColor(getForeground());
paint(graph); /draws
Finally drawing the back Buffer over to the primary surface.
g.drawImage(i,0,0,this);
Java swing double buffering
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.
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.
Replacing JScrollPane
with heavyweight ScrollPane
seems 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 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.
I suggest next changes:
- Throw away
JScrollPane
- Add canvas directly to frame.
- Add single horizontal
JScrollBar
under canvas. - 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.
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. 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:
Related Topics
How to Compress a String in Java
"Main Method Not Found" Error When Starting Program
How to Append a Node to an Existing Xml File in Java
How to Exit a While Loop in Java
String Parsing in Java with Delimiter Tab "\T" Using Split
How to Retrieve a List of Available/Installed Fonts in Android
Do We Need Volatile When Implementing Singleton Using Double-Check Locking
How to Show Changes Between Commits with Jgit
Why in Java Enum Is Declared as Enum<E Extends Enum<E>>
Best Way to Create a Hashmap of Arraylist
Add Jlabel with Image to Jlist to Show All the Images
Jackson Change JSONignore Dynamically
Scanner Only Reads File Name and Nothing Else
Why Is "Out of Range" Not Thrown for 'Substring(Startindex, Endindex)'