Design Patterns and Video Games

AWT GUI Facade (2): Draw

In this article, I propose to continue the design of a facade for a 2D tile game (previous post). I add two new features: creation/destruction of the window, and basic drawing. We manage each feature using methods in a GUIFacade interface.

This post is part of the AWT GUI Facade series.

GUI Facade

I design the GUIFacade interface in the following way:

GUI Facade with painting capabilities

The first four methods handle the window:

The last three methods allow you to draw in the window:

Most graphic libraries used to follow these steps where you have to start by "preparing" the drawing with a method like beginPaint(), then "free" it with a method like endPaint(). Besides, there is usually a form of blocking between these two calls, which means that you have to draw "as quickly as possible" if you want to offer a good user experience.

Usecase

I propose to use these methods as follows: the window is created (l.2) and we repeat until the game is finished (l.3). If it is possible to start drawing (l.4), draw a line (l.5) and finish the drawing (l.6). Finally, the window is destroyed (l. 9):

public static void run(GUIFacade gui) {
    gui.createWindow("AWT GUI Facade");
    while(!gui.isClosingRequested()) {
        if (gui.beginPaint()) {
            gui.drawLine(100, 150, 600, 350);
            gui.endPaint();
        }
    }      
    gui.dispose();
}

Note: In this example, there is no limitation on the number of frames per second. This can saturate a processor core and cause minor issues on some configurations. We'll see in another post how to get a better frame rate.

AWT Implementation

The use of the facade does not depend on its implementation: it is possible to use different implementations, for example, the AWT library included in the standard Java library:

public class AWTGUIFacade implements GUIFacade {

    private AWTWindow window;

    private boolean closingRequested = false;

    private BufferStrategy bufferStrategy;

    private Graphics graphics;

    @Override
    public void createWindow(String title) {
        window = new AWTWindow(this);
        window.init(title);
        window.setLocationRelativeTo(null);
        window.setVisible(true);
        window.createBufferStrategy(2);
    }

    @Override
    public void setClosingRequested(boolean b) {
        closingRequested = b;
    }

    @Override
    public boolean isClosingRequested() {
        return closingRequested;
    }

    @Override
    public void dispose() {
        window.dispose();
    }

    @Override
    public boolean beginPaint() {
        bufferStrategy = window.getBufferStrategy();
        if (bufferStrategy == null)
            return false;
        graphics = bufferStrategy.getDrawGraphics();
        if (graphics == null)
            return false;
        graphics.setColor(Color.black);
        graphics.fillRect(0, 0, window.getWidth(), window.getHeight());
        return true;
    }

    @Override
    public void drawLine(int x1, int y1, int x2, int y2) {
        graphics.setColor(Color.white);
        graphics.drawLine(x1, y1, x2, y2);
    }

    @Override
    public void endPaint() {
        graphics.dispose();
        bufferStrategy.show();
    }

}

The window attribute is a reference to an AWTWindow class that inherits from java.awt.Frame, shown below. The closingRequested attribute is true if the game must end. The bufferStrategy and graphics attributes handle double buffering rendering.

The init() method is the same as in the previous article, except for the creation of a double buffer with window.createBufferStrategy(2). The setClosingRequested() and isClosingRequested() methods manage the closingRequested attribute. The dispose() method controls the destruction of the window.

The beginPaint() and endPaint() methods handle the display cycle. The first one obtains the current buffer (1.37), and a java.awt.Graphics is created from it (1.40). In both situations, various reasons may cause issues, in which case we return false. Then, the end of the beginPaint() method erases all the content: this approach can be interesting or not, depending on the case. The endPaint() method destroys graphics (1. 56) and inverts the two buffers (1. 57). Finally, the drawLine() method uses graphics to draw a line: it's easy to imagine other use cases for drawing rectangles, circles, and so on.

The AWTWindow class is an AWT window initialized by the init() method with a specific configuration. When the user requests the window closing, we ask to stop the game (1. 16):

public class AWTWindow extends Frame {

    private final AWTGUIFacade gui;

    public AWTWindow(AWTGUIFacade gui) {
        this.gui = gui;
    }

    public void init(String title) {
        setTitle(title);
        setSize(640, 480);
        setResizable(false);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent we) {
                gui.setClosingRequested(true);
            }
        });        
    }
}

Note: The rendering method is simple but not optimal: it is better to use canvas to draw inside a window. This implementation may cause some undesirable effects.

The code of this post can be downloaded here:

awtfacade02.zip

To compile: javac com/learngameprog/awtfacade02/Main.java
To run: java com.learngameprog.awtfacade02.Main

Contents - Next: display an image