How To: Support Android Game Controllers

How To: Support Android Game Controllers

Mobile gaming applications that add support for alternate control methods like keyboard or joystick can transform the gaming experience from that of a basic tablet into a compelling, immersive gaming experience. The intention of this document is to help Android game developers add support for such controllers. The document will outline the process of coding for basic use of keyboard, wired controllers, and Bluetooth controllers in gaming applications. We will use a fictitious 2-player game’s controller system as a guide for learning the new methods. The idea is to focus on the parts of code related specifically to controller use. Note that when we use the word “controllers,” we include keyboards along with the more obvious joysticks and gamepads.

First, we will review a class that saves the controller state. This class can return data to the application such as whether the B button pressed or the current position of the joystick. This provides a basic foundation for many controller concepts without clouding the discussion with the complexity of game logic.

Having reviewed the lower-level input device support code, we will discuss how the application receives and dispatches the events in the first place; the application’s Activity class. This class is where all of our game logic exists, as well as where all the controller inputs are received and initially processed prior to passing to the processing functions. The Activity is where the player’s input is translated to in-game actions on screen.

IMPORTANT: As many controllers are different, is it important that applications handle remapping of devices. This is critical for a successful Android game that supports controllers. All developers should note the “Important Controller Considerations” chapters which discuss handling controller differences to avoid conformance/compatibility issues later.

Why add game controller support?

The following table lists some mobile market statistics:

$5.6B Mobile Gaming End-User Revenues in 2010 Gartner 5/2010
400K Android devices activated daily Google IO 2011
100M+ Android devices activated globally Google IO 2011
200K Android Market apps Google IO 2011
70%+ Android Market downloads are games Gartner 5/2010

Each passing year has brought continued, massive growth in the mobile gaming industry. For instance, the above $5.6B statistic represents a 19% gain over 2009. The same report expects revenues of $11.4B by 2014. This represents the mobile market and the role of games therein, but where does mobile gaming fit in the gaming universe?

US Video Game Software by Revenue
2009 % 2010 % Growth Segment
5 8 +60% Android & iOS
24 16 -33% PSP & DS
71 76 +7% Console

(Source: Flurry Analytics 5/2011)

Statistic after statistic shows explosive growth in the mobile market. The same type of growth can be seen in the horsepower of modern SoC’s like NVIDIA’s Tegra line. The delta in horsepower between modern day consoles and current Super Phones and Tablets is shrinking at an incredible pace. The performance per watt of power is amazing.

To accelerate that growth, we can show that these new devices give console-like or better experiences. One of the ways to grab those console players, add real game differentiation, and create game transformative controls is to include the ability to use other gaming like control methods.

The Fictitious Game

The Fictitious Game

To provide some context throughout this document, we’ll assume that we’re upgrading a game to add controller support. This game will be two-player capable, as we want to show the ability to support more than one controller. It will be able to handle wired controllers, Bluetooth controllers, and keyboards.

This game’s controller system will be somewhat modeled after the Android SDK’s APIDemos example from the API 12 Samples. This particular game’s control system itself is not what is being explored here; it is merely one example of a system and a means to explore controller use. For clarity, we will use a mix of pseudo-code and java. Java will always be used when doing anything critical to game controller API interaction.

There will be no specific discussion about controllers and the Android Native Development Kit, or NDK. In fact, it bears mentioning that many of these new interfaces, specifically joystick support, are not available in the NDK. Developers will need to use Java to add that support, even if they are using NativeActivity. These events can be passed to the application’s native code if desired. However, this must be accomplished via game-specific JNI code, and will not be discussed in this document.

Thankfully, implementing controllers in Android is fairly simple. Throughout this document we will primarily focus on a mere handful of classes/APIs. While not immediately necessary to understand this document, we recommend looking at the documentation for further reading or reference. As more and more advanced controller support is integrated, these links will become increasingly important.

InputDevice class: This describes the capabilities of a particular input device.

http://developer.android.com/reference/android/view/InputDevice.html

InputDevice.MotionRange class: This provides information about the range of values for a particular axis. For instance, a joystick will typically have two axes, one for the up to down motion and another for the left to right motion.

http://developer.android.com/reference/android/view/InputDevice.MotionRange.html

MotionEvent class: This is used to report movement events (finger, pen, mouse, joystick, etc.).

http://developer.android.com/reference/android/view/MotionEvent.html

KeyEvent class: This is used to report key and button events.

http://developer.android.com/reference/android/view/KeyEvent.html

Activity class: This is the high level class that receives MotionEvents and KeyEvents. It is usually where much of your game’s lifecycle processing is handled.

http://developer.android.com/reference/android/app/Activity.html

Controller State System

Controller State System

Overview

This game will use a custom, app-specific class, InputDeviceState, to hold the values received from a particular controller. Since Android only informs the game about changes in state, this class is designed to adapt those events that represent the changes in state into an overall snapshot of the state of the device. The state can then easily be polled later by any system. Since two people and hence two controllers can play this game we will need to instantiate two of these objects. However, we will only instantiate them when needed. Another typical method would be to instantiate all needed device states at level load time. The following block lists the class’s member variables (ignoring for the moment any member methods):

public static class InputDeviceState {
     private InputDevice	mDevice;
     private int[]		mAxes;
     private float[]		mAxisValues;
     private SparseIntArray	mKeys;
}

