A game is just like any other computer program that has a UI. It is contained in some sort of window (if the underlying operating system’s UI paradigm is window based, which is the case on all mainstream operating systems). The window serves as a container, and we basically think of it as a canvas that we draw our game content on. Most operating systems allow the user to interact with the window in a special way besides touching the client area or pressing a key. On desktop systems you can usually drag the window around, resize it or minimize it to some sort of taskbar. On Android, resizing is replaced with accommodating an orientation change, and minimizing is similar to putting the application in the background via a press of the home button or as a reaction to an incoming call.
The application and window management module is also responsible for actually setting up the window and making sure it is filled by a single UI component that we can later render to and that receives input from the user in the form of touching or pressing keys. That UI component might be rendered to via the CPU or it can be hardware accelerated as it is the case with OpenGL ES.
The application and window management module does not have a concrete set of interfaces. We’ll merge it with the game framework later on. What we have to remember are the application states and window events that we have to manage:
Create: Called once when the window (and thus the application) is started up.
Pause: Called when the application is paused by some mechanism.
Resume: Called when the application is resumed and the window is in the foreground again.
NOTE: Some Android aficionados might roll their eyes at this point. Why only use a single window (activity in Android speak)? Why not use more than one UI widget for the game—say, for implementing complex UIs that our game might need? The main reason is that we want
complete control over the look and feel of our game. It also allows me to focus on Android game programming instead of Android UI programming, a topic for which better books exist—for example, Mark Murphy’s excellent Beginning Android 2 (Apress, 2010).
Input
The user will surely want to interact with our game in some way. That’s where the input module comes in. On most operating systems, input events such as touching the screen or pressing a key are dispatched to the currently focused window. The window will then further dispatch the event to the UI component that has the focus. The dispatching process is usually transparent to us; all we need to care about is getting the events from the focused UI component. The UI APIs of the operating system provide a mechanism to hook into the event dispatching system so we can easily register and record the events. This hooking into and recording of events is the main task of the input module. What can we do with the recorded information? There are two modi operandi:
Polling: With polling, we only check the current state of the input devices. Any states between the current check and the last check will be lost. This way of input handling is suitable for checking things like whether a user touches a specific button, for example. It is not suitable for tracking text input, as the order of key events is lost.
Event-based handling: This gives us a full chronological history of the events that have occurred since we last checked. It is a suitable mechanism to perform text input or any other task that relies on the order of events. It’s also useful to detect when a finger first touched the screen or when it was lifted.
What input devices do we want to handle? On Android, we have three main input methods: touchscreen, keyboard/trackball, and accelerometer. The first two are suitable for both polling and event-based handling. The accelerometer is usually just polled. The touchscreen can generate three events:
Touch down: This happens when a finger is touched to the screen.
Touch drag: This happens when a finger is dragged across the screen. Before a drag there’s always a down event.
Touch up: This happens when a finger is lifted from the screen.
Each touch event has additional information: the position relative to the UI components origin, and a pointer index used in multitouch environments to identify and track separate fingers.
The keyboard can generate two types of events:
Key down: This happens when a key is pressed down.
Key up: This happens when a key is lifted. This event is always preceded by a key- down event.
Key events also carry additional information. Key-down events store the pressed key’s code. Key-up events store the key’s code and an actual Unicode character. There’s a difference between a key’s code and the Unicode character generated by a key-up event. In the latter case, the state of other keys are also taken into account, such as the Shift key. This way, we can get upper- and lowercase letters in a key-up event, for
CHAPTER 3: Game Development 101
73
example. With a key-down event, we only know that a certain key was pressed; we have no information on what character that keypress would actually generate.
Finally, there’s the accelerometer. We will always poll the accelerometer’s state. The accelerometer reports the acceleration exerted by the gravity of our planet on one of three axes of the accelerometer. The axes are called x, y, and z. Figure 3–19 depicts each axis’s orientation. The acceleration on each axis is expressed in meters per second squared (m/s). From our physics class, we know that an object will accelerate at
roughly 9.8 m/s when in free fall on planet Earth. Other planets have a different gravity, so the acceleration constant is also different. For the sake of simplicity, we’ll only deal with planet Earth here. When an axis points away from the center of the Earth, the maximum acceleration is applied to it. If an axis points toward the center of the Earth, we get a negative maximum acceleration. If you hold your phone upright in portrait mode, then the y-axis will report an acceleration of 9.8 m/s, for example. In Figure 3–19, the z-axis would report an acceleration of 9.8 m/s, and the x- and y-axes would report and acceleration of zero.
Figure 3–19. The accelerometer axes on an Android phone. The z-axis points out of the phone. Now let’s define an interface that gives us polling access to the touchscreen, the keyboard, and the accelerometer, and gives us event-based access to the touchscreen and keyboard (see Listing 3–1).
Listing 3–1. The Input Interface and the KeyEvent and TouchEvent Classes
package com.badlogic.androidgames.framework; import java.util.List;
public interface Input {
public static class KeyEvent {
public static final int KEY_UP = 1; public int type;
public int keyCode;
public char keyChar; }
public static class TouchEvent {
public static final int TOUCH_DOWN = 0; public static final int TOUCH_UP = 1; public static final int TOUCH_DRAGGED = 2; public int type;
public int x, y; public int pointer; }
public boolean isKeyPressed(int keyCode); public boolean isTouchDown(int pointer); public int getTouchX(int pointer); public int getTouchY(int pointer); public float getAccelX();
public float getAccelY(); public float getAccelZ();
public List<KeyEvent> getKeyEvents(); public List<TouchEvent> getTouchEvents(); }
Our definition is started off by two classes, KeyEvent and TouchEvent. The KeyEvent class defines constants that encode a KeyEvent’s type; the TouchEvent class does the same. A KeyEvent instance records its type, the key’s code, and its Unicode character in case the the event’s type is KEY_UP.
The TouchEvent code is similar, and holds the TouchEvent’s type, the position of the finger relative to the UI component’s origin, and the pointer ID that was given to the finger by the touchscreen driver. The pointer ID for a finger will stay the same for as long as that finger is on the screen. The first finger that goes down gets the pointer ID 0, the next the ID 1, and so on. If two fingers are down and finger 0 is lifted, then finger 1 keeps its ID for as long as it is touching the screen. A new finger will get the the first free ID, which would be 0 in this example.
Next are the polling methods of the Input interface, which should be pretty self- explanatory. Input.isKeyPressed() takes a keyCode and returns whether the corresponding key is currently pressed or not. Input.isTouchDown(),
CHAPTER 3: Game Development 101
75
well as its current x- and y-coordinates. Note that the coordinates will be undefined if the corresponding pointer is not actually touching the screen.
Input.getAccelX(), Input.getAccelY(), and Input.getAccelZ() return the respective acceleration values of each accelerometer axis.
The last two methods are used for event-based handling. They return the KeyEvent and TouchEvent instances that got recorded since the last time we called these methods. The events are ordered according to when they occurred, with the newest event being at the end of the list.
With this simple interface and these helper classes, we have all our input needs covered. Let’s move on to handling files.
NOTE: While mutable classes with public members are an abomination, we can get away with them in this case for two reasons: Dalvik is still slow when calling methods (getters in this case), and the mutability of the event classes does not have an impact on the inner workings of an
Input implementation. Just note that this is bad style in general, but we will resort to this shortcut every once in a while for performance reasons.