diff options
author | quddusc <quddusc@google.com> | 2014-01-21 20:13:48 -0800 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2014-01-21 20:13:48 -0800 |
commit | c99557c92c9bebc089c4eb7bf37a63dca8f28a6a (patch) | |
tree | 9660a0448f869aa8624fdb193dfc6aca467b2512 /docs/html/training | |
parent | 69edcce7831cb41cd0d1a40159fa7a12a3123687 (diff) | |
parent | 0cb965a69aac9c2a83798960a0efeaa6e596e445 (diff) | |
download | frameworks_base-c99557c92c9bebc089c4eb7bf37a63dca8f28a6a.zip frameworks_base-c99557c92c9bebc089c4eb7bf37a63dca8f28a6a.tar.gz frameworks_base-c99557c92c9bebc089c4eb7bf37a63dca8f28a6a.tar.bz2 |
am 0cb965a6: am 5020a873: Merge "cherrypick from jb-mr2-docs docs: Training class for game controllers. Change-Id: I697770aee8604c965c3730691459c1e8f10705da" into klp-docs
* commit '0cb965a69aac9c2a83798960a0efeaa6e596e445':
cherrypick from jb-mr2-docs docs: Training class for game controllers. Change-Id: I697770aee8604c965c3730691459c1e8f10705da
Diffstat (limited to 'docs/html/training')
-rw-r--r-- | docs/html/training/game-controllers/compatibility.jd | 643 | ||||
-rw-r--r-- | docs/html/training/game-controllers/controller-input.jd | 656 | ||||
-rw-r--r-- | docs/html/training/game-controllers/index.jd | 60 | ||||
-rw-r--r-- | docs/html/training/game-controllers/multiple-controllers.jd | 130 | ||||
-rw-r--r-- | docs/html/training/training_toc.cs | 25 |
5 files changed, 1512 insertions, 2 deletions
diff --git a/docs/html/training/game-controllers/compatibility.jd b/docs/html/training/game-controllers/compatibility.jd new file mode 100644 index 0000000..f68ab1a --- /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 + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#prepare">Prepare to Abstract APIs for Game Controller +Suppport</a></li> + <li><a href="#abstraction">Add an Interface for Backward Compatibility</a></li> + <li><a href="#newer">Implement the Interface on Android 4.1 and Higher</a></li> + <li><a href="#older">Implement the Interface on Android 2.3 up to Android +4.0</a></li> + <li><a href="#using">Use the Version-Specific Implementations</a></li> +</ol> + +<h2>Try it out</h2> +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/ControllerSample.zip" +class="button">Download the sample</a> + <p class="filename">ControllerSample.zip</p> +</div> + +</div> +</div> + +<p>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.</p> + +<p>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:</p> +<ul> +<li>The game can detect if a new game controller is added, changed, or removed.</li> +<li>The game can query the capabilities of a game controller.</li> +<li>The game can recognize incoming motion events from a game controller.</li> +</ul> + +<p>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. +</p> + +<h2 id="prepare">Prepare to Abstract APIs for Game Controller Support</h2> +<p>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.</p> + +<p>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).</p> + +<p class="table-caption" id="game-controller-support-table"> +<strong>Table 1.</strong> APIs for game controller support across +different Android versions. +</p> + +<table> +<tbody> +<tr> +<th>Controller Information</th> +<th>Controller API</th> +<th>API level 9</th> +<th>API level 12</th> +<th>API level 16</th> +</tr> + +<tr> +<td rowspan="5">Device Identification</td> +<td>{@link android.hardware.input.InputManager#getInputDeviceIds()}</td> +<td style="text-align: center;"><big> </big></td> +<td style="text-align: center;"><big> </big></td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<tr> +<td>{@link android.hardware.input.InputManager#getInputDevice(int) +getInputDevice()}</td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"><big> </big></td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<tr> +<td>{@link android.view.InputDevice#getVibrator()}</td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"><big> </big></td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<td>{@link android.view.InputDevice#SOURCE_JOYSTICK}</td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"><big>•</big></td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<tr> +<td>{@link android.view.InputDevice#SOURCE_GAMEPAD}</td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"><big>•</big></td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<tr> +<td rowspan="3">Connection Status</td> +<td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceAdded(int) onInputDeviceAdded()}</td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<tr> +<td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceChanged(int) onInputDeviceChanged()}</td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<tr> +<td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceRemoved(int) onInputDeviceRemoved()}</td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<tr> +<td rowspan="4">Input Event Identification</td> +<td>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})</td> +<td style="text-align: center;"><big>•</big></td> +<td style="text-align: center;"><big>•</big></td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<tr> +<td>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})</td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"><big>•</big></td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<tr> +<td>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})</td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"><big>•</big></td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +<tr> +<td>Analog trigger press ( +{@link android.view.MotionEvent#AXIS_LTRIGGER}, +{@link android.view.MotionEvent#AXIS_RTRIGGER})</td> +<td style="text-align: center;"> </td> +<td style="text-align: center;"><big>•</big></td> +<td style="text-align: center;"><big>•</big></td> +</tr> + +</tbody> +</table> + +<p>You can use abstraction to build version-aware game controller support that +works across platforms. This approach involves the following steps:</p> +<ol> +<li>Define an intermediary Java interface that abstracts the implementation of +the game controller features required by your game.</li> +<li>Create a proxy implementation of your interface that uses APIs in Android +4.1 and higher.</li> +<li>Create a custom implementation of your interface that uses APIs available +between Android 2.3 up to Android 4.0.</li> +<li>Create the logic for switching between these implementations at runtime, +and begin using the interface in your game.</li> +</ol> + +<p>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 +<a href="{@docRoot}training/backward-compatible-ui/index.html">Creating +Backward-Compatible UIs</a>. +</p> + +<h2 id="abstraction">Add an Interface for Backward Compatibility</h2> + +<p>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.</p> +<pre> +// 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); + } + ... +} +</pre> +<p>The {@code InputManagerCompat} interface provides the following methods:</p> +<dl> +<dt>{@code getInputDevice()}</dt> +<dd>Mirrors {@link android.hardware.input.InputManager#getInputDevice(int) +getInputDevice()}. Obtains the {@link android.view.InputDevice} +object that represents the capabilities of a game controller.</dd> +<dt>{@code getInputDeviceIds()}</dt> +<dd>Mirrors {@link android.hardware.input.InputManager#getInputDeviceIds() +getInputDeviceIds()}. Returns an array of integers, each of +which is an ID for a different input device. This is useful if you're building +a game that supports multiple players and you want to detect how many +controllers are connected.</dd> +<dt>{@code registerInputDeviceListener()}</dt> +<dd>Mirrors {@link android.hardware.input.InputManager#registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler) +registerInputDeviceListener()}. Lets you register to be informed when a new +device is added, changed, or removed.</dd> +<dt>{@code unregisterInputDeviceListener()}</dt> +<dd>Mirrors {@link android.hardware.input.InputManager#unregisterInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener) unregisterInputDeviceListener()}. +Unregisters an input device listener.</dd> +<dt>{@code onGenericMotionEvent()}</dt> +<dd>Mirrors {@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) +onGenericMotionEvent()}. Lets your game intercept and handle +{@link android.view.MotionEvent} objects and axis values that represent events +such as joystick movements and analog trigger presses.</dd> +<dt>{@code onPause()}</dt> +<dd>Stops polling for game controller events when the +main activity is paused, or when the game no longer has focus.</dd> +<dt>{@code onResume()}</dt> +<dd>Starts polling for game controller events when the +main activity is resumed, or when the game is started and runs in the +foreground.</dd> +<dt>{@code InputDeviceListener}</dt> +<dd>Mirrors the {@link android.hardware.input.InputManager.InputDeviceListener} +interface. Lets your game know when a game controller has been added, changed, or +removed.</dd> +</dl> +<p>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.</p> + +<img src="{@docRoot}images/training/backward-compatible-inputmanager.png" alt="" +id="figure1" /> +<p class="img-caption"> + <strong>Figure 1.</strong> Class diagram of interface and version-specific +implementations. +</p> + +<h2 id="newer">Implement the Interface on Android 4.1 and Higher</h2> +<p>{@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}.</p> + +<pre> +// 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<InputManagerCompat.InputDeviceListener, + V16InputDeviceListener> mListeners; + + public InputManagerV16(Context context) { + mInputManager = (InputManager) + context.getSystemService(Context.INPUT_SERVICE); + mListeners = new HashMap<InputManagerCompat.InputDeviceListener, + V16InputDeviceListener>(); + } + + @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 + } + +} +</pre> + +<h2 id="older">Implementing the Interface on Android 2.3 up to Android 4.0</h2> + +<p>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: +<ul> +<li>A {@link android.util.SparseArray} of device IDs to track the +game controllers that are connected to the device.</li> +<li>A {@link android.os.Handler} to process device events. When an app is started +or resumed, the {@link android.os.Handler} receives a message to start polling +for game controller disconnection. The {@link android.os.Handler} will start a +loop to check each known connected game controller and see if a device ID is +returned. A {@code null} return value indicates that the game controller is +disconnected. The {@link android.os.Handler} stops polling when the app is +paused.</li> +<li>A {@link java.util.Map} of {@code InputManagerCompat.InputDeviceListener} +objects. You will use the listeners to update the connection status of tracked +game controllers.</li> +</ul> + +<pre> +// 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<long[]> mDevices; + private final Map<InputDeviceListener, Handler> mListeners; + private final Handler mDefaultHandler; + … + + public InputManagerV9() { + mDevices = new SparseArray<long[]>(); + mListeners = new HashMap<InputDeviceListener, Handler>(); + mDefaultHandler = new PollingMessageHandler(this); + } +} +</pre> + +<p>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.</p> + +<pre> +private static class PollingMessageHandler extends Handler { + private final WeakReference<InputManagerV9> mInputManager; + + PollingMessageHandler(InputManagerV9 im) { + mInputManager = new WeakReference<InputManagerV9>(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; + } + } +} +</pre> + +<p>To start and stop polling for game controller disconnection, override +these methods:</p> +<pre> +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); +} +</pre> + +<p>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.</p> + +<pre> +@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; +} +</pre> + +<p>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. +</p> + +<pre> +@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<DeviceEvent> sObjectQueue = + new ArrayDeque<DeviceEvent>(); + ... + + 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); + } +} +</pre> + +<p>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.</p> + +<h2 id="using">Use the Version-Specific Implementation</h2> +<p>The version-specific switching logic is implemented in a class that acts as +a <a href="http://en.wikipedia.org/wiki/Factory_(software_concept)" +class="external-link" target="_blank">factory</a>.</p> +<pre> +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(); + } + } +} +</pre> +<p>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.</p> +<pre> +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); + ... + } +} +</pre> +<p>Next, override the +{@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) +onGenericMotionEvent()} method in your main view, as described in +<a href="controller-input.html#analog">Handle a MotionEvent from a Game +Controller</a>. Your game should now be able to process game controller events +consistently on devices running Android 2.3 (API level 9) and higher. +<p> +<pre> +@Override +public boolean onGenericMotionEvent(MotionEvent event) { + mInputManager.onGenericMotionEvent(event); + + // Handle analog input from the controller as normal + ... + return super.onGenericMotionEvent(event); +} +</pre> +<p>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.</p>
\ 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 0000000..2c50ae1 --- /dev/null +++ b/docs/html/training/game-controllers/controller-input.jd @@ -0,0 +1,656 @@ +page.title=Handling Controller Actions +trainingnavtop=true + +@jd:body + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#input">Verify a Game Controller is Connected</a></li> + <li><a href="#button">Process Gamepad Button Presses</a> + </li> + <li><a href="#dpad">Process Directional Pad Input</a> + </li> + <li><a href="#joystick">Process Joystick Movements</a> + </li> +</ol> + +<h2>Try it out</h2> +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/ControllerSample.zip" +class="button">Download the sample</a> + <p class="filename">ControllerSample.zip</p> +</div> + +</div> +</div> + +<p>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.</p> + +<p>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): </p> + +<ul> +<li>From {@link android.app.Activity}: + <ul> + <li>{@link android.app.Activity#dispatchGenericMotionEvent(android.view.MotionEvent) dispatchGenericMotionEvent(android.view.MotionEvent)} + <p>Called to process generic motion events such as joystick movements.</p> + </li> + <li>{@link android.app.Activity#dispatchKeyEvent(android.view.KeyEvent) dispatchKeyEvent(android.view.KeyEvent)} + <p>Called to process key events such as a press or release of a + gamepad or D-pad button.</p> + </li> + </ul> +</li> +<li>From {@link android.view.View}: + <ul> + <li>{@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) +onGenericMotionEvent(android.view.MotionEvent)} + <p>Called to process generic motion events such as joystick movements.</p> + </li> + <li>{@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown(int, android.view.KeyEvent)} + <p>Called to process a press of a physical key such as a gamepad or + D-pad button.</p> + </li> + <li>{@link android.view.View#onKeyUp(int, android.view.KeyEvent) onKeyUp(int, android.view.KeyEvent)} + <p>Called to process a release of a physical key such as a gamepad or + D-pad button.</p> + </li> + </ul> +</li> +</ul> + +<p>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:</p> + +<dl> +<dt>{@link android.view.KeyEvent}</dt> + <dd>An object that describes directional +pad</a> (D-pad) and gamepad button events. Key events are accompanied by a +<em>key code</em> that indicates the specific button triggered, such as +{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN DPAD_DOWN} +or {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}. You can obtain 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()}. +<dd> +<dt>{@link android.view.MotionEvent}</dt> + <dd>An object that describes input from joystick and shoulder trigger + movements. Motion events are accompanied by an action code and a set of +<em>axis values</em>. The action code specifies the state change that occurred +such as a joystick being moved. The axis values describe the position and other +movement properties for a specific physical control, such as +{@link android.view.MotionEvent#AXIS_X} or +{@link android.view.MotionEvent#AXIS_RTRIGGER}. You can obtain the action code +by calling {@link android.view.MotionEvent#getAction()} and the axis value by +calling {@link android.view.MotionEvent#getAxisValue(int) getAxisValue()}. +<dd> +</dl> +<p>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.</p> + +<h2 id="input">Verify a Game Controller is Connected</h2> +<p>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.</p> +<p>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:</p> +<ul> +<li>A source type of {@link android.view.InputDevice#SOURCE_GAMEPAD} indicates +that the input device has gamepad buttons (for example, +{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}). Note that this source +type does not strictly indicate if the game controller has D-pad buttons, +although most gamepads typically have directional controls.</li> +<li>A source type of {@link android.view.InputDevice#SOURCE_DPAD} indicates that +the input device has D-pad buttons (for example, +{@link android.view.KeyEvent#KEYCODE_DPAD_UP DPAD_UP}).</li> +<li>A source type of {@link android.view.InputDevice#SOURCE_JOYSTICK} +indicates that the input device has analog control sticks (for example, a +joystick that records movements along {@link android.view.MotionEvent#AXIS_X} +and {@link android.view.MotionEvent#AXIS_Y}).</li> +</ul> +<p>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 + <a href="multiple-controllers.html">Supporting Multiple Game Controllers</a>.</p> +<pre> +public ArrayList<Integer> getGameControllerIds() { + ArrayList<Integer> gameControllerDeviceIds = new ArrayList<Integer>(); + 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; +} +</pre> +<p>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.</p> +<p>To detect if a specific key code or axis code is supported by a connected +game controller, use these techniques:</p> +<ul> +<li>In Android 4.4 (API level 19) or higher, you can determine if a key code is +supported on a connected game controller by calling +{@link android.view.InputDevice#hasKeys(int...)}.</li> +<li>In Android 3.1 (API level 12) or higher, you can find all available axes +supported on a connected game controller by first calling +{@link android.view.InputDevice#getMotionRanges()}. Then, on each +{@link android.view.InputDevice.MotionRange} object returned, call +{@link android.view.InputDevice.MotionRange#getAxis()} to get its axis ID.</li> +</ul> + +<h2 id="button">Process Gamepad Button Presses</h2> +<p>Figure 1 shows how Android maps key codes and axis values to the physical +controls on most game controllers.</p> +<img src="{@docRoot}images/training/game-controller-profiles.png" alt="" +id="figure1" /> +<p class="img-caption"> + <strong>Figure 1.</strong> Profile for a generic game controller. +</p> +<p>The callouts in the figure refer to the following:</p> +<div style="-moz-column-count:2;-webkit-column-count:2;column-count:2;"> +<ol style="margin-left:30px;list-style:decimal;"> +<li>{@link android.view.MotionEvent#AXIS_HAT_X}, +{@link android.view.MotionEvent#AXIS_HAT_Y}, +{@link android.view.KeyEvent#KEYCODE_DPAD_UP DPAD_UP}, +{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN DPAD_DOWN}, +{@link android.view.KeyEvent#KEYCODE_DPAD_LEFT DPAD_LEFT}, +{@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT DPAD_RIGHT} +</li> +<li>{@link android.view.MotionEvent#AXIS_X}, +{@link android.view.MotionEvent#AXIS_Y}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBL BUTTON_THUMBL}</li> +<li>{@link android.view.MotionEvent#AXIS_Z}, +{@link android.view.MotionEvent#AXIS_RZ}, +{@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBR BUTTON_THUMBR}</li> +<li>{@link android.view.KeyEvent#KEYCODE_BUTTON_X BUTTON_X}</li> +<li>{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}</li> +<li>{@link android.view.KeyEvent#KEYCODE_BUTTON_Y BUTTON_Y}</li> +<li>{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}</li> +<li>{@link android.view.KeyEvent#KEYCODE_BUTTON_R1 BUTTON_R1}</li> +<li>{@link android.view.MotionEvent#AXIS_RTRIGGER}, +{@link android.view.MotionEvent#AXIS_THROTTLE}</li> +<li>{@link android.view.MotionEvent#AXIS_LTRIGGER}, +{@link android.view.MotionEvent#AXIS_BRAKE}</li> +<li>{@link android.view.KeyEvent#KEYCODE_BUTTON_L1 BUTTON_L1}</li> +</ol> +</div> +<p>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. +</p> + +<p class="table-caption" id="table1"> + <strong>Table 1.</strong> Recommended game actions for gamepad +buttons.</p> +<table> + <tr> + <th scope="col">Game Action</th> + <th scope="col">Button Key Code</th> + </tr> + <tr> + <td>Start game in main menu, or pause/unpause during game</td> + <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}</td> + </tr> + <tr> + <td>Display menu</td> + <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT} and +{@link android.view.KeyEvent#KEYCODE_MENU}</td> + </tr> + <tr> + <td>Same as Android <em>Back</em></td> + <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}<sup>*</sup> and +{@link android.view.KeyEvent#KEYCODE_BACK KEYCODE_BACK}</td> + </tr> + <tr> + <td>Confirm selection, or perform primary game action</td> + <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}<sup>*</sup> and +{@link android.view.KeyEvent#KEYCODE_DPAD_CENTER DPAD_CENTER}</td> + </tr> +</table> +<p> +<em>* This could be the opposite button (A/B), depending on the locale that +you are supporting.</em> +</p> + +<p class="note"><strong>Tip: </strong>Consider providing a configuration screen +in your game to allow users to personalize their own game controller mappings for +game actions.</p> + +<p>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. +</p> +<pre> +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; + } +} +</pre> + +<p>Follow these best practices when handling button presses:</p> +<ul> +<li><strong>Provide localized button mappings.</strong> Generally, if your game +has a primary gameplay action (for example, it fires lasers, lets your avatar +do a high jump, or confirms an item selection), you should map +both {@link android.view.KeyEvent#KEYCODE_DPAD_CENTER DPAD_CENTER} and +{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} to this action. However, +in some locales, users may expect +{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B} to be the confirm +button and {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} to be the +back button instead. If you are supporting these locales, make sure to treat +the A and B buttons accordingly in your game. To determine the user's locale, +call the {@link java.util.Locale#getDefault()} method. +<li><strong>Map {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} +consistently across different Android versions.</strong> On Android 4.2 (API +level 17) and lower, the system treats +{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} as the Android +<em>Back</em> key by default. If your app supports these Android +versions, make sure to treat +{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} as the primary game +action (except in the localization case mentioned +above). To determine the current Android SDK +version on the device, refer to the +{@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT} value. +</li> +</ul> + +<h2 id="dpad">Process Directional Pad Input</h2> +<p>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).</p> +<p>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.</p> +<p class="table-caption" id="table2"> + <strong>Table 2.</strong> Recommended default game actions for D-pad key + codes and hat axis values.</p> +<table> + <tr> + <th scope="col">Game Action</th> + <th scope="col">D-pad Key Code</th> + <th scope="col">Hat Axis Code</th> + </tr> + <tr> + <td>Move Up</td> + <td>{@link android.view.KeyEvent#KEYCODE_DPAD_UP}</td> + <td>{@link android.view.MotionEvent#AXIS_HAT_Y} (for values 0 to -1.0)</td> + </tr> + <tr> + <td>Move Down</td> + <td>{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN}</td> + <td>{@link android.view.MotionEvent#AXIS_HAT_Y} (for values 0 to 1.0)</td> + </tr> + <tr> + <td>Move Left</td> + <td>{@link android.view.KeyEvent#KEYCODE_DPAD_LEFT}</td> + <td>{@link android.view.MotionEvent#AXIS_HAT_X} (for values 0 to -1.0)</td> + </tr> + <tr> + <td>Move Right</td> + <td>{@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT}</td> + <td>{@link android.view.MotionEvent#AXIS_HAT_X} (for values 0 to 1.0)</td> + </tr> +</table> + + +<p>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. +</p> +<pre> +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; + } + } +} +</pre> + +<p>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).</p> +<p>For example:</p> +<pre> +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. + ... +} +</pre> + +<h2 id="joystick">Process Joystick Movements</h2> +<p>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. +</p> +<p>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.</p> +<p>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()}.</p> +<p>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. +</p> +<pre> +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); + } +} +</pre> +<p>Before using joystick input, you need to determine if the joystick is +centered, then calculate its axis movements accordingly. Joysticks typically +have a <em>flat</em> 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).</p> +<p>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. +</p> +<pre> +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; +} +</pre> +<p>Putting it all together, here is how you might process joystick movements in +your game:</p> +<pre> +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; +} +</pre> +<p>To support game controllers that have more sophisticated +features beyond a single joystick, follow these best practices: </p> +<ul> +<li><strong>Handle dual controller sticks.</strong> Many game controllers have +both a left and right joystick. For the left stick, Android +reports horizontal movements as {@link android.view.MotionEvent#AXIS_X} events +and vertical movements as {@link android.view.MotionEvent#AXIS_Y} events. +For the right stick, Android reports horizontal movements as +{@link android.view.MotionEvent#AXIS_Z} events and vertical movements as +{@link android.view.MotionEvent#AXIS_RZ} events. Make sure to handle +both controller sticks in your code.</li> +<li><strong>Handle shoulder trigger presses (but provide alternative input +methods).</strong> Some controllers have left and right shoulder +triggers. If these triggers are present, Android reports a left trigger press +as an {@link android.view.MotionEvent#AXIS_LTRIGGER} event and a +right trigger press as an +{@link android.view.MotionEvent#AXIS_RTRIGGER} event. On Android +4.3 (API level 18), a controller that produces a +{@link android.view.MotionEvent#AXIS_LTRIGGER} also reports an +identical value for the {@link android.view.MotionEvent#AXIS_BRAKE} axis. The +same is true for {@link android.view.MotionEvent#AXIS_RTRIGGER} and +{@link android.view.MotionEvent#AXIS_GAS}. Android reports all analog trigger +presses with a normalized value from 0.0 (released) to 1.0 (fully pressed). Not +all controllers have triggers, so consider allowing players to perform those +game actions with other buttons. +</li> +</ul>
\ No newline at end of file diff --git a/docs/html/training/game-controllers/index.jd b/docs/html/training/game-controllers/index.jd new file mode 100644 index 0000000..2976420 --- /dev/null +++ b/docs/html/training/game-controllers/index.jd @@ -0,0 +1,60 @@ +page.title=Supporting Game Controllers +page.tags="game controller" + +trainingnavtop=true +startpage=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<!-- Required platform, tools, add-ons, devices, knowledge, etc. --> +<h2>Dependencies and prerequisites</h2> +<ul> + <li>Android 2.3 (API level 9) or higher.</li> +</ul> + +<h2>You should also read</h2> +<ul> + <li><a +href="http://source.android.com/devices/tech/input/key-layout-files.html" +class="external-link" target="_blank">Key Layout Files</a></li> + <li><a href="{@docRoot}guide/topics/ui/ui-events.html">Input Events</a></li> +</ul> + +<h2>Try it out</h2> +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/ControllerSample.zip" +class="button">Download the sample</a> + <p class="filename">ControllerSample.zip</p> +</div> + +</div> +</div> + +<p>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.</p> + +<p>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.</p> + +<h2>Lessons</h2> + +<dl> + <dt><b><a href="controller-input.html">Handling Controller +Actions</a></b></dt> + <dd>Learn how to handle user input from common +input elements on game controllers, including directional pad (D-pad) +buttons, gamepad buttons, and joysticks.</dd> + <dt><b><a href="compatibility.html">Supporting Controllers Across Android +Versions</a></b></dt> + <dd>Learn how to make game controllers behave the same across +devices running different versions of Android.</dd> + <dt><b><a href="multiple-controllers.html">Supporting Multiple Game +Controllers</a></b></dt> + <dd>Learn how to detect and use multiple game controllers that +are simultaneously connected.</dd> diff --git a/docs/html/training/game-controllers/multiple-controllers.jd b/docs/html/training/game-controllers/multiple-controllers.jd new file mode 100644 index 0000000..6fcad45 --- /dev/null +++ b/docs/html/training/game-controllers/multiple-controllers.jd @@ -0,0 +1,130 @@ +page.title=Supporting Multiple Game Controllers +trainingnavtop=true + +@jd:body + +<!-- This is the training bar --> +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#map">Map Players to Controller Device IDs</a></li> + <li><a href="#detect">Process Multiple Controller Input</a></li> +</ol> + +<h2>Try it out</h2> +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/ControllerSample.zip" +class="button">Download the sample</a> + <p class="filename">ControllerSample.zip</p> +</div> + + +</div> +</div> +<p>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.</p> +<p>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. +</p> + +<h2 id="map">Map Players to Controller Device IDs</h2> +<p>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 <a href="controller-input.html#input">Verify a Game Controller is Connected</a>. You can then associate each +device ID with a player in your game, and process game actions for each player separately. +</p> +<p class="note"><strong>Note: </strong>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. +</p> +<p>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. +</p> +<p>The {@code onInputDeviceAdded()} and {@code onInputDeviceRemoved()} callback +methods are part of the abstraction layer introduced in +<a href="{@docRoot}training/game-controllers/compatibility.html#status_callbacks}"> +Supporting Controllers Across Android Versions</a>. 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. +</p> + +<pre> +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); +} +</pre> + +<h2 id="detect">Process Multiple Controller Input</h2> +<p>Your game should execute the following loop to process +input from multiple controllers: +</p> +<ol> +<li>Detect whether an input event occurred.</li> +<li>Identify the input source and its device ID.</li> +<li>Based on the action indicated by the input event key code or axis value, + update the player avatar associated with that device ID.</li> +<li>Render and update the user interface.</li> +</ol> +<p>{@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. +</p> +<p>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. +</p> +<pre> +@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); +} +</pre> +<p class="note"><strong>Note: </strong>As a best practice, when a user's +game controller disconnects, you should pause the game and ask if the user +wants to reconnect. +</p> diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 9e265ec..0616b62 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -1,6 +1,4 @@ <ul id="nav"> - - <li class="nav-section"> <div class="nav-section-header"> <a href="<?cs var:toroot ?>training/index.html"> @@ -1116,6 +1114,29 @@ results." </li> </ul> </li> + + <li class="nav-section"> + <div class="nav-section-header"> + <a href="<?cs var:toroot ?>training/game-controllers/index.html" + description= + "How to write apps that support game controllers." + >Supporting Game Controllers</a> + </div> + <ul> + <li><a href="<?cs var:toroot ?>training/game-controllers/controller-input.html"> + Handling Controller Actions + </a> + </li> + <li><a href="<?cs var:toroot ?>training/game-controllers/compatibility.html"> + Supporting Controllers Across Android Versions + </a> + </li> + <li><a href="<?cs var:toroot ?>training/game-controllers/multiple-controllers.html"> + Supporting Multiple Game Controllers + </a> + </li> + </ul> + </li> </ul> </li> <!-- end of User Input --> |