Letting the Player Configure the Keyboard

Finally, you'll add a feature we've been planning for a while: the ability to let the user configure the keyboard, as shown in Screenshot.

Screenshot The Configure dialog box enables the user to customize the game controls.

Java graphics 03fig02.gif


This feature enables the user to map any key or mouse event to any game action. All the user has to do is select a game action and press a key or click the mouse, and that key is assigned to the game action. The keyboard configuration feature can be broken into two parts:

  1. First, you need to actually create the configuration dialog box.

  2. Second, you need to create a special component that enables the user to enter a key or mouse click.

Creating the Configuration dialog box is not a difficult task. The dialog box lists all the possible game actions for a game and also contains instructions and an OK button. The dialog box itself is a JPanel, and everything within the dialog box can be created using a series of components, panels, and layouts. Creating the special input component is a little more difficult. You need this component to display what key is currently mapped to a game action, and also to enable the user to press a key or a mouse button to change its setting. When input is done, you'll also have to make sure that the input component gives the keyboard focus back to the game's main window. The component that you create is called InputComponent, and it's an inner class of the KeyConfigTest class in Listing 3.11. InputComponent is a subclass of JTextField. Although a normal JTextField component enables the user to type in any amount of plain text, you want the user to type only one key or mouse click, so you need to override JTextField's input methods. Normally, you would create a KeyListener and MouseListener to listen for the input events, but this time you're going to get keys in a different way. That's right, there's more than one way to get input events. Every Swing component is an instance of the Component class. The Component class has methods such as processKeyEvent() and processMouseEvent(). The methods work just like the KeyListener or MouseListener methods. All you have to do is override these methods and enable input events by calling the enableEvents() method. The result is pretty much the same as if you had used listeners. This is just to show you an alternative.

