Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Java Tutor is my platform for teaching about Java 7+ and JavaFX 2.0+, mainly via programming projects.
As the character moves to the right, it encounters dangerous obstacles (such as crocodiles) that will kill it (and end the game) if they make contact with the character. To avoid these obstacles, it's necessary for the character to jump while moving to the right.
Writing code that detects and acts on single key presses is trivial. Writing code that detects and acts on multiple key presses (such as pressing the right and up arrow keys together) is also trivial when you use Boolean variables.
When a key listener's pressed event handler detects that a specific key is pressed, it assigns true to the corresponding Boolean variable. Similarly, when its released event handler detects that the key is released, it assigns false to the variable.
Each event handler then invokes a method whose chained if-else statement detects multiple/single key presses and updates game state appropriately, and subsequently invokes another method that acts on this state and updates the game.
I've created a Multiple Key Press Detection application that demonstrates detecting right arrow, up arrow, or right arrow and up arrow key presses, updating game state, and acting on this state to update the game. Listing 1 presents this application's source code.
// MKPD.html
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class MKPD extends JFrame
{
public MKPD()
{
super("MKPD");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setContentPane(new KeyPanel());
pack();
setVisible(true);
}
public static void main(String[] args)
{
Runnable r = new Runnable()
{
public void run()
{
new MKPD();
}
};
EventQueue.invokeLater(r);
}
}
class KeyPanel extends JPanel
{
private enum GameState { MOVED, JUMPED, MOVED_THEN_JUMPED,
JUMPED_THEN_MOVED };
private GameState gs = null;
private boolean rightPressed = false;
private boolean upPressed = false;
private JLabel lblStatus = null;
KeyPanel()
{
KeyAdapter ka = new KeyAdapter()
{
public void keyPressed(KeyEvent ke)
{
handleKeyPress(ke);
}
public void keyReleased(KeyEvent ke)
{
handleKeyRelease(ke);
}
};
addKeyListener(ka);
lblStatus = new JLabel("", JLabel.CENTER);
lblStatus.setPreferredSize(new Dimension(200, 50));
add(lblStatus);
setFocusable(true);
requestFocus();
}
private void handleKeyPress(KeyEvent ke)
{
int keyCode = ke.getKeyCode();
if (keyCode == KeyEvent.VK_RIGHT)
rightPressed = true;
else
if (keyCode == KeyEvent.VK_UP)
upPressed = true;
updateState();
updateLabel();
}
private void handleKeyRelease(KeyEvent ke)
{
int keyCode = ke.getKeyCode();
if (keyCode == KeyEvent.VK_RIGHT)
rightPressed = false;
else
if (keyCode == KeyEvent.VK_UP)
upPressed = false;
updateState();
updateLabel();
}
private void updateLabel()
{
if (gs == null)
{
lblStatus.setText("");
return;
}
switch (gs)
{
case MOVED_THEN_JUMPED:
lblStatus.setText("Moved and then jumped");
break;
case JUMPED_THEN_MOVED:
lblStatus.setText("Jumped and then moved");
break;
case MOVED:
lblStatus.setText("Moved");
break;
case JUMPED:
lblStatus.setText("Jumped");
}
}
private void updateState()
{
if (rightPressed && upPressed)
{
if (gs == GameState.MOVED)
gs = GameState.MOVED_THEN_JUMPED;
else
if (gs == GameState.JUMPED)
gs = GameState.JUMPED_THEN_MOVED;
}
else
if (rightPressed)
gs = GameState.MOVED;
else
if (upPressed)
gs = GameState.JUMPED;
else
gs = null;
}
}
Listing 1: MKPD.java
Listing 1 declares an application class named MKPD and a Swing component class named KeyPanel (which subclasses javax.swing.JPanel). MKPD's constructor creates a user interface consisting of a single instance of this custom panel.
KeyPanel is more interesting. This class first declares a GameState enum that enumerates four game states: moved, jumped, moved and then jumped, and jumped and then moved. The current game state is recorded in GameState variable gs.
After declaring Boolean variables rightPressed and upPressed, which are associated with the right arrow and up arrow keys, and after declaring a variable to hold a Swing label instance, this JPanel subclass presents a constructor for initializing this component.
The constructor first creates a java.awt.event.KeyAdapter instance that handles key pressed and key released events by calling private handleKeyPress(KeyEvent) and handleKeyRelease(KeyEvent) methods, respectively.
The constructor then registers the key adapter with the custom panel, by calling the void addKeyListener(KeyListener kl) method, which KeyPanel inherits from the java.awt.Component class, so that it can receive key pressed and key released events.
Continuing, the constructor creates a Swing label component to present centered text, and sets this component's preferred size so that the frame window's pack() method call results in a user interface large enough to display label text.
After adding the label to the panel, the constructor makes the panel focusable so that it will receive key-related events. It accomplishes this task by executing setFocusable(true). The constructor then executes requestFocus() to ensure that the panel has the focus.
Note: The call to requestFocus() isn't necessary when this application runs on Windows XP.
However, it might be necessary on other platforms.
|
The handleKeyPress() and handleKeyRelease() methods detect right arrow and up arrow presses and releases by assigning true or false to rightPressed or upPressed. These methods then invoke the private void updateState() and void updateLabel() methods.
updateState()'s if-else logic first evaluates rightPressed && upPressed to determine if the right arrow and up arrow keys are pressed. If so, it uses the previous gs value to learn which key was pressed first so that it can update gs appropriately (moved first or jumped first).
| Caution: It's important to check for multiple key presses before individual key presses. Checking first for individual key presses prevents multiple key press detection. |
I inserted if (gs == GameState.JUMPED) between else and gs = GameState.JUMPED_THEN_MOVED to prevent the state from immediately changing from MOVED_THEN_JUMPED to
JUMPED_THEN_MOVED when updateState() is repeatedly called during key repeat.
| Note: When you press a key and keep the key pressed, Java generates repeated key pressed events. I refer to this scenario as key repeat. |
updateLabel() uses the current state to update the label's text, but first tests gs for null (default game state, and game state chosen by updateState() when neither the up nor the right arrow key is pressed) to avoid a NullPointerException when switch evaluates gs.
Figure 1 reveals this application's user interface.
Figure 1: The right arrow key was pressed first and the up arrow key was pressed second.
When this window appears, press the right and up arrow keys by themselves or together, and in any order. When both keys are pressed, the label will reflect which key was pressed first. The label doesn't display text when neither key is pressed.
if (gs == GameState.JUMPED) between else and gs = GameState.JUMPED_THEN_MOVED to prevent the state from immediately changing from MOVED_THEN_JUMPED to JUMPED_THEN_MOVED when updateState() is repeatedly called during key repeat. Provide a step-by-step example of how the state changes when if (gs == GameState.JUMPED) isn't present.
You can download this post's code and answers here. Code was developed and tested with JDK 7u2 on a Windows XP SP3 platform.
* * *
I welcome your input to this blog, and will write about relevant topics that you suggest. While waiting for the next blog post, check out my TutorTutor website to learn more about Java and other computer technologies (and that's just the beginning).