mDevice is the InputDevice. We’ll save it here for convenience. The InputDevice class describes the capabilities of a particular set of inputs that are grouped as a single device, such as a multi-axis, multi-button gamepad. Even if a device contains multiple input methods, like a pair of joysticks and a set of buttons on a gamepad, there is only one InputDevice.

Your game can query which “source” an input came from. Take for instance, the gamepad mentioned above; it has a pair of joysticks and a set of buttons. This gamepad is considered one InputDevice. If you query the InputDevice on what sources it has, you’re likely to get joystick sources and button sources. Every input will have what InputDevice and what source it came from.

Since the InputDeviceState class is meant to serve all controller types, from analog joysticks sticks to a keyboard button, it will need to save key pressed information and joystick axes information. The mAxes array and its companion mAxisValues array, hold the identifying axis id and the current axis value respectively. Similarly, mKeys will hold the current key value, either up or down. It is using the Android SparseIntArray using the pressed key’s code (keyCode) as the key for the array.

Axes

Axes bring a bit more complication to controller use. A simple key press can be up or down, whereas an axis value requires additional information regarding the mapping of the current position. For instance, what are the possible min/max ranges? Below is helper routine, built with the intension to show axis usage. It is a member function of the InputDeviceState class and it will process an axis’ input.

public static float ProcessAxis(InputDevice.MotionRange range,
     float axisvalue) {
     float absaxisvalue = Math.abs(axisvalue);
     float deadzone = range.getFlat();
     if (absaxisvalue <= deadzone) {
          return 0.0f;
     }
     float normalizedvalue;
     if (axisvalue < 0.0f) {
          normalizedvalue = absaxisvalue / range.getMin();
     } else {
          normalizedvalue = absaxisvalue / range.getMax();
     }

     return normailzedvalue;
} 

Every InputDevice can have multiple axes. A simple analog joystick should have two axes, one for up to down, and the other for left to right. Each axis has a range associated with it which describes the range values for that axis. A range, InputDevice.MotionRange, is an input to this routine, as is an axis value.

Android typically returns an already normalized (-1.0f to 1.0f) axis value. This routine is being extra careful of calculations to the axis value that may have preceded it by normalizing before exiting. In reality, for this simple method, this normalization is under the guise of learning a bit more about the MotionRange values and is superfluous.

Another MotionRange value, which is not discussed is from the getFuzz() method. It returns an error tolerance for input device with respect to the axis. For example, a value of 2 indicates that the axis value may be up to +/- 2 units away from the actual value. If your game requires precise controller values, the fuzz value should be returned with this method.

Starting from the top of the routine, we take the absolute value of the input axis value. We do this as it simplifies some of our further calculations by either giving magnitude or keeping the sign of the calculation.

The next bit of code handles the “dead zone” that most analog controllers have. Not all controllers at rest report (0, 0). The dead zone is the area around (0, 0) that should be considered “at rest”. The value reported for the dead zone from getFlat() is a +/- float value. If the input value is within this zone, 0.0f is returned.

The final bit of code normalizes the input axis value. Depending on the axis value being negative or positive, we divide against getMin() or getMax() from the range respectively. That calculated value is returned as the normalized value. Note there is little to no error checking in these routines like divide by zero or checking return codes.

Supporting Methods

The InputDeviceState class contains “get” and “set” methods for querying and setting member variables. We will not cover those here, but will highlight the constructor and an important query function below.

public InputDeviceState(InputDevice device) {
     mDevice = device;
     int numAxes = 0;
     for (MotionRange range : device.getMotionRanges()) {
          if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
               numAxes += 1;
          }
     }

     mAxes		= new int[numAxes];
     mAxisValues	= new float[numAxes];
     mKeys		= new SparseIntArray();

     int i = 0;
     for (MotionRange range : device.getMotionRanges()) {
          if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
               mAxes[i++] = range.getAxis();
          }
     }
} 

The constructor begins with saving off the device for convenience.

The next section of code is ultimately trying to count the number of axes we care about from this device. An axis is defined using a MotionRange which describes the range of values for that axis. For each MotionRange, we are going to check if that range (hence axis) is from a source we care about and increment numAxes.

Each range has a source that can be checked. We use getSource() to get the source for which the axis is defined. Then compare that with the defined sources from InputDevice, in this case, InputDevice.SOURCE_CLASS_JOYSTICK. Since we are dealing with analog controllers, we have chosen to filter out sources that are not traditionally analog like SOURCE_DPAD, SOURCE_GAMEPAD, etc.

Now that we have the number of axes (numAxes) the mAxes and mAxisValues arrays can be allocated. The mKeys sparse array is also allocated at this time.

The final section of this constructor populates the mAxes array which contains axis ids. We again go through all the axes and only consider the SOURCE_CLASS_JOYSTICK axes. To finally get the axis id we call getAxis() on the range and populate the array.

Another support routine, isGameKey, answers the question - given this key code, does my game care. The Android method that answers a similar question is included here along with key codes this game needs. By doing this, we sort of extend the Android method.