Listing 3.11 KeyConfigTest.java
import java.awt.event.*;
import java.awt.*;
import java.util.List;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.Border;
import com.brackeen.javagamebook.graphics.*;
import com.brackeen.javagamebook.input.*;
/**
 The KeyConfigTest class extends the MenuTest demo to add
 a dialog box to configure the keyboard keys.
*/
public class KeyConfigTest extends MenuTest {
 public static void main(String[] args) {
 new KeyConfigTest().run();
 }
 private static final String INSTRUCTIONS =
 "<html>Click an action's input box to change its keys." +
 "<br>An action can have at most three keys associated " +
 "with it.<br>Press Backspace to clear an action's keys.";
 private JPanel dialog;
 private JButton okButton;
 private List inputs;
 public void init() {
 super.init();
 inputs = new ArrayList();
 // create the list of GameActions and mapped keys
 JPanel configPanel = new JPanel(new GridLayout(5,2,2,2));
 addActionConfig(configPanel, moveLeft);
 addActionConfig(configPanel, moveRight);
 addActionConfig(configPanel, jump);
 addActionConfig(configPanel, pause);
 addActionConfig(configPanel, exit);
 // create the panel containing the OK button
 JPanel bottomPanel = new JPanel(new FlowLayout());
 okButton = new JButton("OK");
 okButton.setFocusable(false);
 okButton.addActionListener(this);
 bottomPanel.add(okButton);
 // create the panel containing the instructions.
 JPanel topPanel = new JPanel(new FlowLayout());
 topPanel.add(new JLabel(INSTRUCTIONS));
 // create the dialog border
 Border border =
 BorderFactory.createLineBorder(Color.black);
 // create the config dialog.
 dialog = new JPanel(new BorderLayout());
 dialog.add(topPanel, BorderLayout.NORTH);
 dialog.add(configPanel, BorderLayout.CENTER);
 dialog.add(bottomPanel, BorderLayout.SOUTH);
 dialog.setBorder(border);
 dialog.setVisible(false);
 dialog.setSize(dialog.getPreferredSize());
 // center the dialog
 dialog.setLocation(
 (screen.getWidth() - dialog.getWidth()) / 2,
 (screen.getHeight() - dialog.getHeight()) / 2);
 // add the dialog to the "modal dialog" layer of the
 // screen's layered pane.
 screen.getFullScreenWindow().getLayeredPane().add(dialog,
 JLayeredPane.MODAL_LAYER);
 }
 /**
 Adds a label containing the name of the GameAction and an
 InputComponent used for changing the mapped keys.
 */
 private void addActionConfig(JPanel configPanel,
 GameAction action)
 {
 JLabel label = new JLabel(action.getName(), JLabel.RIGHT);
 InputComponent input = new InputComponent(action);
 configPanel.add(label);
 configPanel.add(input);
 inputs.add(input);
 }
 public void actionPerformed(ActionEvent e) {
 super.actionPerformed(e);
 if (e.getSource() == okButton) {
 // hides the config dialog
 configAction.tap();
 }
 }
 public void checkSystemInput() {
 super.checkSystemInput();
 if (configAction.isPressed()) {
 // hide or show the config dialog
 boolean show = !dialog.isVisible();
 dialog.setVisible(show);
 setPaused(show);
 }
 }
 /**
 Resets the text displayed in each InputComponent, which
 is the names of the mapped keys.
 */
 private void resetInputs() {
 for (int i=0; i<inputs.size(); i++) {
 ((InputComponent)inputs.get(i)).setText();
 }
 }
 /**
 The InputComponent class displays the keys mapped to a
 particular action and allows the user to change the mapped
 keys. The user selects an InputComponent by clicking it,
 then can press any key or mouse button (including the
 mouse wheel) to change the mapped value.
 */
 class InputComponent extends JTextField {
 private GameAction action;
 /**
 Creates a new InputComponent for the specified
 GameAction.
 */
 public InputComponent(GameAction action) {
 this.action = action;
 setText();
 enableEvents(KeyEvent.KEY_EVENT_MASK |
 MouseEvent.MOUSE_EVENT_MASK |
 MouseEvent.MOUSE_MOTION_EVENT_MASK |
 MouseEvent.MOUSE_WHEEL_EVENT_MASK);
 }
 /**
 Sets the displayed text of this InputComponent to the
 names of the mapped keys.
 */
 private void setText() {
 String text = "";
 List list = inputManager.getMaps(action);
 if (list.size() > 0) {
 for (int i=0; i<list.size(); i++) {
 text+=(String)list.get(i) + ", ";
 }
 // remove the last comma
 text = text.substring(0, text.length() - 2);
 }
 // make sure we don't get deadlock
 synchronized (getTreeLock()) {
 setText(text);
 }
 }
 /**
 Maps the GameAction for this InputComponent to the
 specified key or mouse action.
 */
 private void mapGameAction(int code, boolean isMouseMap) {
 if (inputManager.getMaps(action).size() >= 3) {
 inputManager.clearMap(action);
 }
 if (isMouseMap) {
 inputManager.mapToMouse(action, code);
 }
 else {
 inputManager.mapToKey(action, code);
 }
 resetInputs();
 screen.getFullScreenWindow().requestFocus();
 }
 // alternative way to intercept key events
 protected void processKeyEvent(KeyEvent e) {
 if (e.getID() == e.KEY_PRESSED) {
 // if backspace is pressed, clear the map
 if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE &&
 inputManager.getMaps(action).size() > 0)
 {
 inputManager.clearMap(action);
 setText("");
 screen.getFullScreenWindow().requestFocus();
 }
 else {
 mapGameAction(e.getKeyCode(), false);
 }
 }
 e.consume();
 }
 // alternative way to intercept mouse events
 protected void processMouseEvent(MouseEvent e) {
 if (e.getID() == e.MOUSE_PRESSED) {
 if (hasFocus()) {
 int code = InputManager.getMouseButtonCode(e);
 mapGameAction(code, true);
 }
 else {
 requestFocus();
 }
 }
 e.consume();
 }
 // alternative way to intercept mouse events
 protected void processMouseMotionEvent(MouseEvent e) {
 e.consume();
 }
 // alternative way to intercept mouse events
 protected void processMouseWheelEvent(MouseWheelEvent e) {
 if (hasFocus()) {
 int code = InputManager.MOUSE_WHEEL_DOWN;
 if (e.getWheelRotation() < 0) {
 code = InputManager.MOUSE_WHEEL_UP;
 }
 mapGameAction(code, true);
 }
 e.consume();
 }
 }
}


The KeyConfigTest class extends the MenuTest class. The init() method creates the configuration dialog box and adds it to the layered pane. The checkSystemInput() method is overridden to check to see whether the configuration button is pressed (the bolt icon); if it is, the configuration dialog box is shown. InputComponent has several methods to get input and also maps keys and mouse events to game actions by using InputManager's methods. Also, InputComponent limits the number of keys that can be mapped to a game action to three. This is just an artificial limit; in reality, there isn't a limit to the number of mapped keys the InputManager can have per game action. Also, InputComponent treats the Backspace key as special. If the Backspace key is pressed, it clears all the mapped keys for a game action. However, if an action has no mapped keys, the Backspace key is mapped to it. One cool thing not mentioned before is that JLabels can contain HTML, the language of the web. Here, you use HTML to make the JLabel that contains the instructions to be broken up into multiple lines by using HTML's line break tag, <br>.



   
Comments