diff --git a/docs/downloads/training/ControllerSample.zip b/docs/downloads/training/ControllerSample.zip new file mode 100644 index 0000000000000..b09686195150e Binary files /dev/null and b/docs/downloads/training/ControllerSample.zip differ diff --git a/docs/html/images/training/backward-compatible-inputmanager.png b/docs/html/images/training/backward-compatible-inputmanager.png new file mode 100644 index 0000000000000..fdac78a145260 Binary files /dev/null and b/docs/html/images/training/backward-compatible-inputmanager.png differ diff --git a/docs/html/images/training/game-controller-profiles.png b/docs/html/images/training/game-controller-profiles.png new file mode 100644 index 0000000000000..f8c1cbbace8d1 Binary files /dev/null and b/docs/html/images/training/game-controller-profiles.png differ diff --git a/docs/html/training/game-controllers/compatibility.jd b/docs/html/training/game-controllers/compatibility.jd new file mode 100644 index 0000000000000..f68ab1ae12783 --- /dev/null +++ b/docs/html/training/game-controllers/compatibility.jd @@ -0,0 +1,643 @@ +page.title=Supporting Controllers Across Android Versions +trainingnavtop=true + +@jd:body + + +
ControllerSample.zip
+If you are supporting game controllers in your game, it's your responsibility +to make sure that your game responds to controllers consistently across devices +running on different versions of Android. This lets your game reach a wider +audience, and your players can enjoy a seamless gameplay experience with +their controllers even when they switch or upgrade their Android devices.
+ +This lesson demonstrates how to use APIs available in Android 4.1 and higher +in a backward compatible way, enabling your game to support the following +features on devices running Android 2.3 and higher:
+The examples in this lesson are based on the reference implementation +provided by the sample {@code ControllerSample.zip} available for download +above. This sample shows how to implement the {@code InputManagerCompat} +interface to support different versions of Android. To compile the sample, you +must use Android 4.1 (API level 16) or higher. Once compiled, the sample app +runs on any device running Android 2.3 (API level 9) or higher as the build +target. +
+ +Suppose you want to be able to determine if a game controller's connection +status has changed on devices running on Android 2.3 (API level 9). However, +the APIs are only available in Android 4.1 (API level 16) and higher, so you +need to provide an implementation that supports Android 4.1 and higher while +providing a fallback mechanism that supports Android 2.3 up to Android 4.0.
+ +To help you determine which features require such a fallback mechanism for + older versions, table 1 lists the differences in game controller support + between Android 2.3 (API level 9), 3.1 (API level 12), and 4.1 (API level + 16).
+ ++Table 1. APIs for game controller support across +different Android versions. +
+ +| Controller Information | +Controller API | +API level 9 | +API level 12 | +API level 16 | +
|---|---|---|---|---|
| Device Identification | +{@link android.hardware.input.InputManager#getInputDeviceIds()} | ++ | + | • | +
| {@link android.hardware.input.InputManager#getInputDevice(int) +getInputDevice()} | ++ | + | • | +|
| {@link android.view.InputDevice#getVibrator()} | ++ | + | • | +{@link android.view.InputDevice#SOURCE_JOYSTICK} | ++ | • | +• | + + +
| {@link android.view.InputDevice#SOURCE_GAMEPAD} | ++ | • | +• | +|
| Connection Status | +{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceAdded(int) onInputDeviceAdded()} | ++ | + | • | +
| {@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceChanged(int) onInputDeviceChanged()} | ++ | + | • | +|
| {@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceRemoved(int) onInputDeviceRemoved()} | ++ | + | • | +|
| Input Event Identification | +D-pad press ( +{@link android.view.KeyEvent#KEYCODE_DPAD_UP}, +{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN}, +{@link android.view.KeyEvent#KEYCODE_DPAD_LEFT}, +{@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT}, +{@link android.view.KeyEvent#KEYCODE_DPAD_CENTER}) | +• | +• | +• | +
| Gamepad button press ( +{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBL BUTTON_THUMBL}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBR BUTTON_THUMBR}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_R1 BUTTON_R1}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_L1 BUTTON_L1}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_R2 BUTTON_R2}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_L2 BUTTON_L2}) | ++ | • | +• | +|
| Joystick and hat switch movement ( +{@link android.view.MotionEvent#AXIS_X}, +{@link android.view.MotionEvent#AXIS_Y}, +{@link android.view.MotionEvent#AXIS_Z}, +{@link android.view.MotionEvent#AXIS_RZ}, +{@link android.view.MotionEvent#AXIS_HAT_X}, +{@link android.view.MotionEvent#AXIS_HAT_Y}) | ++ | • | +• | +|
| Analog trigger press ( +{@link android.view.MotionEvent#AXIS_LTRIGGER}, +{@link android.view.MotionEvent#AXIS_RTRIGGER}) | ++ | • | +• | +
You can use abstraction to build version-aware game controller support that +works across platforms. This approach involves the following steps:
+For an overview of how abstraction can be used to ensure that applications +can work in a backward compatible way across different versions of Android, see +Creating +Backward-Compatible UIs. +
+ +To provide backward compatibility, you can create a custom interface then +add version-specific implementations. One advantage of this approach is that it +lets you mirror the public interfaces on Android 4.1 (API level 16) that +support game controllers.
+
+// The InputManagerCompat interface is a reference example.
+// The full code is provided in the ControllerSample.zip sample.
+public interface InputManagerCompat {
+ ...
+ public InputDevice getInputDevice(int id);
+ public int[] getInputDeviceIds();
+
+ public void registerInputDeviceListener(
+ InputManagerCompat.InputDeviceListener listener,
+ Handler handler);
+ public void unregisterInputDeviceListener(
+ InputManagerCompat.InputDeviceListener listener);
+
+ public void onGenericMotionEvent(MotionEvent event);
+
+ public void onPause();
+ public void onResume();
+
+ public interface InputDeviceListener {
+ void onInputDeviceAdded(int deviceId);
+ void onInputDeviceChanged(int deviceId);
+ void onInputDeviceRemoved(int deviceId);
+ }
+ ...
+}
+
+The {@code InputManagerCompat} interface provides the following methods:
+Next, create implementations for {@code InputManagerCompat} that work +across different platform versions. If your game is running on Android 4.1 or +higher and calls an {@code InputManagerCompat} method, the proxy implementation +calls the equivalent method in {@link android.hardware.input.InputManager}. +However, if your game is running on Android 2.3 up to Android 4.0, the custom implementation processes calls to {@code InputManagerCompat} methods by using +only APIs introduced no later than Android 2.3. Regardless of which +version-specific implementation is used at runtime, the implementation passes +the call results back transparently to the game.
+ +
++ Figure 1. Class diagram of interface and version-specific +implementations. +
+ +{@code InputManagerCompatV16} is an implementation of the +{@code InputManagerCompat} interface that proxies method calls to an +actual {@link android.hardware.input.InputManager} and {@link +android.hardware.input.InputManager.InputDeviceListener}. The +{@link android.hardware.input.InputManager} is obtained from the system +{@link android.content.Context}.
+ +
+// The InputManagerCompatV16 class is a reference implementation.
+// The full code is provided in the ControllerSample.zip sample.
+public class InputManagerV16 implements InputManagerCompat {
+
+ private final InputManager mInputManager;
+ private final Map mListeners;
+
+ public InputManagerV16(Context context) {
+ mInputManager = (InputManager)
+ context.getSystemService(Context.INPUT_SERVICE);
+ mListeners = new HashMap();
+ }
+
+ @Override
+ public InputDevice getInputDevice(int id) {
+ return mInputManager.getInputDevice(id);
+ }
+
+ @Override
+ public int[] getInputDeviceIds() {
+ return mInputManager.getInputDeviceIds();
+ }
+
+ static class V16InputDeviceListener implements
+ InputManager.InputDeviceListener {
+ final InputManagerCompat.InputDeviceListener mIDL;
+
+ public V16InputDeviceListener(InputDeviceListener idl) {
+ mIDL = idl;
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ mIDL.onInputDeviceAdded(deviceId);
+ }
+
+ // Do the same for device change and removal
+ ...
+ }
+
+ @Override
+ public void registerInputDeviceListener(InputDeviceListener listener,
+ Handler handler) {
+ V16InputDeviceListener v16Listener = new
+ V16InputDeviceListener(listener);
+ mInputManager.registerInputDeviceListener(v16Listener, handler);
+ mListeners.put(listener, v16Listener);
+ }
+
+ // Do the same for unregistering an input device listener
+ ...
+
+ @Override
+ public void onGenericMotionEvent(MotionEvent event) {
+ // unused in V16
+ }
+
+ @Override
+ public void onPause() {
+ // unused in V16
+ }
+
+ @Override
+ public void onResume() {
+ // unused in V16
+ }
+
+}
+
+
+The {@code InputManagerV9} implementation uses APIs introduced no later +than Android 2.3. To create an implementation of {@code +InputManagerCompat} that supports Android 2.3 up to Android 4.0, you can use +the following objects: +
+// The InputManagerCompatV9 class is a reference implementation.
+// The full code is provided in the ControllerSample.zip sample.
+public class InputManagerV9 implements InputManagerCompat {
+ private final SparseArray mDevices;
+ private final Map mListeners;
+ private final Handler mDefaultHandler;
+ …
+
+ public InputManagerV9() {
+ mDevices = new SparseArray();
+ mListeners = new HashMap();
+ mDefaultHandler = new PollingMessageHandler(this);
+ }
+}
+
+
+Implement a {@code PollingMessageHandler} object that extends +{@link android.os.Handler}, and override the +{@link android.os.Handler#handleMessage(android.os.Message) handleMessage()} +method. This method checks if an attached game controller has been +disconnected and notifies registered listeners.
+ +
+private static class PollingMessageHandler extends Handler {
+ private final WeakReference mInputManager;
+
+ PollingMessageHandler(InputManagerV9 im) {
+ mInputManager = new WeakReference(im);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ switch (msg.what) {
+ case MESSAGE_TEST_FOR_DISCONNECT:
+ InputManagerV9 imv = mInputManager.get();
+ if (null != imv) {
+ long time = SystemClock.elapsedRealtime();
+ int size = imv.mDevices.size();
+ for (int i = 0; i < size; i++) {
+ long[] lastContact = imv.mDevices.valueAt(i);
+ if (null != lastContact) {
+ if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
+ // check to see if the device has been
+ // disconnected
+ int id = imv.mDevices.keyAt(i);
+ if (null == InputDevice.getDevice(id)) {
+ // Notify the registered listeners
+ // that the game controller is disconnected
+ ...
+ imv.mDevices.remove(id);
+ } else {
+ lastContact[0] = time;
+ }
+ }
+ }
+ }
+ sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
+ CHECK_ELAPSED_TIME);
+ }
+ break;
+ }
+ }
+}
+
+
+To start and stop polling for game controller disconnection, override +these methods:
+
+private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
+private static final long CHECK_ELAPSED_TIME = 3000L;
+
+@Override
+public void onPause() {
+ mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
+}
+
+@Override
+public void onResume() {
+ mDefaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
+ CHECK_ELAPSED_TIME);
+}
+
+
+To detect that an input device has been added, override the +{@code onGenericMotionEvent()} method. When the system reports a motion event, +check if this event came from a device ID that is already tracked, or from a +new device ID. If the device ID is new, notify registered listeners.
+ +
+@Override
+public void onGenericMotionEvent(MotionEvent event) {
+ // detect new devices
+ int id = event.getDeviceId();
+ long[] timeArray = mDevices.get(id);
+ if (null == timeArray) {
+ // Notify the registered listeners that a game controller is added
+ ...
+ timeArray = new long[1];
+ mDevices.put(id, timeArray);
+ }
+ long time = SystemClock.elapsedRealtime();
+ timeArray[0] = time;
+}
+
+
+Notification of listeners is implemented by using the +{@link android.os.Handler} object to send a {@code DeviceEvent} +{@link java.lang.Runnable} object to the message queue. The {@code DeviceEvent} +contains a reference to an {@code InputManagerCompat.InputDeviceListener}. When +the {@code DeviceEvent} runs, the appropriate callback method of the listener +is called to signal if the game controller was added, changed, or removed. +
+ +
+@Override
+public void registerInputDeviceListener(InputDeviceListener listener,
+ Handler handler) {
+ mListeners.remove(listener);
+ if (handler == null) {
+ handler = mDefaultHandler;
+ }
+ mListeners.put(listener, handler);
+}
+
+@Override
+public void unregisterInputDeviceListener(InputDeviceListener listener) {
+ mListeners.remove(listener);
+}
+
+private void notifyListeners(int why, int deviceId) {
+ // the state of some device has changed
+ if (!mListeners.isEmpty()) {
+ for (InputDeviceListener listener : mListeners.keySet()) {
+ Handler handler = mListeners.get(listener);
+ DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId,
+ listener);
+ handler.post(odc);
+ }
+ }
+}
+
+private static class DeviceEvent implements Runnable {
+ private int mMessageType;
+ private int mId;
+ private InputDeviceListener mListener;
+ private static Queue sObjectQueue =
+ new ArrayDeque();
+ ...
+
+ static DeviceEvent getDeviceEvent(int messageType, int id,
+ InputDeviceListener listener) {
+ DeviceEvent curChanged = sObjectQueue.poll();
+ if (null == curChanged) {
+ curChanged = new DeviceEvent();
+ }
+ curChanged.mMessageType = messageType;
+ curChanged.mId = id;
+ curChanged.mListener = listener;
+ return curChanged;
+ }
+
+ @Override
+ public void run() {
+ switch (mMessageType) {
+ case ON_DEVICE_ADDED:
+ mListener.onInputDeviceAdded(mId);
+ break;
+ case ON_DEVICE_CHANGED:
+ mListener.onInputDeviceChanged(mId);
+ break;
+ case ON_DEVICE_REMOVED:
+ mListener.onInputDeviceRemoved(mId);
+ break;
+ default:
+ // Handle unknown message type
+ ...
+ break;
+ }
+ // Put this runnable back in the queue
+ sObjectQueue.offer(this);
+ }
+}
+
+
+You now have two implementations of {@code InputManagerCompat}: one that +works on devices running Android 4.1 and higher, and another +that works on devices running Android 2.3 up to Android 4.0.
+ +The version-specific switching logic is implemented in a class that acts as +a factory.
+
+public static class Factory {
+ public static InputManagerCompat getInputManager(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ return new InputManagerV16(context);
+ } else {
+ return new InputManagerV9();
+ }
+ }
+}
+
+Now you can simply instantiate an {@code InputManagerCompat} object and +register an {@code InputManagerCompat.InputDeviceListener} in your main +{@link android.view.View}. Because of the version-switching logic you set +up, your game automatically uses the implementation that's appropriate for the +version of Android the device is running.
+
+public class GameView extends View implements InputDeviceListener {
+ private InputManagerCompat mInputManager;
+ ...
+
+ public GameView(Context context, AttributeSet attrs) {
+ mInputManager =
+ InputManagerCompat.Factory.getInputManager(this.getContext());
+ mInputManager.registerInputDeviceListener(this, null);
+ ...
+ }
+}
+
+Next, override the +{@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) +onGenericMotionEvent()} method in your main view, as described in +Handle a MotionEvent from a Game +Controller. Your game should now be able to process game controller events +consistently on devices running Android 2.3 (API level 9) and higher. +
+
+@Override
+public boolean onGenericMotionEvent(MotionEvent event) {
+ mInputManager.onGenericMotionEvent(event);
+
+ // Handle analog input from the controller as normal
+ ...
+ return super.onGenericMotionEvent(event);
+}
+
+You can find a complete implementation of this compatibility code in the +{@code GameView} class provided in the sample {@code ControllerSample.zip} +available for download above.
\ No newline at end of file diff --git a/docs/html/training/game-controllers/controller-input.jd b/docs/html/training/game-controllers/controller-input.jd new file mode 100644 index 0000000000000..2c50ae1a67fb4 --- /dev/null +++ b/docs/html/training/game-controllers/controller-input.jd @@ -0,0 +1,656 @@ +page.title=Handling Controller Actions +trainingnavtop=true + +@jd:body + + +ControllerSample.zip
+At the system level, Android reports input event codes from game controllers +as Android key codes and axis values. In your game, you can receive these codes +and values and convert them to specific in-game actions.
+ +When players physically connect or wirelessly pair a game controller to +their Android-powered devices, the system auto-detects the controller +as an input device and starts reporting its input events. Your game can receive +these input events by implementing the following callback methods in your active +{@link android.app.Activity} or focused {@link android.view.View} (you should +implement the callbacks for either the {@link android.app.Activity} or +{@link android.view.View}, but not both):
+ +Called to process generic motion events such as joystick movements.
+Called to process key events such as a press or release of a + gamepad or D-pad button.
+Called to process generic motion events such as joystick movements.
+Called to process a press of a physical key such as a gamepad or + D-pad button.
+Called to process a release of a physical key such as a gamepad or + D-pad button.
+The recommended approach is to capture the events from the + specific {@link android.view.View} object that the user interacts with. + Inspect the following objects provided by the callbacks to get information + about the type of input event received:
+ +This lesson focuses on how you can handle input from the most common types of +physical controls (gamepad buttons, directional pads, and +joysticks) in a game screen by implementing the above-mentioned +{@link android.view.View} callback methods and processing +{@link android.view.KeyEvent} and {@link android.view.MotionEvent} objects.
+ +When reporting input events, Android does not distinguish +between events that came from a non-game controller device and events that came +from a game controller. For example, a touch screen action generates an +{@link android.view.MotionEvent#AXIS_X} event that represents the X +coordinate of the touch surface, but a joystick generates an {@link android.view.MotionEvent#AXIS_X} event that represents the X position of the joystick. If +your game cares about handling game-controller input, you should first check +that the input event comes from a relevant source type.
+To verify that a connected input device is a game controller, call +{@link android.view.InputDevice#getSources()} to obtain a combined bit field of +input source types supported on that device. You can then test to see if +the following fields are set:
+The following code snippet shows a helper method that lets you check whether + the connected input devices are game controllers. If so, the method retrieves + the device IDs for the game controllers. You can then associate each device + ID with a player in your game, and process game actions for each connected + player separately. To learn more about supporting multiple game controllers + that are simultaneously connected on the same Android device, see + Supporting Multiple Game Controllers.
++public ArrayList+getGameControllerIds() { + ArrayList gameControllerDeviceIds = new ArrayList (); + int[] deviceIds = InputDevice.getDeviceIds(); + for (int deviceId : deviceIds) { + InputDevice dev = InputDevice.getDevice(deviceId); + int sources = dev.getSources(); + + // Verify that the device has gamepad buttons, control sticks, or both. + if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) + || ((sources & InputDevice.SOURCE_JOYSTICK) + == InputDevice.SOURCE_JOYSTICK)) { + // This device is a game controller. Store its device ID. + if (!gameControllerDeviceIds.contains(deviceId)) { + gameControllerDeviceIds.add(deviceId); + } + } + } + return gameControllerDeviceIds; +} +
Additionally, you might want to check for individual input capabilities +supported by a connected game controller. This might be useful, for example, if +you want your game to use only input from the set of physical controls it +understands.
+To detect if a specific key code or axis code is supported by a connected +game controller, use these techniques:
+Figure 1 shows how Android maps key codes and axis values to the physical +controls on most game controllers.
+
++ Figure 1. Profile for a generic game controller. +
+The callouts in the figure refer to the following:
+Common key codes generated by gamepad button presses include + {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT}, +and {@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}. Some game +controllers also trigger the {@link android.view.KeyEvent#KEYCODE_DPAD_CENTER +DPAD_CENTER} key code when the center of the D-pad crossbar is pressed. Your +game can inspect the key code by calling {@link android.view.KeyEvent#getKeyCode()} +or from key event callbacks such as +{@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()}, +and if it represents an event that is relevant to your game, process it as a +game action. Table 1 lists the recommended game actions for the most common +gamepad buttons. +
+ ++ Table 1. Recommended game actions for gamepad +buttons.
+| Game Action | +Button Key Code | +
|---|---|
| Start game in main menu, or pause/unpause during game | +{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START} | +
| Display menu | +{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT} and +{@link android.view.KeyEvent#KEYCODE_MENU} | +
| Same as Android Back | +{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}* and +{@link android.view.KeyEvent#KEYCODE_BACK KEYCODE_BACK} | +
| Confirm selection, or perform primary game action | +{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}* and +{@link android.view.KeyEvent#KEYCODE_DPAD_CENTER DPAD_CENTER} | +
+* This could be the opposite button (A/B), depending on the locale that +you are supporting. +
+ +Tip: Consider providing a configuration screen +in your game to allow users to personalize their own game controller mappings for +game actions.
+ +The following snippet shows how you might override +{@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()} to +associate the {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} and +{@link android.view.KeyEvent#KEYCODE_DPAD_CENTER DPAD_CENTER} button presses +with a game action. +
+
+public class GameView extends View {
+ ...
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ boolean handled = false;
+ if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
+ == InputDevice.SOURCE_GAMEPAD) {
+ if (event.getRepeatCount() == 0) {
+ switch (keyCode) {
+ // Handle gamepad and D-pad button presses to
+ // navigate the ship
+ ...
+
+ default:
+ if (isFireKey(keyCode)) {
+ // Update the ship object to fire lasers
+ ...
+ handled = true;
+ }
+ break;
+ }
+ }
+ if (handled) {
+ return true;
+ }
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private static boolean isFireKey(int keyCode) {
+ // Here we treat Button_A and DPAD_CENTER as the primary action
+ // keys for the game. You may need to switch this to Button_B and
+ // DPAD_CENTER depending on the user expectations for the locale
+ // in which your game runs.
+ return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_BUTTON_A;
+ }
+}
+
+
+Follow these best practices when handling button presses:
+The 4-way directional pad (D-pad) is a common physical control in many game +controllers. Android reports D-pad UP and DOWN presses as +{@link android.view.MotionEvent#AXIS_HAT_Y} events with a range +from -1.0 (up) to 1.0 (down), and D-pad LEFT or RIGHT presses as +{@link android.view.MotionEvent#AXIS_HAT_X} events with a range from -1.0 +(left) to 1.0 (right).
+Some controllers instead report D-pad presses with a key code. If your game +cares about D-pad presses, you should treat the hat axis events and the D-pad +key codes as the same input events, as recommended in table 2.
++ Table 2. Recommended default game actions for D-pad key + codes and hat axis values.
+| Game Action | +D-pad Key Code | +Hat Axis Code | +
|---|---|---|
| Move Up | +{@link android.view.KeyEvent#KEYCODE_DPAD_UP} | +{@link android.view.MotionEvent#AXIS_HAT_Y} (for values 0 to -1.0) | +
| Move Down | +{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN} | +{@link android.view.MotionEvent#AXIS_HAT_Y} (for values 0 to 1.0) | +
| Move Left | +{@link android.view.KeyEvent#KEYCODE_DPAD_LEFT} | +{@link android.view.MotionEvent#AXIS_HAT_X} (for values 0 to -1.0) | +
| Move Right | +{@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT} | +{@link android.view.MotionEvent#AXIS_HAT_X} (for values 0 to 1.0) | +
The following code snippet shows a helper class that lets you check the hat +axis and key code values from an input event to determine the D-pad direction. +
+
+public class Dpad {
+ final static int UP = 0;
+ final static int LEFT = 1;
+ final static int RIGHT = 2;
+ final static int DOWN = 3;
+ final static int CENTER = 4;
+
+ int directionPressed = -1; // initialized to -1
+
+ public int getDirectionPressed(InputEvent event) {
+ if (!isDpadDevice(event)) {
+ return -1;
+ }
+
+ // If the input event is a MotionEvent, check its hat axis values.
+ if (event instanceof MotionEvent) {
+
+ // Use the hat axis value to find the D-pad direction
+ MotionEvent motionEvent = (MotionEvent) event;
+ float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
+ float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);
+
+ // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
+ // LEFT and RIGHT direction accordingly.
+ if (Float.compare(xaxis, -1.0f) == 0) {
+ directionPressed = Dpad.LEFT;
+ } else if (Float.compare(xaxis, 1.0f) == 0) {
+ directionPressed = Dpad.RIGHT;
+ }
+ // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
+ // UP and DOWN direction accordingly.
+ else if (Float.compare(yaxis, -1.0f) == 0) {
+ directionPressed = Dpad.UP;
+ } else if (Float.compare(yaxis, -1.0f) == 0) {
+ directionPressed = Dpad.DOWN;
+ }
+ }
+
+ // If the input event is a KeyEvent, check its key code.
+ else if (event instanceof KeyEvent) {
+
+ // Use the key code to find the D-pad direction.
+ KeyEvent keyEvent = (KeyEvent) event;
+ if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
+ directionPressed = Dpad.LEFT;
+ } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
+ directionPressed = Dpad.RIGHT;
+ } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
+ directionPressed = Dpad.UP;
+ } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
+ directionPressed = Dpad.DOWN;
+ } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
+ directionPressed = Dpad.CENTER;
+ }
+ }
+ return directionPressed;
+ }
+
+ public static boolean isDpadDevice(InputEvent event) {
+ // Check that input comes from a device with directional pads.
+ if ((event.getSource() & InputDevice.SOURCE_DPAD)
+ != InputDevice.SOURCE_DPAD) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+
+
+You can use this helper class in your game wherever you want to process + D-pad input (for example, in the +{@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) +onGenericMotionEvent()} or +{@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()} +callbacks).
+For example:
+
+Dpad mDpad = new Dpad();
+...
+@Override
+public boolean onGenericMotionEvent(MotionEvent event) {
+
+ // Check if this event if from a D-pad and process accordingly.
+ if (Dpad.isDpadDevice(event)) {
+
+ int press = mDpad.getDirectionPressed(event);
+ switch (press) {
+ case LEFT:
+ // Do something for LEFT direction press
+ ...
+ return true;
+ case RIGHT:
+ // Do something for RIGHT direction press
+ ...
+ return true;
+ case UP:
+ // Do something for UP direction press
+ ...
+ return true;
+ ...
+ }
+ }
+
+ // Check if this event is from a joystick movement and process accordingly.
+ ...
+}
+
+
+When players move a joystick on their game controllers, Android reports a +{@link android.view.MotionEvent} that contains the +{@link android.view.MotionEvent#ACTION_MOVE} action code and the updated +positions of the joystick's axes. Your game can use the data provided by +the {@link android.view.MotionEvent} to determine if a joystick movement it +cares about happened. +
+Note that joystick motion events may batch multiple movement samples together +within a single object. The {@link android.view.MotionEvent} object contains +the current position for each joystick axis as well as multiple historical +positions for each axis. When reporting motion events with action code {@link android.view.MotionEvent#ACTION_MOVE} (such as joystick movements), Android batches up the +axis values for efficiency. The historical values for an axis consists of the +set of distinct values older than the current axis value, and more recent than +values reported in any previous motion events. See the +{@link android.view.MotionEvent} reference for details.
+You can use the historical information to more accurately render a game +object's movement based on the joystick input. To +retrieve the current and historical values, call +{@link android.view.MotionEvent#getAxisValue(int) +getAxisValue()} or {@link android.view.MotionEvent#getHistoricalAxisValue(int, +int) getHistoricalAxisValue()}. You can also find the number of historical +points in the joystick event by calling +{@link android.view.MotionEvent#getHistorySize()}.
+The following snippet shows how you might override the +{@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) +onGenericMotionEvent()} callback to process joystick input. You should first +process the historical values for an axis, then process its current position. +
+
+public class GameView extends View {
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+
+ // Check that the event came from a game controller
+ if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
+ InputDevice.SOURCE_JOYSTICK &&
+ event.getAction() == MotionEvent.ACTION_MOVE)
+
+ // Process all historical movement samples in the batch
+ final int historySize = event.getHistorySize();
+
+ // Process the movements starting from the
+ // earliest historical position in the batch
+ for (int i = 0; i < historySize; i++) {
+ // Process the event at historical position i
+ processJoystickInput(event, i);
+ }
+
+ // Process the current movement sample in the batch (position -1)
+ processJoystickInput(event, -1);
+ return true;
+ }
+ return super.onGenericMotionEvent(event);
+ }
+}
+
+Before using joystick input, you need to determine if the joystick is +centered, then calculate its axis movements accordingly. Joysticks typically +have a flat area, that is, a range of values near the (0,0) coordinate +at which the axis is considered to be centered. If the axis value reported by +Android falls within the flat area, you should treat the controller to be at +rest (that is, motionless along both axes).
+The snippet below shows a helper method that calculates the movement along +each axis. You invoke this helper in the {@code processJoystickInput()} method +described further below. +
+
+private static float getCenteredAxis(MotionEvent event,
+ InputDevice device, int axis, int historyPos) {
+ final InputDevice.MotionRange range =
+ device.getMotionRange(axis, event.getSource());
+
+ // A joystick at rest does not always report an absolute position of
+ // (0,0). Use the getFlat() method to determine the range of values
+ // bounding the joystick axis center.
+ if (range != null) {
+ final float flat = range.getFlat();
+ final float value =
+ historyPos < 0 ? event.getAxisValue(axis):
+ event.getHistoricalAxisValue(axis, historyPos);
+
+ // Ignore axis values that are within the 'flat' region of the
+ // joystick axis center.
+ if (Math.abs(value) > flat) {
+ return value;
+ }
+ }
+ return 0;
+}
+
+Putting it all together, here is how you might process joystick movements in +your game:
+
+private void processJoystickInput(MotionEvent event,
+ int historyPos) {
+
+ InputDevice mInputDevice = event.getDevice();
+
+ // Calculate the horizontal distance to move by
+ // using the input value from one of these physical controls:
+ // the left control stick, hat axis, or the right control stick.
+ float x = getCenteredAxis(event, mInputDevice,
+ MotionEvent.AXIS_X, historyPos);
+ if (x == 0) {
+ x = getCenteredAxis(event, mInputDevice,
+ MotionEvent.AXIS_HAT_X, historyPos);
+ }
+ if (x == 0) {
+ x = getCenteredAxis(event, mInputDevice,
+ MotionEvent.AXIS_Z, historyPos);
+ }
+
+ // Calculate the vertical distance to move by
+ // using the input value from one of these physical controls:
+ // the left control stick, hat switch, or the right control stick.
+ float y = getCenteredAxis(event, mInputDevice,
+ MotionEvent.AXIS_Y, historyPos);
+ if (y == 0) {
+ y = getCenteredAxis(event, mInputDevice,
+ MotionEvent.AXIS_HAT_Y, historyPos);
+ }
+ if (y == 0) {
+ y = getCenteredAxis(event, mInputDevice,
+ MotionEvent.AXIS_RZ, historyPos);
+ }
+
+ // Update the ship object based on the new x and y values
+ ...
+
+ return true;
+}
+
+To support game controllers that have more sophisticated +features beyond a single joystick, follow these best practices:
+ControllerSample.zip
+You can greatly enhance the user experience in your game by letting +players use their favorite game controllers. The Android framework provides + APIs for detecting and processing user input from game controllers.
+ +This class shows how to make your game work consistently with game +controllers across different Android API levels (API level 9 and up), +and how to enhance the gaming experience for players by supporting multiple +controllers simultaneously in your app.
+ +ControllerSample.zip
+While most games are designed to support a single user per Android device, +it's also possible to support multiple users with game controllers that are +connected simultaneously on the same Android device.
+This lesson covers some basic techniques for handling input in your single +device multiplayer game from multiple connected controllers. This includes +maintaining a mapping between player avatars and each controller device and +processing controller input events appropriately. +
+ +When a game controller is connected to an Android device, the system +assigns it an integer device ID. You can obtain the device IDs for connected +game controllers by calling {@link android.view.InputDevice#getDeviceIds() InputDevice.getDeviceIds()}, as shown in Verify a Game Controller is Connected. You can then associate each +device ID with a player in your game, and process game actions for each player separately. +
+Note: On devices running Android 4.1 (API +level 16) and higher, you can obtain an input device’s descriptor using +{@link android.view.InputDevice#getDescriptor()}, which returns a unique +persistent string value for the input device. Unlike a device ID, the descriptor +value won't change even if the input device is disconnected, reconnected, or +reconfigured. +
+The code snippet below shows how to use a {@link android.util.SparseArray} +to associate a player's avatar with a specific controller. In this example, the +{@code mShips} variable stores a collection of {@code Ship} objects. A new +player avatar is created in-game when a new controller is attached by a user, +and removed when its associated controller is removed. +
+The {@code onInputDeviceAdded()} and {@code onInputDeviceRemoved()} callback +methods are part of the abstraction layer introduced in + +Supporting Controllers Across Android Versions. By implementing these +listener callbacks, your game can identify the game controller's device ID when a +controller is added or removed. This detection is compatible with Android 2.3 +(API level 9) and higher. +
+ +
+private final SparseArray<Ship> mShips = new SparseArray<Ship>();
+
+@Override
+public void onInputDeviceAdded(int deviceId) {
+ getShipForID(deviceId);
+}
+
+@Override
+public void onInputDeviceRemoved(int deviceId) {
+ removeShipForID(deviceId);
+}
+
+private Ship getShipForID(int shipID) {
+ Ship currentShip = mShips.get(shipID);
+ if ( null == currentShip ) {
+ currentShip = new Ship();
+ mShips.append(shipID, currentShip);
+ }
+ return currentShip;
+}
+
+private void removeShipForID(int shipID) {
+ mShips.remove(shipID);
+}
+
+
+Your game should execute the following loop to process +input from multiple controllers: +
+{@link android.view.KeyEvent} and {@link android.view.MotionEvent} input +events have device IDs associated with them. Your game can take advantage of +this to determine which controller the input event came from, and update the +player avatar associated with that controller. +
+The following code snippet shows how you might get a player avatar reference +corresponding to a game controller device ID, and update the game based on the +user's button press on that controller. +
+
+@Override
+public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
+ == InputDevice.SOURCE_GAMEPAD) {
+ int deviceId = event.getDeviceId();
+ if (deviceId != -1) {
+ Ship currentShip = getShipForId(deviceId);
+ // Based on which key was pressed, update the player avatar
+ // (e.g. set the ship headings or fire lasers)
+ ...
+ return true;
+ }
+ }
+ return super.onKeyDown(keyCode, event);
+}
+
+Note: As a best practice, when a user's +game controller disconnects, you should pause the game and ask if the user +wants to reconnect. +
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 9e265ece54bff..0616b62f01b69 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -1,6 +1,4 @@