| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 1 | page.title=Handling Controller Actions |
| 2 | trainingnavtop=true |
| 3 | |
| 4 | @jd:body |
| 5 | |
| 6 | <!-- This is the training bar --> |
| 7 | <div id="tb-wrapper"> |
| 8 | <div id="tb"> |
| 9 | |
| 10 | <h2>This lesson teaches you to</h2> |
| 11 | <ol> |
| 12 | <li><a href="#input">Verify a Game Controller is Connected</a></li> |
| 13 | <li><a href="#button">Process Gamepad Button Presses</a> |
| 14 | </li> |
| 15 | <li><a href="#dpad">Process Directional Pad Input</a> |
| 16 | </li> |
| 17 | <li><a href="#joystick">Process Joystick Movements</a> |
| 18 | </li> |
| 19 | </ol> |
| 20 | |
| 21 | <h2>Try it out</h2> |
| 22 | <div class="download-box"> |
| 23 | <a href="http://developer.android.com/shareables/training/ControllerSample.zip" |
| 24 | class="button">Download the sample</a> |
| 25 | <p class="filename">ControllerSample.zip</p> |
| 26 | </div> |
| 27 | |
| 28 | </div> |
| 29 | </div> |
| 30 | |
| 31 | <p>At the system level, Android reports input event codes from game controllers |
| 32 | as Android key codes and axis values. In your game, you can receive these codes |
| 33 | and values and convert them to specific in-game actions.</p> |
| 34 | |
| 35 | <p>When players physically connect or wirelessly pair a game controller to |
| 36 | their Android-powered devices, the system auto-detects the controller |
| 37 | as an input device and starts reporting its input events. Your game can receive |
| 38 | these input events by implementing the following callback methods in your active |
| 39 | {@link android.app.Activity} or focused {@link android.view.View} (you should |
| 40 | implement the callbacks for either the {@link android.app.Activity} or |
| 41 | {@link android.view.View}, but not both): </p> |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 42 | <ul> |
| 43 | <li>From {@link android.app.Activity}: |
| 44 | <ul> |
| Quddus Chong | 349e576 | 2014-07-18 14:38:38 -0700 | [diff] [blame] | 45 | <li>{@link android.app.Activity#dispatchGenericMotionEvent(android.view.MotionEvent) |
| 46 | dispatchGenericMotionEvent(android.view. MotionEvent)} |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 47 | <p>Called to process generic motion events such as joystick movements.</p> |
| 48 | </li> |
| Quddus Chong | 349e576 | 2014-07-18 14:38:38 -0700 | [diff] [blame] | 49 | <li>{@link android.app.Activity#dispatchKeyEvent(android.view.KeyEvent) |
| 50 | dispatchKeyEvent(android.view.KeyEvent)} |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 51 | <p>Called to process key events such as a press or release of a |
| 52 | gamepad or D-pad button.</p> |
| 53 | </li> |
| 54 | </ul> |
| 55 | </li> |
| 56 | <li>From {@link android.view.View}: |
| 57 | <ul> |
| 58 | <li>{@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) |
| 59 | onGenericMotionEvent(android.view.MotionEvent)} |
| 60 | <p>Called to process generic motion events such as joystick movements.</p> |
| 61 | </li> |
| 62 | <li>{@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown(int, android.view.KeyEvent)} |
| 63 | <p>Called to process a press of a physical key such as a gamepad or |
| 64 | D-pad button.</p> |
| 65 | </li> |
| 66 | <li>{@link android.view.View#onKeyUp(int, android.view.KeyEvent) onKeyUp(int, android.view.KeyEvent)} |
| 67 | <p>Called to process a release of a physical key such as a gamepad or |
| 68 | D-pad button.</p> |
| 69 | </li> |
| 70 | </ul> |
| 71 | </li> |
| 72 | </ul> |
| 73 | |
| 74 | <p>The recommended approach is to capture the events from the |
| 75 | specific {@link android.view.View} object that the user interacts with. |
| 76 | Inspect the following objects provided by the callbacks to get information |
| 77 | about the type of input event received:</p> |
| 78 | |
| 79 | <dl> |
| 80 | <dt>{@link android.view.KeyEvent}</dt> |
| 81 | <dd>An object that describes directional |
| 82 | pad</a> (D-pad) and gamepad button events. Key events are accompanied by a |
| 83 | <em>key code</em> that indicates the specific button triggered, such as |
| 84 | {@link android.view.KeyEvent#KEYCODE_DPAD_DOWN DPAD_DOWN} |
| 85 | or {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}. You can obtain the |
| 86 | key code by calling {@link android.view.KeyEvent#getKeyCode()} or from key |
| 87 | event callbacks such as |
| 88 | {@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()}. |
| 89 | <dd> |
| 90 | <dt>{@link android.view.MotionEvent}</dt> |
| 91 | <dd>An object that describes input from joystick and shoulder trigger |
| 92 | movements. Motion events are accompanied by an action code and a set of |
| 93 | <em>axis values</em>. The action code specifies the state change that occurred |
| 94 | such as a joystick being moved. The axis values describe the position and other |
| 95 | movement properties for a specific physical control, such as |
| 96 | {@link android.view.MotionEvent#AXIS_X} or |
| 97 | {@link android.view.MotionEvent#AXIS_RTRIGGER}. You can obtain the action code |
| 98 | by calling {@link android.view.MotionEvent#getAction()} and the axis value by |
| 99 | calling {@link android.view.MotionEvent#getAxisValue(int) getAxisValue()}. |
| 100 | <dd> |
| 101 | </dl> |
| 102 | <p>This lesson focuses on how you can handle input from the most common types of |
| 103 | physical controls (gamepad buttons, directional pads, and |
| 104 | joysticks) in a game screen by implementing the above-mentioned |
| 105 | {@link android.view.View} callback methods and processing |
| 106 | {@link android.view.KeyEvent} and {@link android.view.MotionEvent} objects.</p> |
| 107 | |
| 108 | <h2 id="input">Verify a Game Controller is Connected</h2> |
| 109 | <p>When reporting input events, Android does not distinguish |
| 110 | between events that came from a non-game controller device and events that came |
| 111 | from a game controller. For example, a touch screen action generates an |
| 112 | {@link android.view.MotionEvent#AXIS_X} event that represents the X |
| 113 | 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 |
| 114 | your game cares about handling game-controller input, you should first check |
| 115 | that the input event comes from a relevant source type.</p> |
| 116 | <p>To verify that a connected input device is a game controller, call |
| 117 | {@link android.view.InputDevice#getSources()} to obtain a combined bit field of |
| 118 | input source types supported on that device. You can then test to see if |
| 119 | the following fields are set:</p> |
| 120 | <ul> |
| 121 | <li>A source type of {@link android.view.InputDevice#SOURCE_GAMEPAD} indicates |
| 122 | that the input device has gamepad buttons (for example, |
| 123 | {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}). Note that this source |
| 124 | type does not strictly indicate if the game controller has D-pad buttons, |
| 125 | although most gamepads typically have directional controls.</li> |
| 126 | <li>A source type of {@link android.view.InputDevice#SOURCE_DPAD} indicates that |
| 127 | the input device has D-pad buttons (for example, |
| 128 | {@link android.view.KeyEvent#KEYCODE_DPAD_UP DPAD_UP}).</li> |
| 129 | <li>A source type of {@link android.view.InputDevice#SOURCE_JOYSTICK} |
| 130 | indicates that the input device has analog control sticks (for example, a |
| 131 | joystick that records movements along {@link android.view.MotionEvent#AXIS_X} |
| 132 | and {@link android.view.MotionEvent#AXIS_Y}).</li> |
| 133 | </ul> |
| 134 | <p>The following code snippet shows a helper method that lets you check whether |
| 135 | the connected input devices are game controllers. If so, the method retrieves |
| 136 | the device IDs for the game controllers. You can then associate each device |
| 137 | ID with a player in your game, and process game actions for each connected |
| 138 | player separately. To learn more about supporting multiple game controllers |
| 139 | that are simultaneously connected on the same Android device, see |
| 140 | <a href="multiple-controllers.html">Supporting Multiple Game Controllers</a>.</p> |
| 141 | <pre> |
| 142 | public ArrayList<Integer> getGameControllerIds() { |
| 143 | ArrayList<Integer> gameControllerDeviceIds = new ArrayList<Integer>(); |
| 144 | int[] deviceIds = InputDevice.getDeviceIds(); |
| 145 | for (int deviceId : deviceIds) { |
| 146 | InputDevice dev = InputDevice.getDevice(deviceId); |
| 147 | int sources = dev.getSources(); |
| 148 | |
| 149 | // Verify that the device has gamepad buttons, control sticks, or both. |
| 150 | if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) |
| 151 | || ((sources & InputDevice.SOURCE_JOYSTICK) |
| 152 | == InputDevice.SOURCE_JOYSTICK)) { |
| 153 | // This device is a game controller. Store its device ID. |
| 154 | if (!gameControllerDeviceIds.contains(deviceId)) { |
| 155 | gameControllerDeviceIds.add(deviceId); |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | return gameControllerDeviceIds; |
| 160 | } |
| 161 | </pre> |
| 162 | <p>Additionally, you might want to check for individual input capabilities |
| 163 | supported by a connected game controller. This might be useful, for example, if |
| 164 | you want your game to use only input from the set of physical controls it |
| 165 | understands.</p> |
| 166 | <p>To detect if a specific key code or axis code is supported by a connected |
| 167 | game controller, use these techniques:</p> |
| 168 | <ul> |
| 169 | <li>In Android 4.4 (API level 19) or higher, you can determine if a key code is |
| 170 | supported on a connected game controller by calling |
| 171 | {@link android.view.InputDevice#hasKeys(int...)}.</li> |
| 172 | <li>In Android 3.1 (API level 12) or higher, you can find all available axes |
| 173 | supported on a connected game controller by first calling |
| 174 | {@link android.view.InputDevice#getMotionRanges()}. Then, on each |
| 175 | {@link android.view.InputDevice.MotionRange} object returned, call |
| 176 | {@link android.view.InputDevice.MotionRange#getAxis()} to get its axis ID.</li> |
| 177 | </ul> |
| 178 | |
| 179 | <h2 id="button">Process Gamepad Button Presses</h2> |
| 180 | <p>Figure 1 shows how Android maps key codes and axis values to the physical |
| 181 | controls on most game controllers.</p> |
| 182 | <img src="{@docRoot}images/training/game-controller-profiles.png" alt="" |
| 183 | id="figure1" /> |
| 184 | <p class="img-caption"> |
| 185 | <strong>Figure 1.</strong> Profile for a generic game controller. |
| 186 | </p> |
| 187 | <p>The callouts in the figure refer to the following:</p> |
| 188 | <div style="-moz-column-count:2;-webkit-column-count:2;column-count:2;"> |
| 189 | <ol style="margin-left:30px;list-style:decimal;"> |
| 190 | <li>{@link android.view.MotionEvent#AXIS_HAT_X}, |
| 191 | {@link android.view.MotionEvent#AXIS_HAT_Y}, |
| 192 | {@link android.view.KeyEvent#KEYCODE_DPAD_UP DPAD_UP}, |
| 193 | {@link android.view.KeyEvent#KEYCODE_DPAD_DOWN DPAD_DOWN}, |
| 194 | {@link android.view.KeyEvent#KEYCODE_DPAD_LEFT DPAD_LEFT}, |
| 195 | {@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT DPAD_RIGHT} |
| 196 | </li> |
| 197 | <li>{@link android.view.MotionEvent#AXIS_X}, |
| 198 | {@link android.view.MotionEvent#AXIS_Y}, |
| 199 | {@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBL BUTTON_THUMBL}</li> |
| 200 | <li>{@link android.view.MotionEvent#AXIS_Z}, |
| 201 | {@link android.view.MotionEvent#AXIS_RZ}, |
| 202 | {@link android.view.KeyEvent#KEYCODE_BUTTON_THUMBR BUTTON_THUMBR}</li> |
| 203 | <li>{@link android.view.KeyEvent#KEYCODE_BUTTON_X BUTTON_X}</li> |
| 204 | <li>{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}</li> |
| 205 | <li>{@link android.view.KeyEvent#KEYCODE_BUTTON_Y BUTTON_Y}</li> |
| 206 | <li>{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}</li> |
| 207 | <li>{@link android.view.KeyEvent#KEYCODE_BUTTON_R1 BUTTON_R1}</li> |
| 208 | <li>{@link android.view.MotionEvent#AXIS_RTRIGGER}, |
| 209 | {@link android.view.MotionEvent#AXIS_THROTTLE}</li> |
| 210 | <li>{@link android.view.MotionEvent#AXIS_LTRIGGER}, |
| 211 | {@link android.view.MotionEvent#AXIS_BRAKE}</li> |
| 212 | <li>{@link android.view.KeyEvent#KEYCODE_BUTTON_L1 BUTTON_L1}</li> |
| 213 | </ol> |
| 214 | </div> |
| 215 | <p>Common key codes generated by gamepad button presses include |
| 216 | {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}, |
| 217 | {@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}, |
| 218 | {@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT}, |
| 219 | and {@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}. Some game |
| 220 | controllers also trigger the {@link android.view.KeyEvent#KEYCODE_DPAD_CENTER |
| 221 | DPAD_CENTER} key code when the center of the D-pad crossbar is pressed. Your |
| 222 | game can inspect the key code by calling {@link android.view.KeyEvent#getKeyCode()} |
| 223 | or from key event callbacks such as |
| 224 | {@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()}, |
| 225 | and if it represents an event that is relevant to your game, process it as a |
| 226 | game action. Table 1 lists the recommended game actions for the most common |
| 227 | gamepad buttons. |
| 228 | </p> |
| 229 | |
| 230 | <p class="table-caption" id="table1"> |
| 231 | <strong>Table 1.</strong> Recommended game actions for gamepad |
| 232 | buttons.</p> |
| 233 | <table> |
| 234 | <tr> |
| 235 | <th scope="col">Game Action</th> |
| 236 | <th scope="col">Button Key Code</th> |
| 237 | </tr> |
| 238 | <tr> |
| 239 | <td>Start game in main menu, or pause/unpause during game</td> |
| quddusc | 3d7b134 | 2014-03-26 14:42:31 -0700 | [diff] [blame] | 240 | <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}<sup>*</sup></td> |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 241 | </tr> |
| 242 | <tr> |
| 243 | <td>Display menu</td> |
| quddusc | 3d7b134 | 2014-03-26 14:42:31 -0700 | [diff] [blame] | 244 | <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT}<sup>*</sup> |
| 245 | and {@link android.view.KeyEvent#KEYCODE_MENU}<sup>*</sup></td> |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 246 | </tr> |
| 247 | <tr> |
| Quddus Chong | 349e576 | 2014-07-18 14:38:38 -0700 | [diff] [blame] | 248 | <td>Same as Android <em>Back</em> navigation behavior described in the |
| 249 | <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> design |
| 250 | guide.</td> |
| quddusc | 3d7b134 | 2014-03-26 14:42:31 -0700 | [diff] [blame] | 251 | <td>{@link android.view.KeyEvent#KEYCODE_BACK KEYCODE_BACK}</td> |
| 252 | </tr> |
| 253 | <tr> |
| 254 | <td>Navigate back to a previous item in a menu</td> |
| Quddus Chong | 349e576 | 2014-07-18 14:38:38 -0700 | [diff] [blame] | 255 | <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}</td> |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 256 | </tr> |
| 257 | <tr> |
| 258 | <td>Confirm selection, or perform primary game action</td> |
| Quddus Chong | 349e576 | 2014-07-18 14:38:38 -0700 | [diff] [blame] | 259 | <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} and |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 260 | {@link android.view.KeyEvent#KEYCODE_DPAD_CENTER DPAD_CENTER}</td> |
| 261 | </tr> |
| 262 | </table> |
| 263 | <p> |
| quddusc | 3d7b134 | 2014-03-26 14:42:31 -0700 | [diff] [blame] | 264 | <em>* Your game should not rely on the presence of the Start, Select, or Menu |
| 265 | buttons.</em> |
| 266 | </p> |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 267 | |
| 268 | <p class="note"><strong>Tip: </strong>Consider providing a configuration screen |
| 269 | in your game to allow users to personalize their own game controller mappings for |
| 270 | game actions.</p> |
| 271 | |
| 272 | <p>The following snippet shows how you might override |
| 273 | {@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()} to |
| 274 | associate the {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} and |
| 275 | {@link android.view.KeyEvent#KEYCODE_DPAD_CENTER DPAD_CENTER} button presses |
| 276 | with a game action. |
| 277 | </p> |
| 278 | <pre> |
| 279 | public class GameView extends View { |
| 280 | ... |
| 281 | |
| 282 | @Override |
| 283 | public boolean onKeyDown(int keyCode, KeyEvent event) { |
| 284 | boolean handled = false; |
| 285 | if ((event.getSource() & InputDevice.SOURCE_GAMEPAD) |
| 286 | == InputDevice.SOURCE_GAMEPAD) { |
| 287 | if (event.getRepeatCount() == 0) { |
| 288 | switch (keyCode) { |
| 289 | // Handle gamepad and D-pad button presses to |
| 290 | // navigate the ship |
| 291 | ... |
| 292 | |
| 293 | default: |
| 294 | if (isFireKey(keyCode)) { |
| 295 | // Update the ship object to fire lasers |
| 296 | ... |
| 297 | handled = true; |
| 298 | } |
| 299 | break; |
| 300 | } |
| 301 | } |
| 302 | if (handled) { |
| 303 | return true; |
| 304 | } |
| 305 | } |
| 306 | return super.onKeyDown(keyCode, event); |
| 307 | } |
| 308 | |
| 309 | private static boolean isFireKey(int keyCode) { |
| 310 | // Here we treat Button_A and DPAD_CENTER as the primary action |
| Quddus Chong | 349e576 | 2014-07-18 14:38:38 -0700 | [diff] [blame] | 311 | // keys for the game. |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 312 | return keyCode == KeyEvent.KEYCODE_DPAD_CENTER |
| 313 | || keyCode == KeyEvent.KEYCODE_BUTTON_A; |
| 314 | } |
| 315 | } |
| 316 | </pre> |
| 317 | |
| Quddus Chong | 349e576 | 2014-07-18 14:38:38 -0700 | [diff] [blame] | 318 | <p class="note"><strong>Note: </strong>On Android 4.2 (API |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 319 | level 17) and lower, the system treats |
| 320 | {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} as the Android |
| 321 | <em>Back</em> key by default. If your app supports these Android |
| 322 | versions, make sure to treat |
| 323 | {@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A} as the primary game |
| Quddus Chong | 349e576 | 2014-07-18 14:38:38 -0700 | [diff] [blame] | 324 | action. To determine the current Android SDK |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 325 | version on the device, refer to the |
| Quddus Chong | 349e576 | 2014-07-18 14:38:38 -0700 | [diff] [blame] | 326 | {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT} value.</p> |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 327 | |
| 328 | <h2 id="dpad">Process Directional Pad Input</h2> |
| 329 | <p>The 4-way directional pad (D-pad) is a common physical control in many game |
| 330 | controllers. Android reports D-pad UP and DOWN presses as |
| 331 | {@link android.view.MotionEvent#AXIS_HAT_Y} events with a range |
| 332 | from -1.0 (up) to 1.0 (down), and D-pad LEFT or RIGHT presses as |
| 333 | {@link android.view.MotionEvent#AXIS_HAT_X} events with a range from -1.0 |
| 334 | (left) to 1.0 (right).</p> |
| 335 | <p>Some controllers instead report D-pad presses with a key code. If your game |
| 336 | cares about D-pad presses, you should treat the hat axis events and the D-pad |
| 337 | key codes as the same input events, as recommended in table 2.</p> |
| 338 | <p class="table-caption" id="table2"> |
| 339 | <strong>Table 2.</strong> Recommended default game actions for D-pad key |
| 340 | codes and hat axis values.</p> |
| 341 | <table> |
| 342 | <tr> |
| 343 | <th scope="col">Game Action</th> |
| 344 | <th scope="col">D-pad Key Code</th> |
| 345 | <th scope="col">Hat Axis Code</th> |
| 346 | </tr> |
| 347 | <tr> |
| 348 | <td>Move Up</td> |
| 349 | <td>{@link android.view.KeyEvent#KEYCODE_DPAD_UP}</td> |
| 350 | <td>{@link android.view.MotionEvent#AXIS_HAT_Y} (for values 0 to -1.0)</td> |
| 351 | </tr> |
| 352 | <tr> |
| 353 | <td>Move Down</td> |
| 354 | <td>{@link android.view.KeyEvent#KEYCODE_DPAD_DOWN}</td> |
| 355 | <td>{@link android.view.MotionEvent#AXIS_HAT_Y} (for values 0 to 1.0)</td> |
| 356 | </tr> |
| 357 | <tr> |
| 358 | <td>Move Left</td> |
| 359 | <td>{@link android.view.KeyEvent#KEYCODE_DPAD_LEFT}</td> |
| 360 | <td>{@link android.view.MotionEvent#AXIS_HAT_X} (for values 0 to -1.0)</td> |
| 361 | </tr> |
| 362 | <tr> |
| 363 | <td>Move Right</td> |
| 364 | <td>{@link android.view.KeyEvent#KEYCODE_DPAD_RIGHT}</td> |
| 365 | <td>{@link android.view.MotionEvent#AXIS_HAT_X} (for values 0 to 1.0)</td> |
| 366 | </tr> |
| 367 | </table> |
| 368 | |
| 369 | |
| 370 | <p>The following code snippet shows a helper class that lets you check the hat |
| 371 | axis and key code values from an input event to determine the D-pad direction. |
| 372 | </p> |
| 373 | <pre> |
| 374 | public class Dpad { |
| 375 | final static int UP = 0; |
| 376 | final static int LEFT = 1; |
| 377 | final static int RIGHT = 2; |
| 378 | final static int DOWN = 3; |
| 379 | final static int CENTER = 4; |
| 380 | |
| 381 | int directionPressed = -1; // initialized to -1 |
| 382 | |
| 383 | public int getDirectionPressed(InputEvent event) { |
| 384 | if (!isDpadDevice(event)) { |
| 385 | return -1; |
| 386 | } |
| 387 | |
| 388 | // If the input event is a MotionEvent, check its hat axis values. |
| 389 | if (event instanceof MotionEvent) { |
| 390 | |
| 391 | // Use the hat axis value to find the D-pad direction |
| 392 | MotionEvent motionEvent = (MotionEvent) event; |
| 393 | float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X); |
| 394 | float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y); |
| 395 | |
| 396 | // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad |
| 397 | // LEFT and RIGHT direction accordingly. |
| 398 | if (Float.compare(xaxis, -1.0f) == 0) { |
| 399 | directionPressed = Dpad.LEFT; |
| 400 | } else if (Float.compare(xaxis, 1.0f) == 0) { |
| 401 | directionPressed = Dpad.RIGHT; |
| 402 | } |
| 403 | // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad |
| 404 | // UP and DOWN direction accordingly. |
| 405 | else if (Float.compare(yaxis, -1.0f) == 0) { |
| 406 | directionPressed = Dpad.UP; |
| Quddus Chong | 349e576 | 2014-07-18 14:38:38 -0700 | [diff] [blame] | 407 | } else if (Float.compare(yaxis, 1.0f) == 0) { |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 408 | directionPressed = Dpad.DOWN; |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | // If the input event is a KeyEvent, check its key code. |
| 413 | else if (event instanceof KeyEvent) { |
| 414 | |
| 415 | // Use the key code to find the D-pad direction. |
| 416 | KeyEvent keyEvent = (KeyEvent) event; |
| 417 | if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) { |
| 418 | directionPressed = Dpad.LEFT; |
| 419 | } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) { |
| 420 | directionPressed = Dpad.RIGHT; |
| 421 | } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) { |
| 422 | directionPressed = Dpad.UP; |
| 423 | } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) { |
| 424 | directionPressed = Dpad.DOWN; |
| 425 | } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) { |
| 426 | directionPressed = Dpad.CENTER; |
| 427 | } |
| 428 | } |
| 429 | return directionPressed; |
| 430 | } |
| 431 | |
| 432 | public static boolean isDpadDevice(InputEvent event) { |
| 433 | // Check that input comes from a device with directional pads. |
| 434 | if ((event.getSource() & InputDevice.SOURCE_DPAD) |
| 435 | != InputDevice.SOURCE_DPAD) { |
| 436 | return true; |
| 437 | } else { |
| 438 | return false; |
| 439 | } |
| 440 | } |
| 441 | } |
| 442 | </pre> |
| 443 | |
| 444 | <p>You can use this helper class in your game wherever you want to process |
| 445 | D-pad input (for example, in the |
| 446 | {@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) |
| 447 | onGenericMotionEvent()} or |
| 448 | {@link android.view.View#onKeyDown(int, android.view.KeyEvent) onKeyDown()} |
| 449 | callbacks).</p> |
| 450 | <p>For example:</p> |
| 451 | <pre> |
| 452 | Dpad mDpad = new Dpad(); |
| 453 | ... |
| 454 | @Override |
| 455 | public boolean onGenericMotionEvent(MotionEvent event) { |
| 456 | |
| 457 | // Check if this event if from a D-pad and process accordingly. |
| 458 | if (Dpad.isDpadDevice(event)) { |
| 459 | |
| 460 | int press = mDpad.getDirectionPressed(event); |
| 461 | switch (press) { |
| 462 | case LEFT: |
| 463 | // Do something for LEFT direction press |
| 464 | ... |
| 465 | return true; |
| 466 | case RIGHT: |
| 467 | // Do something for RIGHT direction press |
| 468 | ... |
| 469 | return true; |
| 470 | case UP: |
| 471 | // Do something for UP direction press |
| 472 | ... |
| 473 | return true; |
| 474 | ... |
| 475 | } |
| 476 | } |
| 477 | |
| 478 | // Check if this event is from a joystick movement and process accordingly. |
| 479 | ... |
| 480 | } |
| 481 | </pre> |
| 482 | |
| 483 | <h2 id="joystick">Process Joystick Movements</h2> |
| 484 | <p>When players move a joystick on their game controllers, Android reports a |
| 485 | {@link android.view.MotionEvent} that contains the |
| 486 | {@link android.view.MotionEvent#ACTION_MOVE} action code and the updated |
| 487 | positions of the joystick's axes. Your game can use the data provided by |
| 488 | the {@link android.view.MotionEvent} to determine if a joystick movement it |
| 489 | cares about happened. |
| 490 | </p> |
| 491 | <p>Note that joystick motion events may batch multiple movement samples together |
| 492 | within a single object. The {@link android.view.MotionEvent} object contains |
| 493 | the current position for each joystick axis as well as multiple historical |
| 494 | 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 |
| 495 | axis values for efficiency. The historical values for an axis consists of the |
| 496 | set of distinct values older than the current axis value, and more recent than |
| 497 | values reported in any previous motion events. See the |
| 498 | {@link android.view.MotionEvent} reference for details.</p> |
| 499 | <p>You can use the historical information to more accurately render a game |
| 500 | object's movement based on the joystick input. To |
| 501 | retrieve the current and historical values, call |
| 502 | {@link android.view.MotionEvent#getAxisValue(int) |
| 503 | getAxisValue()} or {@link android.view.MotionEvent#getHistoricalAxisValue(int, |
| 504 | int) getHistoricalAxisValue()}. You can also find the number of historical |
| 505 | points in the joystick event by calling |
| 506 | {@link android.view.MotionEvent#getHistorySize()}.</p> |
| 507 | <p>The following snippet shows how you might override the |
| 508 | {@link android.view.View#onGenericMotionEvent(android.view.MotionEvent) |
| 509 | onGenericMotionEvent()} callback to process joystick input. You should first |
| 510 | process the historical values for an axis, then process its current position. |
| 511 | </p> |
| 512 | <pre> |
| 513 | public class GameView extends View { |
| 514 | |
| 515 | @Override |
| 516 | public boolean onGenericMotionEvent(MotionEvent event) { |
| 517 | |
| 518 | // Check that the event came from a game controller |
| 519 | if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == |
| 520 | InputDevice.SOURCE_JOYSTICK && |
| Quddus Chong | 03c15cf | 2015-01-16 11:35:07 -0800 | [diff] [blame] | 521 | event.getAction() == MotionEvent.ACTION_MOVE) { |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 522 | |
| 523 | // Process all historical movement samples in the batch |
| 524 | final int historySize = event.getHistorySize(); |
| 525 | |
| 526 | // Process the movements starting from the |
| 527 | // earliest historical position in the batch |
| 528 | for (int i = 0; i < historySize; i++) { |
| 529 | // Process the event at historical position i |
| 530 | processJoystickInput(event, i); |
| 531 | } |
| 532 | |
| 533 | // Process the current movement sample in the batch (position -1) |
| 534 | processJoystickInput(event, -1); |
| 535 | return true; |
| 536 | } |
| 537 | return super.onGenericMotionEvent(event); |
| 538 | } |
| 539 | } |
| 540 | </pre> |
| 541 | <p>Before using joystick input, you need to determine if the joystick is |
| 542 | centered, then calculate its axis movements accordingly. Joysticks typically |
| 543 | have a <em>flat</em> area, that is, a range of values near the (0,0) coordinate |
| 544 | at which the axis is considered to be centered. If the axis value reported by |
| 545 | Android falls within the flat area, you should treat the controller to be at |
| 546 | rest (that is, motionless along both axes).</p> |
| 547 | <p>The snippet below shows a helper method that calculates the movement along |
| 548 | each axis. You invoke this helper in the {@code processJoystickInput()} method |
| 549 | described further below. |
| 550 | </p> |
| 551 | <pre> |
| 552 | private static float getCenteredAxis(MotionEvent event, |
| 553 | InputDevice device, int axis, int historyPos) { |
| 554 | final InputDevice.MotionRange range = |
| 555 | device.getMotionRange(axis, event.getSource()); |
| 556 | |
| 557 | // A joystick at rest does not always report an absolute position of |
| 558 | // (0,0). Use the getFlat() method to determine the range of values |
| 559 | // bounding the joystick axis center. |
| 560 | if (range != null) { |
| 561 | final float flat = range.getFlat(); |
| 562 | final float value = |
| 563 | historyPos < 0 ? event.getAxisValue(axis): |
| 564 | event.getHistoricalAxisValue(axis, historyPos); |
| 565 | |
| 566 | // Ignore axis values that are within the 'flat' region of the |
| 567 | // joystick axis center. |
| 568 | if (Math.abs(value) > flat) { |
| 569 | return value; |
| 570 | } |
| 571 | } |
| 572 | return 0; |
| 573 | } |
| 574 | </pre> |
| 575 | <p>Putting it all together, here is how you might process joystick movements in |
| 576 | your game:</p> |
| 577 | <pre> |
| 578 | private void processJoystickInput(MotionEvent event, |
| 579 | int historyPos) { |
| 580 | |
| 581 | InputDevice mInputDevice = event.getDevice(); |
| 582 | |
| 583 | // Calculate the horizontal distance to move by |
| 584 | // using the input value from one of these physical controls: |
| 585 | // the left control stick, hat axis, or the right control stick. |
| 586 | float x = getCenteredAxis(event, mInputDevice, |
| 587 | MotionEvent.AXIS_X, historyPos); |
| 588 | if (x == 0) { |
| 589 | x = getCenteredAxis(event, mInputDevice, |
| 590 | MotionEvent.AXIS_HAT_X, historyPos); |
| 591 | } |
| 592 | if (x == 0) { |
| 593 | x = getCenteredAxis(event, mInputDevice, |
| 594 | MotionEvent.AXIS_Z, historyPos); |
| 595 | } |
| 596 | |
| 597 | // Calculate the vertical distance to move by |
| 598 | // using the input value from one of these physical controls: |
| 599 | // the left control stick, hat switch, or the right control stick. |
| 600 | float y = getCenteredAxis(event, mInputDevice, |
| 601 | MotionEvent.AXIS_Y, historyPos); |
| 602 | if (y == 0) { |
| 603 | y = getCenteredAxis(event, mInputDevice, |
| 604 | MotionEvent.AXIS_HAT_Y, historyPos); |
| 605 | } |
| 606 | if (y == 0) { |
| 607 | y = getCenteredAxis(event, mInputDevice, |
| 608 | MotionEvent.AXIS_RZ, historyPos); |
| 609 | } |
| 610 | |
| 611 | // Update the ship object based on the new x and y values |
| quddusc | 1dff746 | 2013-07-01 16:57:26 -0700 | [diff] [blame] | 612 | } |
| 613 | </pre> |
| 614 | <p>To support game controllers that have more sophisticated |
| 615 | features beyond a single joystick, follow these best practices: </p> |
| 616 | <ul> |
| 617 | <li><strong>Handle dual controller sticks.</strong> Many game controllers have |
| 618 | both a left and right joystick. For the left stick, Android |
| 619 | reports horizontal movements as {@link android.view.MotionEvent#AXIS_X} events |
| 620 | and vertical movements as {@link android.view.MotionEvent#AXIS_Y} events. |
| 621 | For the right stick, Android reports horizontal movements as |
| 622 | {@link android.view.MotionEvent#AXIS_Z} events and vertical movements as |
| 623 | {@link android.view.MotionEvent#AXIS_RZ} events. Make sure to handle |
| 624 | both controller sticks in your code.</li> |
| 625 | <li><strong>Handle shoulder trigger presses (but provide alternative input |
| 626 | methods).</strong> Some controllers have left and right shoulder |
| 627 | triggers. If these triggers are present, Android reports a left trigger press |
| 628 | as an {@link android.view.MotionEvent#AXIS_LTRIGGER} event and a |
| 629 | right trigger press as an |
| 630 | {@link android.view.MotionEvent#AXIS_RTRIGGER} event. On Android |
| 631 | 4.3 (API level 18), a controller that produces a |
| 632 | {@link android.view.MotionEvent#AXIS_LTRIGGER} also reports an |
| 633 | identical value for the {@link android.view.MotionEvent#AXIS_BRAKE} axis. The |
| 634 | same is true for {@link android.view.MotionEvent#AXIS_RTRIGGER} and |
| 635 | {@link android.view.MotionEvent#AXIS_GAS}. Android reports all analog trigger |
| 636 | presses with a normalized value from 0.0 (released) to 1.0 (fully pressed). Not |
| 637 | all controllers have triggers, so consider allowing players to perform those |
| 638 | game actions with other buttons. |
| 639 | </li> |
| 640 | </ul> |