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. Each feature is managed by a batch of methods in a GUIFacade interface.
This post is part of the AWT GUI Facade series.
The GUIFacade interface can be represented in the following way:
The first four methods handle the window:
- The createWindow() method builds the window with a given title;
- The setClosingRequested() method is used to define whether the game should be completed;
- The isClosingRequested() method lets you know if the game needs to be completed;
- The dispose() method destroys the window and all associated elements.
The last three methods allow you to draw in the window:
- The beginPaint() method starts the drawing, and returns true if it is possible;
- The drawLine() method draws a line. This is an example of a drawing method, a whole collection can be imagined to draw rectangles, circles, etc .;
- The endPaint() method completes the drawing.
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(). In addition, 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.
These methods can be used 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.
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 are used to 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. In the first one, the current buffer is obtained (1.37), then a java.awt.Graphics is created from it (1.40). In both situations, various reasons may cause issues, in which case false is returned. 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 window closing is requested by the user, the facade is notified that the game must be completed (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:
To compile: javac com/learngameprog/awtfacade02/Main.java
To run: java com.learngameprog.awtfacade02.Main