Chapter 4. Full-Screen Worms

A popular aim for games is to be an immersive experience, where the player becomes so enthralled with the game that he or she forgets everyday trivia such as eating and visiting the bathroom. One simple way of encouraging immersion is to make the game window the size of the desktop; a full-screen display hides tiresome text editors, spreadsheets, or database applications requiring urgent attention.

I'll look at three approaches to creating full-screen games:

  • An almost full-screen JFrame (I'll call this AFS)

  • An undecorated full-screen JFrame (UFS)

  • Full-screen exclusive mode (FSEM)

FSEM is getting a lot of attention since its introduction in J2SE 1.4 because it has increased frame rates over traditional gaming approaches using repaint events and paintComponent(). However, comparisons between AFS, UFS, and FSEM show their maximum frame rates to be similar. This is due to my use of the animation loop developed in Chapter 2, with its active rendering and high-resolution Java 3D timer. You should read Chapter 2 before continuing.

The examples in this chapter will continue using the WormChase game, first introduced in Chapter 3, so you'd better read that chapter as well. By sticking to a single game throughout this chapter, the timing comparisons more accurately reflect differences in the animation code rather than in the game-specific parts.

The objective is to produce 80 to 85 FPS, which is near the limit of a typical graphics card's rendering capacity. If the game's frame rate falls short of this, then the updates per second (UPS) should still stay close to 80 to 85, causing the game to run quickly but without every update being rendered.

An Almost Full-Screen (AFS) Worm

Figure 4-1 shows the WormChase application running inside a JFrame that almost covers the entire screen. The JFrame's titlebar, including its close box and iconification/de-iconfication buttons are visible, and a border is around the window. The OS desktop controls are visible (in this case, Windows's task bar at the bottom of the screen).

An AFS WormChase

Figure 4-1. An AFS WormChase

These JFrame and OS components allow the player to control the game (e.g., pause it by iconification) and to switch to other applications in the usual way, without the need for GUI controls inside the game. Also, little code has to be modified to change a windowed game into an AFS version, aside from resizing the canvas.

Though the window can be iconified and switched to the background, it can't be moved. To be more precise, it can be selected and dragged, but as soon as the mouse button is released, the window snaps back to its original position.

Tip

This is a fun effect, as if the window is attached by a rubber band to the top lefthand corner of the screen.

Figure 4-2 gives the class diagrams for the AFS version of WormChase, including the public methods.

Class diagrams for the AFS version of WormChase

Figure 4-2. Class diagrams for the AFS version of WormChase

The AFS approach and the windowed application are similar as shown by the class diagrams in Figure 4-2 being identical to those for the windowed WormChase application at the start of Chapter 3. The differences are located in the private methods and the constructor, where the size of the JFrame is calculated and listener code is put in place to keep the window from moving.

WormPanel is almost the same as before, except that WormChase passes it a calculated width and height (in earlier version these were constants in the class). The Worm and Obstacles classes are unaltered from Chapter 3.

Tip

The code for the AFS WormChase can be found in the directory Worm/WormAFS/ .

The AFS WormChase Class

Figure 4-3 gives a class diagram for WormChase showing all its variables and methods.

The constructor has to work hard to obtain correct dimensions for the JPanel. The problem is that the sizes of three distinct kinds of elements must be calculated:

  • The JFrame's insets (e.g., the titlebar and borders)

  • The desktop's insets (e.g., the taskbar)

  • The other Swing components in the window (e.g., two text fields)

WormChase in detail

Figure 4-3. WormChase in detail

The insets of a container are the unused areas around its edges (at the top, bottom, left, and right). Typical insets are the container's border lines and its titlebar. The widths and heights of these elements must be subtracted from the screen's dimensions to get WormPanel's width and height. Figure 4-4 shows the insets and GUI elements for WormChase.

The subtraction of the desktop and JFrame inset dimensions from the screen size is standard, but the calculation involving the on-screen positions of the GUI elements depends on the game design. For WormChase, only the heights of the text fields affect WormPanel's size.

A subtle problem is that the dimensions of the JFrame insets and GUI elements will be unavailable until the game window has been constructed. In that case, how can the panel's dimensions be calculated if the application has to be created first?

