am 5020a873: Merge "cherrypick from jb-mr2-docs docs: Training class for game controllers. Change-Id: I697770aee8604c965c3730691459c1e8f10705da" into klp-docs

* commit '5020a8736f3319d295d31bed421f837788878451':
  cherrypick from jb-mr2-docs docs: Training class for game controllers. Change-Id: I697770aee8604c965c3730691459c1e8f10705da
This commit is contained in:
quddusc
2014-01-22 04:10:13 +00:00
committed by Android Git Automerger
10 changed files with 5361 additions and 2 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -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>&nbsp;</big></td>
<td style="text-align: center;"><big>&nbsp;</big></td>
<td style="text-align: center;"><big>&bull;</big></td>
</tr>
<tr>
<td>{@link android.hardware.input.InputManager#getInputDevice(int)
getInputDevice()}</td>
<td style="text-align: center;">&nbsp;</td>
<td style="text-align: center;"><big>&nbsp;</big></td>
<td style="text-align: center;"><big>&bull;</big></td>
</tr>
<tr>
<td>{@link android.view.InputDevice#getVibrator()}</td>
<td style="text-align: center;">&nbsp;</td>
<td style="text-align: center;"><big>&nbsp;</big></td>
<td style="text-align: center;"><big>&bull;</big></td>
</tr>
<td>{@link android.view.InputDevice#SOURCE_JOYSTICK}</td>
<td style="text-align: center;">&nbsp;</td>
<td style="text-align: center;"><big>&bull;</big></td>
<td style="text-align: center;"><big>&bull;</big></td>
</tr>
<tr>
<td>{@link android.view.InputDevice#SOURCE_GAMEPAD}</td>
<td style="text-align: center;">&nbsp;</td>
<td style="text-align: center;"><big>&bull;</big></td>
<td style="text-align: center;"><big>&bull;</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;">&nbsp;</td>
<td style="text-align: center;">&nbsp;</td>
<td style="text-align: center;"><big>&bull;</big></td>
</tr>
<tr>
<td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceChanged(int) onInputDeviceChanged()}</td>
<td style="text-align: center;">&nbsp;</td>
<td style="text-align: center;">&nbsp;</td>
<td style="text-align: center;"><big>&bull;</big></td>
</tr>
<tr>
<td>{@link android.hardware.input.InputManager.InputDeviceListener#onInputDeviceRemoved(int) onInputDeviceRemoved()}</td>
<td style="text-align: center;">&nbsp;</td>
<td style="text-align: center;">&nbsp;</td>
<td style="text-align: center;"><big>&bull;</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>&bull;</big></td>
<td style="text-align: center;"><big>&bull;</big></td>
<td style="text-align: center;"><big>&bull;</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;">&nbsp;</td>
<td style="text-align: center;"><big>&bull;</big></td>
<td style="text-align: center;"><big>&bull;</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;">&nbsp;</td>
<td style="text-align: center;"><big>&bull;</big></td>
<td style="text-align: center;"><big>&bull;</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;">&nbsp;</td>
<td style="text-align: center;"><big>&bull;</big></td>
<td style="text-align: center;"><big>&bull;</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>();
}
&#64;Override
public InputDevice getInputDevice(int id) {
return mInputManager.getInputDevice(id);
}
&#64;Override
public int[] getInputDeviceIds() {
return mInputManager.getInputDeviceIds();
}
static class V16InputDeviceListener implements
InputManager.InputDeviceListener {
final InputManagerCompat.InputDeviceListener mIDL;
public V16InputDeviceListener(InputDeviceListener idl) {
mIDL = idl;
}
&#64;Override
public void onInputDeviceAdded(int deviceId) {
mIDL.onInputDeviceAdded(deviceId);
}
// Do the same for device change and removal
...
}
&#64;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
...
&#64;Override
public void onGenericMotionEvent(MotionEvent event) {
// unused in V16
}
&#64;Override
public void onPause() {
// unused in V16
}
&#64;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);
}
&#64;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 &lt; 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;
&#64;Override
public void onPause() {
mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
}
&#64;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>
&#64;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>
&#64;Override
public void registerInputDeviceListener(InputDeviceListener listener,
Handler handler) {
mListeners.remove(listener);
if (handler == null) {
handler = mDefaultHandler;
}
mListeners.put(listener, handler);
}
&#64;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;
}
&#64;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>
&#64;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>

View File

@@ -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 &amp; InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
|| ((sources &amp; 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 {
...
&#64;Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean handled = false;
if ((event.getSource() &amp; 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() &amp; 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();
...
&#64;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 {
&#64;Override
public boolean onGenericMotionEvent(MotionEvent event) {
// Check that the event came from a game controller
if ((event.getSource() &amp; InputDevice.SOURCE_JOYSTICK) ==
InputDevice.SOURCE_JOYSTICK &amp;&amp;
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 &lt; 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 &lt; 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>

View File

@@ -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>

View File

@@ -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 devices 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&lt;Ship&gt; mShips = new SparseArray&lt;Ship&gt;();
&#64;Override
public void onInputDeviceAdded(int deviceId) {
getShipForID(deviceId);
}
&#64;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>
&#64;Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((event.getSource() &amp; 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>

View File

@@ -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 -->

File diff suppressed because it is too large Load Diff