Now that you have an idea about the role that user input plays in games, you're ready to learn the specifics of how user input works in Java. This lesson is devoted to the handling of user input in Java, including the supported input devices and the methods used to trap input events. You also learn about the event-driven structure of Java event handling and how it applies to input events.
This lesson clearly establishes the fact that Java user input handling is both powerful and simple. A handful of methods is really all it takes to deal with user input in a Java game. You get to see this firsthand by building a Java applet with keyboard and mouse input support. Let's get started.
Before getting into the details of how user input is handled in Java, it's important that you understand how event-driven programming works. This is important because user input in Java is heavily based on the event-driven architecture that makes up the heart of Java. In Java, an event is defined quite literally as something that happens that you might want to know about. For example, when a Java component gains the input focus, an event occurs because it might be important for your applet to know about the focus change.
An event is something that happens that you might want to know about and possibly react to.
In the event-driven world of Java, the flow of your program follows events external to your applet, as opposed to following an internally linear program flow. This is an important point because it means that a Java applet is in a constant state of responding to events. The most visible events are things such as mouse clicks and key presses, which are known as input events. You provide methods that respond to these events, which are called event handlers.
An input event is an event generated by user manipulation of an input device such as the mouse or keyboard.
Event handlers are special methods that are used to respond or react to events.
Because of the inherent graphical nature of Java applets, it will eventually become obvious to you why the event-driven programming model is not only more convenient, but downright necessary. With the potential of having multiple applets on a single Web page, along with on-the-fly system configuration changes and a multitude of other things going on, a procedural programming model would be much more difficult to manage. The event-based model provides a more sound solution to the problems inherent in a system with a graphical interface, such as Java.
All events in Java are processed within the awt windowing toolkit package, and they are tightly linked to awt components. A component is basically a generic abstraction for a Java window. You might recall that Java applets are themselves a specific type of component, which means that they inherit the same event-processing features built into the Component superclass.
A component is a generic abstraction of a window in Java.
As you just learned, user input in Java is handled through an event-driven architecture. When the user interacts with an input device, it results in an input event being dispatched to the component with the input focus. In most cases, this component is the applet window. An input event is a special type of event that notifies an applet that something has occurred on an input device. An example of an input event is a movement of the mouse.
Input events are crucial in Java games because they provide a means of handling user responses. Without being able to monitor user responses, Java games wouldn't be too exciting. User response handling is not only important for providing an interface to the user for playing a game; it also establishes much of the feel of a game. Simply altering the means by which you provide user response support can dramatically alter the play of a game. This is an important point, one that you'll deal with later in this lesson when you develop the Flying Saucer sample applet.
Java user event responses come in two varieties, which correspond to the input devices supported by Java. The two types of input events supported by release 1.0 of Java are as follows:
Keyboard events are events generated by a key press on the keyboard. Any time the user presses a key, a keyboard event is generated that can be trapped and handled by the applet with the input focus. Actually, a key press generates two events: a key down event and a key up event. You'll learn more about these two types soon.
Mouse events are generated by mouse clicks and movements. Every mouse click and mouse movement generates a corresponding mouse input event. Like key presses, mouse clicks actually come as a series of events: a mouse click down event and a mouse click up event. A mouse event is also specifically targeted at mouse dragging. Mouse dragging occurs when the mouse is moved with the button down. Applets that want to respond to mouse clicks and movement simply have to process these events and take action accordingly. You learn more about processing mouse events a little later in this lesson.
Note |
You might have noticed in the discussion of mouse events the mention of the mouse button, as opposed to the mouse buttons. This is intentional because Java only supports a single mouse button. This might seem limiting to users on some platforms, such as Windows, but keep in mind that Java is designed to support as many platforms as possible. Considering the fact that some platforms (such as Macintosh) have mice with a single button, it makes sense for Java to support only a single button. |
Before getting into the specific event handlers for keyboard and mouse events, let's look at how events are handled in a general sense in Java. The Java awt provides the Event class for encapsulating all types of events that can occur within the system. The Event class models a generic event and has constants defined within it to represent specific events. The Event class is used primarily by the handleEvent method of Component. The handleEvent method is the default event handler for all events, and is defined as follows:
public boolean handleEvent(Event evt)
Notice that handleEvent takes an Event object as its only parameter. handleEvent uses this Event object to determine what type of event has occurred. It then calls a more specific event handler method to deal with the specific event. For example, if a key is pressed, the Event object's id member variable is set to KEY_PRESS, which is a constant defining the key press event. handleEvent checks the value of id and, upon finding it equal to KEY_PRESS, calls the keyDown handler method. Listing 9.1 shows the key press handling portion of the handleEvent method in the 1.0 release of Java.
Listing 9.1. The key press portion of the handleEvent method.
public boolean handleEvent(Event evt) {
switch (evt.id) {
...
case Event.KEY_PRESS:
return keyDown(evt, evt.key);
...
}
return false;
}
The handling of other system events is very similar to that of the KEY_PRESS event. You could easily override handleEvent to provide custom routing of event handlers, but it is rarely necessary. Although you might not ever need to intervene with the default event handling provided by handleEvent, it is nevertheless important to understand how it works.
Java keyboard events are generated when the user presses or releases a key. Two standard keyboard event handler methods are supported by the Component class: keyDown and keyUp. These two methods are defined as follows:
public boolean keyDown(Event evt, int key)
public boolean keyUp(Event evt, int key)
The keyDown method is called in response to the user pressing a key, and the keyUp method is called in response to the user releasing a key. Both methods are passed an Event object and an integer key value parameter. The key value parameter, key, specifies which key was pressed or released. The Event object parameter contains extra information relating to the keyboard event, such as whether the Shift key was held down when the key was pressed.
The Event object contains
constants representing the different keys that can be specified
in the key parameter. Table
9.1 shows a list of the more useful key constants.
Table 9.1. Useful key constants for games.
Constant | Key |
UP | Up arrow |
DOWN | Down arrow |
LEFT | Left arrow |
RIGHT | Right arrow |
HOME | Home |
END | End |
PGUP | Page Up |
PGDN | Page Down |
To check whether the key pressed or released is one of these keys, you override keyDown or keyUp and compare the value of key to one of the constants. Listing 9.2 contains an example of overriding keyDown to check for the user pressing one of the arrow keys.
Listing 9.2. Handling arrow key presses in the keyDown method.
public boolean keyDown(Event evt, int key) {
switch (key) {
case Event.LEFT:
// left arrow key pressed
break;
case Event.RIGHT:
// right arrow key pressed
break;
case Event.UP:
// up arrow key pressed
break;
case Event.DOWN:
// down arrow key pressed
break;
}
return true;
}
This keyDown method shows that handling different key presses is as easy as providing a switch statement with case clauses for each key. Although the example here used the keyDown method for handling key presses, the keyUp method works in the same fashion.
If you need more details about the key that was pressed or released, you can use the Event object passed into the keyDown and keyUp methods. The typical usage of the Event object in regard to key processing is to check for modifier keys. Modifier keys are keys that can be pressed in conjunction with other input events, such as the Shift and Control keys. The following are the three methods in Event used to check the status of modifier keys:
public boolean shiftDown()
public boolean controlDown()
public boolean metaDown()
All of these methods return boolean values specifying whether or not the key in question is being held down. Checking the status of the modifier keys is necessary sometimes in applets that make heavy use of the mouse. For example, you might have a drawing applet that performs a different function if the Shift key is held down and the mouse is moved. You probably won't need the modifier keys in Java games, but it is still important to know how they work. Who knows, you might think of an interesting way to incorporate them into a game.
Mouse events occur when the user moves the mouse or clicks the mouse button. A handful of methods exist for handling mouse events, such as the following methods:
public boolean mouseUp(Event evt, int x, int y)
public boolean mouseDown(Event evt, int x, int y)
public boolean mouseMove(Event evt, int x, int y)
public boolean mouseDrag(Event evt, int x, int y)
public boolean mouseEnter(Event evt, int x, int y)
public boolean mouseExit(Event evt, int x, int y)
All of these methods are passed an Event object and two integer parameters representing the X and Y position of the mouse pointer. The mouseUp and mouseDown methods are called when the user presses and releases the mouse button. The mouseMove method is called when the mouse is moved. The mouseDrag method is very similar to the mouseMove method-the only difference being that mouseDrag is called when the mouse is moved with the button held down. The mouseEnter and mouseExit methods are used to track when the mouse enters and exits the applet window.
You can use the x and y parameters passed into the mouse event handler methods to perform any processing based on the position of the mouse. The following code snippet contains an example of overriding the mouseMove method to output the mouse position to standard output:
public boolean mouseMove(Event evt, int x, int y) {
System.out.println("Mouse position = (" + x + ", " + y + ")");
return true;
}
Similar to the keyboard event handlers, you can use the Event object passed in the mouse event handlers to find out additional information such as the status of modifier keys.
Now that you have a good idea of how to process user input events in Java, let's take a look at a sample applet that uses this newfound knowledge. The Flying Saucer applet uses the sprite classes and event handler methods to implement a user-controllable flying saucer. Figure 9.1 shows what Flying Saucer looks like. The complete source code, executable, and images for the Flying Saucer applet are included on the accompanying CD-ROM.
Figure 9.1 : The Flying Saucer sample applet.
The FlyingSaucer class models the applet itself and takes care of all the details related to setting up the sprite classes and handling the user input events. Similar to the other applet classes you've developed that use sprites, FlyingSaucer contains familiar support for the sprite classes. In fact, the only significantly new code in the FlyingSaucer class is the code for handling user input.
Before getting into the specifics of the user input handlers, however, take a look at two of the member variables defined in the FlyingSaucer class:
private Sprite theSaucer;
private int lastKey;
The theSaucer member variable is a Sprite object that holds the flying saucer sprite. It is necessary to keep up with this sprite outside of the sprite list because you need to be able to alter its position and velocity based on user input events. The lastKey member variable is used to hold the value of the last key pressed. This variable is used to provide finer control over the flying saucer, as you'll see later in this lesson.
The keyDown method handles all the details of supporting keyboard control of the saucer. Listing 9.3 shows the source code for the keyDown method.
Listing 9.3. The FlyingSaucer class's keyDown method.
public boolean keyDown(Event evt, int key) {
// Change the saucer velocity based on the key pressed
Point vel = theSaucer.getVelocity();
switch (key) {
case Event.LEFT:
vel.x = -4;
if (lastKey == Event.LEFT)
vel.y = 0;
break;
case Event.RIGHT:
vel.x = 4;
if (lastKey == Event.RIGHT)
vel.y = 0;
break;
case Event.UP:
vel.y = -4;
if (lastKey == Event.UP)
vel.x = 0;
break;
case Event.DOWN:
vel.y = 4;
if (lastKey == Event.DOWN)
vel.x = 0;
break;
default:
vel.x = vel.y = 0;
}
theSaucer.setVelocity(vel);
lastKey = key;
return true;
}
The keyDown method first gets the current velocity of the saucer and checks to see which key was pressed. It then alters the saucer's velocity according to the directional arrow key pressed. The lastKey member variable is then checked to see whether this is a repeat key press. If so, the tangential velocity component is cleared. For example, if the left arrow key is held down, the Y velocity component is cleared. This has the result of causing the saucer to change from moving in a diagonal direction to moving in a pure X or Y direction if you hold a key down, which gives the keyboard controls a better feel. Try it out for yourself.
The mouseDown and mouseDrag methods are used to handle mouse input events and position the saucer at an absolute location:
public boolean mouseDown(Event evt, int x, int y) {
theSaucer.setPosition(new Point(x - (saucerSize.width / 2),
y - (saucerSize.height / 2)));
return true;
}
public boolean mouseDrag(Event evt, int x, int y) {
theSaucer.setPosition(new Point(x - (saucerSize.width / 2),
y - (saucerSize.height / 2)));
return true;
}
Both of these methods simply reposition the saucer at a location centered on the current mouse position, which enables you to click and drag the saucer around with the mouse. This might not be an ideal usage of the mouse in most game scenarios, but it shows how the mouse can be used to control a sprite, which can be useful.
Note |
You might be thinking that the duplicate code in the mouseDown and mouseDrag methods goes against good programming practice. You're right! The truth is that I didn't want to confuse things by having these methods call a third method, which is typically the way you avoid duplicate code in a situation like this. You might also think that mouseDrag could just call mouseDown and simply pass its parameters along. Although this technique would work in this particular case, it's generally not a good idea to directly call event handler methods yourself, primarily because the Event parameter means different things to different event handlers. |
In this lesson, you learned about Java events and the event-driven architecture necessary to support them. More specifically, you learned about Java input events, including the input devices capable of generating them and how they are handled by the Java awt library.
You saw examples of using the input event handler methods to capture and respond to keyboard and mouse events. You then finished up the lesson with a sample applet using the sprite classes that implements a user-controllable flying saucer supporting both keyboard and mouse input. This sample applet brought you yet another step closer to implementing a complete game. As a matter of fact, you're now ready to embark on your first complete Java game; the next lesson focuses on developing your first full-blown Java game, Traveling Gecko.
Q | What's the big deal with event-driven programming? |
A | Event-driven programming provides a powerful methodology for handling the complexities inherent in a graphical system. By modeling every action in the system as an event with a corresponding handler, the complexities are broken down into individually serviceable items. |
Q | What are Java input events? |
A | They are any events generated by the user manipulating an input device such as the mouse or keyboard. |
Q | What is the purpose of the handleEvent method? |
A | The handleEvent method acts as a router method for all events. All events must pass through handleEvent, which in turn calls the appropriate event handler method. |
Q | If the user has more than one mouse button, can I detect when the user presses one of the extra buttons? |
A | No, Java only supports single-button mice. This is to eliminate the creation of extra button features that wouldn't be available to users (such as Macintosh users) with one button on their mice. |
The Workshop section provides questions and exercises to help strengthen your grasp on the material you learned today. Try to answer the questions and at least put some thought into the exercises before moving on to tomorrow's lesson. You'll find the answers to the questions in appendix A, "Quiz Answers."