Sunday, March 8, 2015

Double Buffering with JFrames

Java games using the Swing library can oftentimes be tricky to render and implement, so here's an example of a very basic technique called double buffering that should simplify things.


import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferStrategy;

public class DoubleBufferTest {


    public Canvas canvas;
    public boolean running = true;
    public BufferStrategy strategy;
    public int width = 600;
    public int height = 600;

    public DoubleBufferTest() {

        initAndRunUI();

    }

    public static void main(String[] args) {
        DoubleBufferTest test = new DoubleBufferTest();
    }

    public void initAndRunUI() {

        JFrame frame = new JFrame("Double Buffer Test");

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        canvas = new Canvas();

        canvas.setPreferredSize(new Dimension(width, height));

        frame.add(canvas);

        frame.pack();

        frame.setVisible(true);

        run();

    }

    public void run() {

        canvas.createBufferStrategy(2);

        strategy = canvas.getBufferStrategy();

        while (running) {

            render();

            try {

                Thread.sleep(10);

            } catch (Exception e) {

                e.printStackTrace();

            }
        }

    }

    public void render() {

        Graphics g = strategy.getDrawGraphics();

        Graphics2D g2d = (Graphics2D) g;

        g2d.fillRect(0, 0, width, height);

        strategy.show();

        g2d.dispose();
        g.dispose();

    }

}

Okay! Our program will open a JFrame that renders a black screen, which is a great start
for any game. The code is a lot to take in, but here's the breakdown of what's happening:

The heart of our program is our run() method, which actually renders our screen. If you'll
notice, our screen is just a canvas we paint on. Some techniques involve having our
main class extend JPanel and add itself to the JFrame, but I find that using a Canvas is
generally more stable and easier to work with in the long run with larger projects.

The render() method is fairly simple, we just refresh our graphics every time by calling
strategy.getDrawGraphics(), which of course requires that at the start of our run() method
we actually create the strategy and store it in our variable. Other than that, using
Graphics2D draw methods is straightforward. Key note: don't forget strategy.show() and
g2d.dispose()!! These methods are key. Without show(), nothing is shown, and forgetting
dispose just means your program will gradually hog memory over time. This is called a
memory leak and should be avoided at all costs.

Also worth noting is that we aren't setting the size of our JFrame with the conventional frame.setSize()
method, but instead setting the preferred size of our canvas and adding it to the frame. This way,
when we call frame.pack(), we let Java handle arranging the frame's borders to best fit the canvas,
rather than fitting the canvas to the border.

Feel free to comment with questions or thoughts, I'd love to hear your feedback!


~N

No comments:

Post a Comment