private static boolean isGameKey(int keyCode) {
     switch (keyCode) {
          case KeyEvent.KEYCODE_DPAD_UP:
          case KeyEvent.KEYCODE_DPAD_DOWN:
          case KeyEvent.KEYCODE_DPAD_LEFT:
          case KeyEvent.KEYCODE_DPAD_RIGHT:
               return true;
          default:
               return KeyEvent.isGamepadButton(keyCode);
}

This routine returns true if the input key code is any of the keyboard arrow keys – KEYCODE_DPAD_xx. It also calls the static method KeyEvent.isGamepadButton() which returns a boolean if the key code is a “gamepad” button – KEYCODE_BUTTON_xx.

Key Presses

The InputDeviceState class will need to be called for all controller events so it can process them and save the state. In this section we will handle when a key press event has occurred. When a key is pressed this class needs the onKeyDown() method to be called. When a key is released, the onKeyUp() method should be called. Both methods return a boolean indicating whether the key has been processed.

public boolean onKeyDown(KeyEvent event) {
     int keyCode = event.getKeyCode();
     if (event.getRepeatCount() == 0) {
         if (isGameKey(keyCode)) {
              mKeys.put(keyCode, 1);
              return true;
         }
     }
     return false;
} 

The onKeyDown method is called with a KeyEvent. The KeyEvent in Android is used to report key and button events. To get the key code from the event we call the getKeyCode() method.

Now that we have the key code, we should check if this is a repeat key event meaning the player is likely holding his finger down on a key. We do this by calling the getRepeatCount() method from the key event. If it returns 0, it means this is the first down event of the key down/key up pair. We want to act only on the first down event.

Using the first key down event, we check if it’s a key we care about in our game by calling isGameKey() , defined in the “Supporting Function” section, passing it the key code.

So we now have a good key code and we know this event isn’t a repeat event, we finally populate our key state sparse array using mKeys.put(keyCode, 1). Basically this sets the key code to “on” in the mKeys array.

We return true or false depending on whether we processed this key or not.

Much of the onKeyUp() method is similar to the onKeyDown() method. We will skip the similarities and focus quickly on the single difference.

public boolean onKeyUp(KeyEvent event) {
     int keyCode = event.getKeyCode();
     if (isGameKey(keyCode)) {
         mKeys.put(keyCode, 0);
         return true;
     }
     return false;
} 

Jumping ahead, after we’ve decided this is a valid game key code, we set the key to “off” in our mKey key state array by mKeys.put(keyCode, 0). This is the only difference between the down and up methods.

Joystick Movement

In this section we will handle when a joystick has been moved. This game only cares about one analog source, the joystick. Recall in the constructor, when enumerating all the axes of the device, we only focused on the axes that had a source of SOURCE_CLASS_JOYSTICK. When the joystick has moved this class requires the onJoystickMotion method to be called.

public boolean onJoystickMotion(MotionEvent event) {
     if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0) {
         return false;
     }
     for (int i = 0; i < mAxes.length; i++) {
         int axisId		= mAxes[i];
     }

     return true;
} 

The input to this method is the MotionEvent. This object is used to report movement events. Anything from touchscreen, mouse, trackball, to joystick can be the source of this event. Unlike KeyEvents, which return only the new state of a single changing key per event, a MotionEventreturns a simultaneous snapshot of all of the input device’s axes. This is true even if only one of the axes in the device has changed value since the previous MotionEvent.

It is also important to note that a MotionEventevent may batch together multiple movement samples within a single object. These samples are batched for each of the device’s axes. In this InputDeviceStats class we will only be saving the state of the most recent samples. The “historical” values (batched values) will be discussed and used later.

The beginning of this method makes sure the input MotionEventis from a joystick source. This method is only called when it knows the event is from a joystick source. While this shouldn’t be needed, we are just doing a sanity check on the input.

The motion event includes updated values for all axes of the input device. We have already recorded the indices of the axes we care about in mAxes, likely a proper subset of the device’s full set of axes. Thus, we loop over the mAxes array, each entry of which is the index of an axis of interest. The MotionEventmember function getAxisValue() maps each index to the axis’s current value. We cache this value in the corresponding element of mAxisValues.

The method returns true, indicating it processed the event.

The Activity

The Activity

Overview

The state of a controller is saved in the InputDeviceState class which is described above. While learning about that class InputDevice, InputDevice.MotionRange, KeyEvent, and MotionEvent were explored.

Now we need to see where that class and all that exploration can fit in our Fictitious Game. Only the parts of the game that relate to controller use will be examined. The intention is to simply and straightforwardly show controller use and not get caught up in game logics.

This is the main Activity class named FictitiousGame extended from Activity. Only the member variables necessary for controllers are shown.

public class FictitiousGame extends Activity {
     private InputDeviceState	inputPlayerA;
     private InputDeviceState	inputPlayerB;
     private PlayerShip		playerShipA;
     private PlayerShip		playerShipB;
}

Fictitious Game can be up to a two player game. The letters A and B are used to denote player 1 and player 2 respectively. As you can see, there is one InputDeviceState class and one PlayerShip class per a player.

The InputDeviceState is the class from the “Controller State System”. In short, this class will keep the state of the controller for each player. The controllers will be assigned in order. The first controller to produce a KeyEvent or MotionEvent will be player A’s controller (inputPlayerA). The second controller to produce an event will be assigned to player B.

Another way to assign controllers is during game initialization. A list of all attached input devices can easily be gotten by calling InputDevice.getDeviceIds(). The returned int array contains device ids for each device. A call to InputDevice.getDevice() passing it one of the device ids from the array will return the corresponding InputDevice object. You should have all you need to check the device source to see if it’s a joystick, keyboard, etc.

