Creating a Simple Menu
Now you can extend your game from InputManagerTest to add a simple user interface with pause, configuration, and exit buttons. For now, the configuration button won't do anything, but you'll go ahead and put it on the screen. First, when you click a button, what exactly happens? Swing sees the click and checks to see whether the button has any ActionListeners. If it does, those ActionListeners are notified that the button was clicked in the AWT event dispatch thread. Like KeyListener or MouseListener, ActionListener is an interface that any object can implement. It has one method named actionPerformed() that takes an ActionEvent as a parameter. You can check to see what component generated the event by calling ActionEvent's getSource() method. For example:
public void actionPerformed(ActionEvent e) { Object src = e.getSource(); if (src == okButton) { // do something } }
Finally, in the user interface, you can do a few things to your buttons to make them more usable:
- Add Tooltips. Just call something like setToolTip("Hello World"), and Swing handles the rest.
- Use icons. Instead of having text in your buttons, you can use icons. There can be different icons for the default, rollover, and pressed states.
- Hide the default look. You want your icons to appear by themselves, so turn off the button's border and call setContentAreaFilled(false) to make sure the button background isn't drawn.
- Change the cursor. Make the cursor appear as a hand when you roll over a button by calling the setCursor() method.
- Turn off key focus. If the button is focusable, the button can "steal" keyboard focus away from the game when it is clicked. To fix this, just call setFocusable(false). The only drawback to this is that only the mouse can activate a button.
All of these changes are done in the createButton() method of the MenuTest class. The MenuTest class, in Listing 3.10, extends the InputManagerTest and adds the buttons on top of it. For a screen capture of MenuTest, see Screenshot.
Listing 3.10 MenuTest.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; import com.brackeen.javagamebook.graphics.*; import com.brackeen.javagamebook.input.GameAction; /** Extends the InputManagerTest demo and adds Swing buttons for pause, config and quit. */ public class MenuTest extends InputManagerTest implements ActionListener { public static void main(String[] args) { new MenuTest().run(); } protected GameAction configAction; private JButton playButton; private JButton configButton; private JButton quitButton; private JButton pauseButton; private JPanel playButtonSpace; public void init() { super.init(); // make sure Swing components don't paint themselves NullRepaintManager.install(); // create an additional GameAction for "config" configAction = new GameAction("config"); // create buttons quitButton = createButton("quit", "Quit"); playButton = createButton("play", "Continue"); pauseButton = createButton("pause", "Pause"); configButton = createButton("config", "Change Settings"); // create the space where the play/pause buttons go. playButtonSpace = new JPanel(); playButtonSpace.setOpaque(false); playButtonSpace.add(pauseButton); JFrame frame = super.screen.getFullScreenWindow(); Container contentPane = frame.getContentPane(); // make sure the content pane is transparent if (contentPane instanceof JComponent) { ((JComponent)contentPane).setOpaque(false); } // add components to the screen's content pane contentPane.setLayout(new FlowLayout(FlowLayout.LEFT)); contentPane.add(playButtonSpace); contentPane.add(configButton); contentPane.add(quitButton); // explicitly lay out components (needed on some systems) frame.validate(); } /** Extends InputManagerTest's functionality to draw all Swing components. */ public void draw(Graphics2D g) { super.draw(g); JFrame frame = super.screen.getFullScreenWindow(); // the layered pane contains things like popups (tooltips, // popup menus) and the content pane. frame.getLayeredPane().paintComponents(g); } /** Changes the pause/play button whenever the pause state changes. */ public void setPaused(boolean p) { super.setPaused(p); playButtonSpace.removeAll(); if (isPaused()) { playButtonSpace.add(playButton); } else { playButtonSpace.add(pauseButton); } } /** Called by the AWT event dispatch thread when a button is pressed. */ public void actionPerformed(ActionEvent e) { Object src = e.getSource(); if (src == quitButton) { // fire the "exit" gameAction super.exit.tap(); } else if (src == configButton) { // doesn't do anything (for now) configAction.tap(); } else if (src == playButton || src == pauseButton) { // fire the "pause" gameAction super.pause.tap(); } } /** Creates a Swing JButton. The image used for the button is located at "../images/menu/" + name + ".png". The image is modified to create a "default" look (translucent) and a "pressed" look (moved down and to the right). <p>The button doesn't use Swing's look-and-feel and instead just uses the image. */ public JButton createButton(String name, String toolTip) { // load the image String imagePath = "../images/menu/" + name + ".png"; ImageIcon iconRollover = new ImageIcon(imagePath); int w = iconRollover.getIconWidth(); int h = iconRollover.getIconHeight(); // get the cursor for this button Cursor cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); // make translucent default image Image image = screen.createCompatibleImage(w, h, Transparency.TRANSLUCENT); Graphics2D g = (Graphics2D)image.getGraphics(); Composite alpha = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, .5f); g.setComposite(alpha); g.drawImage(iconRollover.getImage(), 0, 0, null); g.dispose(); ImageIcon iconDefault = new ImageIcon(image); // make a pressed image image = screen.createCompatibleImage(w, h, Transparency.TRANSLUCENT); g = (Graphics2D)image.getGraphics(); g.drawImage(iconRollover.getImage(), 2, 2, null); g.dispose(); ImageIcon iconPressed = new ImageIcon(image); // create the button JButton button = new JButton(); button.addActionListener(this); button.setIgnoreRepaint(true); button.setFocusable(false); button.setToolTipText(toolTip); button.setBorder(null); button.setContentAreaFilled(false); button.setCursor(cursor); button.setIcon(iconDefault); button.setRolloverIcon(iconRollover); button.setPressedIcon(iconPressed); return button; } }
Screenshot The MenuTest class creates a simple user interface on top of your game.
In MenuTest, only one PNG image exists for each button. The other images are generated in code when the program starts. The loaded image is treated as the rollover image, shown when the mouse is over the button. The default image is a slightly faded image that is created by copying the rollover image with an AlphaComposite of 0.5. The AlphaComposite class simply signifies to the Graphics2D class to blend the source image with its destination, creating a translucent effect. Finally, offsetting the rollover image by a few pixels creates the pressed image. This makes the pressed image look like the user is pressing the button down. Another note about MenuTest is how pausing works in the interface. When the game is paused, the pause button becomes a play button. To achieve this functionality, the pause button is placed in a JPanel, which is a container for other components. When the user pauses or unpauses the game, the JPanel's contents are changed to display the correct button. Of course, the configuration button in MenuTest doesn't do anything-yet.