The answer is that the application must be constructed in stages. First, the JFrame and other pieces needed for the size calculations are put together. This fixes their sizes, so the drawing panel's area can be determined. The sized JPanel is then added to the window to complete it, and the window is made visible. The WormChase constructor utilizes these stages:

    public WormChase(long period)
    { super("The Worm Chase");

      makeGUI();
Dimensions in the AFS WormChase

Figure 4-4. Dimensions in the AFS WormChase

      pack();    // first pack (the GUI doesn't include the JPanel yet)
      setResizable(false);  //so sizes are for nonresizable GUI elems
      calcSizes();
      setResizable(true);    // so panel can be added

      Container c = getContentPane();
      wp = new WormPanel(this, period, pWidth, pHeight);
      c.add(wp, "Center");
      pack();      // second pack, after JPanel added

      addWindowListener( this );

      addComponentListener( new ComponentAdapter() {
        public void componentMoved(ComponentEvent e)
        {  setLocation(0,0);  }
      });

      setResizable(false);
      setVisible(true);
    }  // end of WormChase() constructor

makeGUI() builds the GUI without a drawing area, and the call to pack() makes the JFrame displayable and calculates the component's sizes. Resizing is turned off since some platforms render insets differently (i.e., with different sizes) when their enclosing window can't be resized.

calcSizes() initializes two globals, pWidth and pHeight, which are later passed to the WormPanel constructor as the panel's width and height:

    private void calcSizes()
    {
      GraphicsConfiguration gc = getGraphicsConfiguration();
      Rectangle screenRect = gc.getBounds();  // screen dimensions

      Toolkit tk = Toolkit.getDefaultToolkit();
      Insets desktopInsets = tk.getScreenInsets(gc);

      Insets frameInsets = getInsets();     // only works after pack()

      Dimension tfDim = jtfBox.getPreferredSize();  // textfield size

      pWidth = screenRect.width
                 - (desktopInsets.left + desktopInsets.right)
                 - (frameInsets.left + frameInsets.right);

      pHeight = screenRect.height
                  - (desktopInsets.top + desktopInsets.bottom)
                  - (frameInsets.top + frameInsets.bottom)
                  - tfDim.height;
    }

Tip

If the JFrame's insets (stored in frameInsets) are requested before a call to pack(), then they will have zero size.

An Insets object has four public variables—top, bottom, left, and right—that hold the thickness of its container's edges. Only the dimensions for the box's text field (jtfBox) is retrieved since its height will be the same as the time-used text field. Back in WormChase(), resizing is switched back on so the correctly sized JPanel can be added to the JFrame. Finally, resizing is switched off permanently, and the application is made visible with a call to show().

Stopping Window Movement

Unfortunately, there is no simple way of preventing an application's window from being dragged around the screen. The best you can do is move it back to its starting position as soon as the user releases the mouse.

The WormChase constructor sets up a component listener with a componentMoved() handler. This method is called whenever a move is completed:

    addComponentListener( new ComponentAdapter() {
      public void componentMoved(ComponentEvent e)
      {  setLocation(0,0);  }
    });

setLocation() positions the JFrame so its top-left corner is at the top left of the screen.

Timings for AFS

Timing results for the AFS WormChase are given in Table 4-1.

Table 4-1. Average FPS/UPS rates for the AFS WormChase

Requested FPS

20

50

80

100

Windows 98

20/20

49/50

75/83

86/100

Windows 2000

20/20

20/50

20/83

20/100

Windows XP (1)

20/20

50/50

82/83

87/100

Windows XP (2)

20/20

50/50

75/83

75/100

WormChase on the slow Windows 2000 machine is the worst performer again, as seen in Chapter 3, though its slowness is barely noticeable due to the update rate remaining high.

The Windows 98 and XP boxes produce good frame rates when 80 FPS is requested, which is close to or inside my desired range (80 to 85 FPS). The numbers start to flatten as the FPS request goes higher, indicating that the frames can't be rendered any faster.

Tip

The timing tests for Windows XP were run on two machines to highlight the variation in WormChase's performance at higher requested FPSs.

Get Killer Game Programming in Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.