The PlayerShip class isn’t necessary to explore much. All we need to know is that there are member functions to set the “ship” on a particular heading – setHeadingX() and setHeadingY().

As well as a function that will move the ship along the heading given the time – moveShip(). Recall that motion events are sometimes batched and key events aren’t always in real time.

Supporting Methods

Before getting to the handling the key events, we need some helper functions. These are application specific. The first helper we need answers the question, given an InputEvent, what InputDeviceState should be updated. KeyEvents and MotionEvents are extended from InputEvent.

private InputDeviceState getInputDeviceState(InputEvent event) {
     InputDevice device = event.getDevice();
     if (device == null) {
         return null;
     }
     if (inputPlayerA == null) {
         inputPlayerA = new InputDeviceState(device);
     }

     if (inputPlayerA.getDevice() == device) {
         return inputPlayerA;
     }

     if (inputPlayerB == null) {
         inputPlyerB = new InputDeviceStats(device);
     }

     if (inputPlayerB.getDevice() == device) {
         return inputPlayerB;
     }

     return null;
} 

It’s important to note this method is dynamically allocating and assigning a device to a player. The first time this method is called the device that generated the InputEvent is ultimately assigned to inputPlayerA, the second to inputPlayerB. Further events just return null. Additionally note that the method does no filtering on the event’s source, it is assumed reasonable filtering has already been done.

The other helper method getPlayerShip(), which we will forgo showing any code for, simply takes an InputDeviceState and returns the matching PlayerShip class. So given a player’s controller event, their proper InputDeviceState and PlayerShip can be gained.

The helper functions will simplify and bring focus on controller needs in the key event and motion event handling function discussed below. Note that error handling and input sanity checking is light to keep the methods focused.

Key Event

KeyEvents can be obtained quite easily in Android. The Activity class, which the FictitiousGame class has extended, has two methods that allow an application to intercept KeyEvents and MotionEvents. These methods, dispatchKeyEvent() and dispatchGenericMotionEvent(), need to be overridden to intercept those events. We will focus on the key events now and motion events in the next section.

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
     InputDeviceState state = getInputDeviceState(event);
     if (state == null) {
         return super.dispatchKeyEvent(event);
     }
     PlayerShip ship = getPlayerShip(state);

     switch (event.getAction()) {

         case KeyEvent.ACTION_DOWN:
             if (state.onKeyDown(event)) {
                 SetShipHeading(ship, event.getKeyCode(),KeyEvent.ACTION_DOWN);
                 ship.moveShip(event.getEventTime());
                 return true;
             }
             break;

         case KeyEvent.ACTION_UP:
             if (state.onKeyUp(event)) {
                 SetShipHeading(ship, event.getKeyCode(),KeyEvent.ACTION_UP);
                 ship.moveShip(event.getEventTime());
                 return true;
             }
             break;
     }

     return super.dispatchKeyEvent(event);
} 

The beginning of this method takes the input KeyEvent and tries to get the proper InputDeviceState by calling the previously defined getInputDeviceState() method. This should give either player A or player B’s controller state or null. If this event is not any player’s, null was returned, then the normal dispatchKeyEvent() method is called and method is exited.

Next we get the PlayerShip that matches the InputDeviceState by passing that state to getPlayerShip(). At this point, the state variable contains the controller state class and the ship variable contains the player ship for this KeyEvent.

Given a KeyEvent, one can determine if the key is down or up. This is called an “action.” The switch statement calls getAction() on the KeyEvent. The switch contains two cases for the returned value, ACTION_DOWN or ACTION_UP. They effectively mean that the key has been pressed or the key has been released.

Each case calls the InputDeviceState onKeyDown() or onKeyUp() method passing in the key event. These methods update the state of the controller and return a boolean indicating whether the key event was processed. If the event was not processed we exit the method calling the normal dispatchKeyEvent().

Assuming the KeyEvent was processed, then the SetShipHeading() method is called passing the PlayerShip, event key code, and ACTION_DOWN or ACTION_UP. This method, examined later, sets the player’s ship heading.

Now that the ship’s heading is set, the move method needs to be called. As was discussed before the PlayerShip contains a method called moveShip() which requires the time be passed in. The event’s time is passed to this method by calling getEventTime() on the event. moveShip() then can calculate the delta time from the previous time it was called and the current time that is passed to it. Now it can compute the distance down the ship’s current heading it should move.

Lastly, because we processed this KeyEvent, we return with true.

One of the helper methods used in dispatchKeyEvent(), once a game valid key code was found and the proper player’s ship, would set the ship’s heading. This method is below.

private void SetShipHeading(PlayerShip ship, int keyCode, int keyState) {
     if (keyState == KeyEvent.ACTION_DOWN) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_LEFT:
                 ship.setHeadingX(-1);
                 break;
             case KeyEvent.KEYCODE_DPAD_RIGHT:
                 ship.setHeadingX(1);
                 break;

             case KeyEvent.KEYCODE_DPAD_UP:
                 ship.setHeadingY(-1);
                 break;

             case KeyEvent.KEYCODE_DPAD_DOWN:
                 ship.setHeadingY(1);
                 break;

             default:
                 ship.setHeadingX(0);
                 ship.setHeadingY(0);
                 break;
             }
         } else {
             switch (keyCode) {
                 case KeyEvent.KEYCODE_DPAD_LEFT:
                 case KeyEvent.KEYCODE_DPAD_RIGHT:
                     ship.setHeadingX(0);
                     break;


                 case KeyEvent.KEYCODE_DPAD_UP:
                 case KeyEvent.KEYCODE_DPAD_DOWN:
                     ship.setHeadingY(0);
                     break;

                 default:
                     ship.setHeadingX(0);
                     ship.setHeadingY(0);
                     break;
             }
         }
     } 

SetShipHeading is split into two major parts, one for ACTION_DOWN and the other for ACTION_UP. This split happens right when the method is entered.

On either action, the left to right axis is considered the x-value and the up to down axis is considered the y-value. All other game valid keys that are received and are not a KEYCODE_DPAD_xxx we set the heading to (0, 0) to simulate an all-stop on the ship.

On an ACTION_DOWN, depending on the input key code, is how the heading is set. Conversely, on an ACTION_UP, is how a heading is cleared. Each time through this method only one axis is ever getting set or cleared (except for the default condition).

Motion Event

MotionEvents are obtained just as easily as KeyEvents. Here we override the dispatchGenericMotionEvent() which allows capturing of all motion events. These can range from a variety of devices such as, mice, pens, touch, trackball, and analog joysticks. This game only cares about the analog joystick.

@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
      if (((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
         || (event.getAction() != MotionEvent.ACTION_MOVE)) {
          return super.dispatchGenericMotionEvent(event);
     }

     InputDeviceState state = getInputDeviceState(event);
     if (state == null) {
          return super.dispatchGenericMotionEvent(event);
     }

      if (state.onJoystickMotion(event)) {
         PlayerShip ship = getPlayerShip(state);

          int historySize = event.getHistorySize();
          for (int i = 0; i < historySize; i++) {
             processJoystickInput(ship, state.getDevice(), event, i);
         }

         processJoystickInput(ship, state.getDevice(), event, -1);
          return true;
   }


      return super.dispatchGenericMotionEvent(event);
}

At the top of dispatchGenericMotionEvent() the input event is checked if it needs processing by the game. Fictitious Game only cares about motion events that are from a moving joystick. As has been done in previous methods, the event’s source is checked using getSource() and comparing the returned value with SOURCE_CLASS_JOYSTICK.

Since we know we have a joystick event, we also want to be sure it is a joystick move event. A call to getAction() from the event will return the kind of action for this event. There are many types of actions an event can be such as, ACTION_CANCEL (current gesture has been canceled) or ACTION_DOWN (a pressed gesture has started). The one we care about is ACTION_MOVE, essentially the joystick has moved. If the event is not a moving joystick event, the method is exited calling the normal dispatchGenericMotionEvent().

Similar to dispatchKeyEvent(), the proper InputDeviceState is needed. This is returned from getInputDeviceState() passing in the input event. This should give either player A or player B’s controller state or null. If this event is not any player’s, null was returned, the method is exited calling the normal dispatchGenericMotionEvent().

Having an event that is a moving joystick and its matching InputDeviceState, the device state needs to be updated. A call to the InputDeviceState’s onJoystickMotion() method passing it the event will do the update. This method updates the state of the joystick and returns a boolean indicating whether the motion event was processed. If the event was not processed we exit the method calling the normal dispatchGenericMotionEvent().

An event as this point is a valid joystick movement for the game. A call to getPlayerShip() will get the proper ship for this motion event.

As was briefly mentioned above, MotionEvents can have “historic” values. This means a motion event may batch together multiple movement samples within one event. These older samples are distinct from any other samples reported in previous events. To process all these samples in order, the historic samples must be first.

We will go through all the samples first by calling the event’s getHistroySize() and looping that many times. Each time calling the processJoystickInput() method passing it the ship, device, the event, and the historic position to process. The processJoystickInput() method ultimately calculates the ship’s heading and moves the ship. Method will be discussed below.

Once all the historical motion event samples have been processed, one final call is made to processJoystickInput() but this time passing -1 as the historic position. Indicating to that method, process the most current sample - the “non-historic” sample.

Since we have processed this MotionEvent, we return true.

The processJoystickInput() method is called for all game valid joystick inputs. Every joystick input is a MotionEvent. Each MotionEvent can have batched movement samples called “historic” values. This method will handles those as well.

private void processJoystickInput(PlayerShip ship, InputDevice device,
     MotionEvent event, int historyPos) {
     final int axisX = MotionEvent.AXIS_X;
     final int axisY = MotionEvent.AXIS_Y;

     float x = 0.0f;
     InputDevice.MotionRange range = device.getMotionRange(axisX,
         event.getSource());
     if (range != null) {
         float axisValue;
         if (historyPos >= 0) {
             axisValue = event.getHistoricalAxisValue(axisX, historyPos);
         } else {
             axisValue = event.getAxisValue(axisX);
         }
         x = InputDeviceState.ProcessAxis(range, axisValue);
     }

     float y = 0.0f;
     range = device.getMotionRange(axisY, event.getSource());
     if (range != null) {
         float axisValue;
         if (historyPos >= 0) {
             axisValue = event.getHistoricalAxisValue(axisY, historyPos);
         } else {
             axisValue = event.getAxisValue(axisY);
         }
         y = InputDeviceState.ProcessAxis(range, axisValue);
     }

     ship.setHeadingX(x);
     ship.setHeadingY(y);

     if (historyPos >= 0) {
         ship.moveShip(event.getHistoricalEventTime(historyPos));
     } else {
         ship.moveShip(event.getEventTime());
     }
} 

This method has four major parts: (1) find the x-value, (2) find the y-value, (3) set the ship heading, and (4) move the ship. At the start of the method are a couple of variables that are not one of the four parts and require a bit more discussion.

Finding the x and y values of this event rely on knowing what axis you want. At the start of the method axisX and axisY are hardcoded to AXIS_X and AXIS_Y respectively. While these are typical of the first analog joystick on a controller it may not always be the case (discussed in Appendix A). Also, a player may prefer another analog control on their device. In your game, it is highly recommended, that the player be able to assign the control they would like. This is best handled with a “Key Configuration” UI. One way would be to list all your game’s actions (left, right, up, down, jump, etc.) and have the ability to assign a controller movement or key press to them. A player would push a button assigned to a game’s action, like “Left;" your game would wait for input and the first KeyEvent or MotionEvent would be assigned to that action. For Fictitious Game, the AXIS_X and AXIS_Y will be used.

The next two sections of code are identical except one handles the x-axis and the other handles the y-axis of the joystick event. With both sections being identical, we will only describe one, the x-axis (just as a matter of choice). It is the first section.

Ultimately, in this section, getting the AXIS_X value is the goal. Initially we set a local variable, x, to zero. At the end of this section of this code, x, should hold the AXIS_X value.

Next the InputDevice.MotionRange is asked from the device. The motion range provides information about the range of values from a particular axis. To get the range we call getMotionRange() from the device passing it the axis and source wanted. In this case the AXIS_X and SOURCE_CLASS_JOYSTICK. The return range is checked for null as a device can, for a variety of reasons (turned off, etc.), be disconnected. If the range is null, the entire section of code is skipped.

Assuming a good InputDevice.MotionRange, the axis value can be requested. There are two methods to get the axis value, one for historical values and one for the current value. The input historyPos indicates if a historical value should be requested. historyPos will either be a particular historical point indicated by a non-negative value otherwise the current axis value is required. A call to getHistoricalAxisValue() on the event passing the axis and history position wanted will return the axis value at that historical point. To get the current axis value, a call to getAxisValue() on the event passing the axis wanted will return the current axis value.

Using this axis value from above, a call to the static method InputDeviceState.ProcessAxis() passing in the MotionRange and axis value will return a processed axis. This routine, described more thoroughly in the Controller State System sub-heading Axes, will take the axis value and process for dead zones. This is an excellent place to put other axis processing as your game requires.

The next section of code does identical processing as above but for the y-value. After these two sections of code, the x and y values for the MotionEvent have been calculated.

Taking those x and y values, the ship’s heading is set by calling setShipHeadingX() and setShipHeadingY() from PlayerShip.

At the bottom of the method, the ship is moved. To move the ship, we need to take into account historical points much like when calculating the axis value. Each historical axis value has a matching historical event time. The ship requires the event time to calculate movement. In the same way as getting the axis value, if the input historyPos is non-negative it will indicate a historical point to get the event time, otherwise the current time is required. The historical time is received by calling getHistoricalEventTime() from the event passing in the historyPos. The current time is received by calling getEventTime() from the event. Having the proper event time, we finally call ship.moveShip() passing in the event time and exit the method.

Important Controller Considerations

Important Controller Considerations

Controller Configuration Screen

As we know now, our main way of getting data from the controller is through KeyEvents and MotionEvents. From those events we get a number indicating a KEYCODE or position. This is great. except when the controllers are inconsistent. There can be so much inconsistency as something that would typically be a KeyEvent is a MotionEvent. Here is a mini-case study between two excellent controllers, the Bluetooth iControlPad and the Logitech Dual Action gamepad.

On the left is the iControlPad and the right the Logitech. Focus will be put on three major similarities as the picture above outlines. The table below shows what each controller reports for these three similarities.

Control iControlPad Reports Logitech Reports
Digital Pad Up KEYCODE_BUTTON_A AXIS_HAT_Y (-1.0)
Digital Pad Down KEYCODE_BUTTON_X AXIS_HAT_Y (1.0)
Digital Pad Left KEYCODE_BUTTON_C AXIS_HAT_X (-1.0)
Digital Pad Right KEYCODE_BUTTON_B AXIS_HAT_X (1.0)
Button Left KEYCODE_BUTTON_START KEYCODE_BUTTON_A
Button Right KEYCODE_BUTTON_THUMBL KEYCODE_BUTTON_Y
Button Bottom KEYCODE_BUTTON_MODE KEYCODE_BUTTON_X
Button Top KEYCODE_BUTTON_SELECT KEYCODE_BUTTON_B
L Analog Stick AXIS_X, AXIS_Y (-1.0 to 1.0) AXIS_X, AXIS_Y (-1.0 to 1.0)
R Analog Stick AXIS_Z, AXIS_RX (-1.0 to 1.0) AXIS_Z, AXIS_RZ (-1.0 to 1.0)

This inconsistency continues through the other buttons, such as the start, select, and shoulder button.

As there is no industry standard, we essentially are using the Android standard. Using their KEYCODE mapping and applying it against what the controller returns. Notice the Digital Pad controls (U, D, L, R) on the iControlPad they are KeyEvents and on the Logitech they are MotionEvents. In this above table, the only thing that matches is the Left Analog Stick.

So it can’t be stressed enough (at least for now) that a control configuration screen in your game is essential for a great controller experience. For instance, have a UI in which the user presses a button that is “Left” which brings up a prompt, “Press the key you want to be Left.” You application would then wait for the next KeyEvent or MotionEvent and assign that to the action “Left” in your game; thus giving the player flexibility and your game logic a way to deal with the inconsistency.

Another, less effective, alternative is that there is a scrollable list under the word “Left” in which the user picks what button they want it to be. The problem with this method is that the user may not know that AXIS_HAT_X is attached to their Left key as the table above shows. To get around this, you would need to provide an area that shows the current key pressed.

Note: In the package that you received with this document, you may have received a “Controller Table” spreadsheet. This spreadsheet contains different controllers and what each reports for button presses, analog stick movements, and the like. It is similar to the table above and should be useful in designing your Controller Screen solution.

Important Controller Considerations

Controller Profiles

The section above, proposes the use of a query-style controller configuration screen in your application. An example “Configuration Screen” would contain all the game functions like “accelerate,” “brake,” “steer left,” and “steer right.” Then the user would press “accelerate” which brings up a dialog stating something like “Press the button or move the direction you want to be accelerate.” This would continue through all the game functions.

The example above will give your application the ability to handle the greatest amount of controllers now and in the future. As an instance of this, we use a racing game that implemented a query-style system, hooked up a guitar controller from another popular game, and were able to assign the guitar functions to game functions; thus showing the flexibility of the query-system.

While the query system is the most flexible, there is a way to enhance and streamline the user experience in many cases. Instead, an application would still have the query-style system as above but would include the ability to identify what controller was being used and pre-populate the “Configuration Screen” with recommend keys.

It would be difficult, if not impossible to know all controllers and therefore how to populate your controller configuration. However, if you target the major brands, the most popular controllers would be covered.

Ideally, checking for popular controllers would be done at any time while the application running. This would allow a user to plug in his (popular) controller at any time and never even have to visit the controller query screen. The hybrid system of, a query-style screen and checking for popular controllers is the best of both worlds – “plug in and it works,” if not, use the query screen to handle the rest.

Let’s take an instance of handling a Logitech controller as described above in the above section, Controller Configuration Screen. Further, let’s say we want the game’s “accelerate” to be the “Button Left” as described in the table from Appendix A. Notice that the input for “accelerate” would be “KEYCODE_BUTTON_A” for the Logitech.

An over-simple pseudo-code example for this handling this would be:

  1. Identify the controller.
  2. Check if it’s a popular controller.
    1. If it is, load and use the popular controller’s profile.
    2. Otherwise, use the custom controller profile (the one from the query-style screen).

These steps will be described further below.

Identify the controller: To identify any new controllers, there are typically only two ways. The first is to wait for an input event from the controller. Controller events are usually, either KeyEvents or MotionEvents, both events are also an InputEvent. Having an InputEvent allows getting a unique identifier is easy.

public int GetDeviceHash(InputEvent event) {
     final InputDevice device = event.getDevice();
     if (device == null) {
         return 0;
     }
     String deviceData = device.getName();
     return deviceData.hashCode();
} 

The returned hash can then be used to call into a table of controller profiles. Of course, the String’s hash function does not have to be used if you have an internal mechanism.

Note: Some devices may not return fully consistent device names. The only known one, right now, is the iControlPad. It returns, “iControlPad-nnnn” where nnnn is the last four digits of the controllers Bluetooth MAC address.

The second way to identify controllers, which we will go over quickly here, is to poll for all attached devices. This can be done by calling the static function InputDevice.getDeviceIds() which returns an int[]. Crawling through the int array, get the InputDevice, then the sources it supports, and finally check if the supported sources are ones you care about like SOURCE_GAMEPAD, SOURCE_KEYBOARD, etc.

Check if it’s a popular controller: Using the unique hash, the popular controller profile table can be checked to see if this controller exists in it. Here is a sample table for the Logitech.

Key/Hash Accelerate Brake Steer Left Steer Right
0x33D1CD00 KEYCODE_BUTTON_A KEYCODE_BUTTON_X -AXIS_HAT_X +AXIS_HAT_X

If the controller exists in the table, the profile can be easily used as above. If it does not exist, then the profile from the query-style controller screen should be used. It would be a good idea to put up a dialog stating that the player should visit the controller configuration screen to setup their controller properly.

While this example is a bit basic, it does show all that is needed to implement “profiles.” Using this hybrid style (profiles with query) allows the most seamless user experience.

Note: In the package you received with this document, you may have received a sample application that takes this hybrid theory and shows you an implementation of it.

Summary

Summary

We followed the initial key press/joystick movement all the way to useful (x, y) values. As you can see, it doesn’t take much to open the world of controllers to your game. It can really transform your game into a console-like experience.

Consider compiling the GameControllerInput sample (called ApiDemo) and breakpoint your way though it to see all that is going on. Also, consider sharing your experience with others on the internet and at conferences.

We invite you to check out all that NVIDIA Tegra has to offer and to see the resources we’ve pulled together here:

http://developer.nvidia.com/category/zone/mobile-development

If you’d like to compile the sample, it should be in the SDK as below. If not, check if the samples need to be updated or re-downloaded using the “SDK Manager”.

ANDROID_SDK_PATH\samples\android-12\ApiDemos

Appendix A: Runtime Backward Compatability

Appendix A: Runtime Backward Compatability

Honeycomb 3.1 (API 12) added some exciting options for controller use. However, the goal of this document is to show alternate controls available to games, not to push exclusive use of 3.1.

Since we want to have the ability to use the latest that 3.1 has to offer, but still be able to run on a breadth of Android devices, we need some forethought before designing our control system. Here we consider a layered approach. Essentially, the first layer only has the most common control, and the last layer the most modern controls available.

Here is the first layer. This is always the default or touch layer. The first layer is always there and should always work.

Here is the second layer. This layer adds the keyboard. Since API 1 there has been keyboard functionality. Of course, this was, and is usually used with the soft keyboard. All current hardware keyboards generally use the same methods. A gamepad, for the more adventurous, can even be used here with other software.

Here is the third and last layer. The functionality jumps all the way to API 12. Android Honeycomb 3.1 allows for both USB Controllers and Bluetooth HID Controllers. Unfortunately, as of this writing, Bluetooth HID Controller are difficult to find. Luckily though, the code path for both is the same. Android takes care of the connections for you.

So for this layered approach to work with devices from API 12 to API 1, a game would need to compile under API 12 even though its manifest claims an earlier API. This is perfectly valid as long as care is taken in calling only API’s that are available, given the OS. There is some excellent documentation from Google discussing this:

http://developer.android.com/resources/articles/backward-compatibility.html

For instance, a game running on pre-3.1 would not want to call anything but keyboard API’s, the first and second layer. A check during initialization for what API level the OS is would be helpful. Then appropriate action could be taken when the game’s input system is run. Here is where you would fallback layers to the one that the OS can handle. The documentation cited above can help here.

It goes without saying that layers can be omitted simply because they do not work for your game play.

Appendix B: Demoing with a GamePad Pre-3.1

Appendix B: Demoing with a GamePad Pre-3.1

So you need to demo your application with a gamepad on a pre-3.1 device or maybe you want to offer this ability in your game (recommended!). While you don’t have an option natively on Android, there are applications that can help. These applications essentially take a Bluetooth controller and translate the inputs as keyboard events. If your application supports the keyboard, it’s likely that your game will just work. We will focus on using the most popular program, BluezIME. There are some other programs out there but they all work essentially the same.

What you’ll need . . .

Prepare the software . . .

  • Install the application and BluezIME apk using your favorite method

The first time you play you’ll need to . . .

  • Know your controller's “connect method.”
WiiMote Open battery cover and notice where the red button is located.
iControlPad Notice where the Start button is; you'll need to hold the Start button until the green light flashes when the time comes.
Zeemote Notice where the red Power button is; you'll need to hold the Power button until the light flashes with blue and orange when the time comes.
Others Find where the "connect" button/method is, using the controller's documentation.
  • This is an extra step ONLY if using the Zeemote, pair it to your device first.
    • Settings > Wireless & networks > turn Bluetooth on.
    • Settings > Wireless & networks > Bluetooth settings > Find nearby devices.
    • Click on Zeemote JS1.
    • Enter 0000 as the PIN.
    • Click OK.
  • On your device, turn this on:
    Settings > Language & input > Configure input methods > Bluez IME
  • Run the Bluez IME Settings app in the App Drawer, within the app.
    • Make sure Bluetooth is on
    • Press Select device driver > choose your supported controller.
    • Press Select device > Scan …
    • Run the appropriate “connect method” on your controller (see above).
    • Click on the entry with your controller’s manufacturer in it, similar to:
WiiMote Nintendo RVL-CNT-01
iControlPad iControlPad-0DD4
Zeemote Zeemote JS1
Others See your documentation.
  • Quickly, if you can, press Select IME > Bluez IME.
  • You should now be connected, which is indicated on the controller by:
WiiMote First blue light in the bank of 4 is lit.
iControlPad Green light goes out (not a great indicator) .
Zeemote Blue light slowly blinks.
Others See your documentation.
  • If you are not connected:
    • Re-press your connect button.
    • Select IME > select anything except Bluez IME.
    • Select IME > reselect Bluez IME to restart the connection.
  • Exit the Bluez program and launch your application.
    While in the application:
    • Go to its keyboard setup screen, set your keys up, and enjoy.
    • If there is no setup screen, and results aren’t as expected, try this:
    • Launch BluezIME.
    • Go to the Configure key mapping.
    • Here you can configure what key is sent to the application, given the button pressed on the controller.
  • When you are done playing, launch Bluez IME Settings in the app :
    • Select IME > English (US) Keyboard (Android keyboard).
    • Exit. This completes the demonstration.

Every other time you play . . .

  • Run the Bluez IME Settings app in the App Drawer, within the app:
    • Make sure Bluetooth is on.
    • Press the connect button on your controller (button from the “Prepare your controller” step above).
    • Press Select IME > Bluez IME.
    • You should now be connected.
  • Launch the application and enjoy.
  • When you are done playing, launch Bluez IME Settings in the app:
    • Select IME > English (US) Keyboard (Android keyboard)
    • Exit. This completes the demonstration.