Home > Getting Started > Devices > Amazon Fire TV

GameController API

Introduction

Amazon Fire TV devices support up to seven Bluetooth controllers to be connected to the system at the same time, which enables you to develop multi-player apps and games. You can always use standard Android mechanisms for identifying controllers and managing input events. These mechanisms include the InputDevice class and standard Android event handlers (onKeyUp(), onKeyDown() or onGenericMotionEvent()). See Amazon Fire Game Controller Input for information on the game controller event constants for Android, and Controller Behavior Guidelines for suggestions on managing different buttons.

The GameController API, part of the Amazon Fire TV SDK, provides input management features for game controllers. It is especially useful for game development, including the ability to identify player numbers, and for handling controller input on a frame-based basis (inside a game loop). In addition, the Amazon Fire TV SDK includes a sample app called SampleGame that demonstrates the use of the GameController API.

Getting Started

The GameController class is included with the Amazon Fire TV SDK. GameController provides features especially useful for games, including:

  • Methods to associate game controllers with the player numbers as defined by the Amazon Fire TV platform.
  • Methods to query controller state at any time.
  • Input event constants specific to game controllers.
  • Behavior to enable you to process game controller input events on a per-frame basis (that is, within a game loop).

To use any of the features in the GameController class, import that class and its associated exceptions into your app or game:

import com.amazon.device.gamecontroller.GameController;
import com.amazon.device.gamecontroller.GameController.DeviceNotFoundException;
import com.amazon.device.gamecontroller.GameController.PlayerNumberNotFoundException;
import com.amazon.device.gamecontroller.GameController.NotInitializedException;

You must also initialize the GameController class in your activity's onCreate() method before you use any of the methods in the GameController API. If you try to use any of the GameController features before calling GameController.init(), the game controller API throws a NotInitializedException exception.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //...
    //Initialize GameController with the context to attach to the game controller service
    GameController.init(this);
}

Players

Although you can connect up to seven Bluetooth game controllers to an Amazon Fire TV device, only four of those controllers are assigned to player numbers. Note that controllers are assigned to player number slots and do not re-shuffle when an individual controller is disconnected, that is, player numbers are not consecutive.

The GameController API includes methods to access the player numbers for available game controllers. If your game supports more than four players you can always manage controllers and player numbers inside your game on your own as well.

Note: All of the methods in this section throw a NotInitializedException exception if you have not called GameController.init() before using them.

Getting Player Numbers by Device ID

Use the GameController.getPlayerNumber() static method with an Android device ID to get the player number for that device. You can get an Android device ID with the InputDevice.getDeviceIDs() method, from any key or motion event (InputEvent.getDeviceId()), or from a player number with GameController.getDeviceID().

Player numbers are defined system-wide. Not all device IDs may be associated with a player number. If this is the case getPlayerNumber() throws the PlayerNumberNotFoundException exception.

int[] ids = InputDevice.getDeviceIds();

for (int i=0; i < ids.length; i++) {
    try {
       int playerNum = GameController.getPlayerNumber(ids[i]);
    }   catch (PlayerNumberNotFoundException e) {
        // ...
    }
    // ...
}

Getting Device IDs by Player Number

The GameController.getDeviceID() method is the reverse of getPlayerNumber(); given a player number (from 1 to 4), it returns the Android device ID. Not all player numbers may be associated with device IDs. If this is the case GameController.getDeviceID() throws the DeviceNotFoundException exception.

for (int n = 0; n < GameController.MAX_PLAYERS; n++) {
    try {
        int devicenum = GameController.getDeviceID(n + 1);
    }
    catch (DeviceNotFoundException e) {
        // ...
    }
    // ...
}

Getting the Number of Players

Use the GameController.getPlayerCount() static method to get the number of available players. Note that player numbers are not necessarily consecutive, for example, two players may be assigned to slots 2 and 4.

int players = GameController.getPlayerCount();

Getting GameController Objects by Player

Use the GameController.getControllerByPlayer() static method with a player number to retrieve a GameController object for use with frame-based event input (see Frame-Based Input Events). Player numbers are assigned by the system and can be from 1 to 4. Not all player numbers may have associated controllers. If this is the case GameController.getControllerbyPlayer() throws the PlayerNumberNotFoundException exception.

for (int n = 0; n < GameController.MAX_PLAYERS; n++) {
    GameController gameController = null;
    try {
        gameController =
                GameController.getControllerByPlayer(n + 1);
    }
    catch (PlayerNumberNotFoundException e) {
        // ...
    }
}

Frame-Based Input Events

You can always handle input events from controller using the standard Android event-driven programming model, in which your code handles input events as they arrive. The GameController class enables you to use a frame-based input model, in which events are cached and you can query the state of the controller before rendering each frame. With this model, you use the following steps:

  1. Provide your own game loop to replace the standard Android event model.
  2. Forward standard input events to GameController.
  3. For each frame within the loop:
    • Query the state of each controller for player input.
    • Render the frame using that input data.

The SampleGame sample app, included with the Fire TV SDK, provides an example implementation of a simple game loop and demonstrates how to use GameController for frame-based input.

Note: All of the methods in this section throw a NotInitializedException exception if you have not called GameController.init() before using them.

Forwarding Events to GameController

To use the GameController API to manage your input events, forward key and motion events to the GameController class to handle. Override the onKeyUp(), onKeyDown() and onGenericMotionEvent() methods to pass those events to GameController:

//Forward key down events to GameController so it can manage state
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    boolean handled = false;
    try {
        handled = GameController.onKeyDown(keyCode, event);
    }
    catch (DeviceNotFoundException e) {
    }
    return handled || super.onKeyDown(keyCode, event);
}
//Forward key up events to GameController so it can manage state
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    boolean handled = false;
    try {
        handled = GameController.onKeyUp(keyCode, event);
    }
    catch (DeviceNotFoundException e) {
    }
    return handled || super.onKeyUp(keyCode, event);
}
//Forward motion events to GameController so it can manage state
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
    boolean handled = false;
    try {
        handled = GameController.onGenericMotionEvent(event);
    }
    catch (DeviceNotFoundException e) {
    }
    return handled || super.onGenericMotionEvent(event);
}

Implementing the Game Loop

Implement the game loop on your own thread. Inside the run() method for that thread, test for input and render each frame based on that input. Don't forget to stop and start the thread in your activity's onPause() and onResume() methods.

Within the loop, use GameController.startFrame() to reset the input event queue between frames. Here's a simple example:

while (running){
    GameController.startFrame();

    //Draw the background

    //Draw each player
    for (int n = 0; n<GameController.MAX_PLAYERS; n++){
        GameController gameController = null;
        try {
            gameController = GameController.getControllerByPlayer(n + 1);
        }
        catch (PlayerNumberNotFoundException e) {
        }

        if (gameController != null){
            //Move player n on the board
            float x = gameController.getAxisValue(GameController.AXIS_STICK_LEFT_X);
            float y = gameController.getAxisValue(GameController.AXIS_STICK_LEFT_Y);

            if (gameController.getKeyValue(GameController.BUTTON_TRIGGER_LEFT){
                //Draw laser beam for player n
            }
        }
    }

    //Draw the foreground
}

Querying Controller State

Use these utility methods from GameController to query the current input state of the controller, including axis values and button state. All of these methods are invoked on GameController objects, which you can retrieve with a player number with the GameController.getControllerByPlayer() method. In addition, all these methods take a GameController event constant as an argument. These constants are listed in GameController Event Constants.

Analog Motion

Use wasAxisChanged() to test whether an analog stick movement has occurred since the last frame:

if (gameController.wasAxisChanged(GameController.AXIS_HAT_X) || gameController.wasAxisChanged(GameController.AXIS_HAT_Y)) {
    // ... Dpad navigation
}

Use getAxisValue() to get the actual change in value from an analog stick since the last frame. This example also shows how to use the GameController.DEAD_ZONE constant, which lets you normalize analog stick movement for the actual center for the stick (the "dead zone," where the stick is in the center position but may not be reporting true center axis values).

float deltaX = gameController.getAxisValue(GameController.AXIS_STICK_LEFT_X);
float deltaY = gameController.getAxisValue(GameController.AXIS_STICK_LEFT_Y);
if ((deltaX * deltaX + deltaY * deltaY) > GameController.DEAD_ZONE * GameController.DEAD_ZONE){
    //stick angle is greater than the center dead zone
    x[n] += Math.round(deltaX * 10);
    y[n] += Math.round(deltaY * 10);
}

Button Presses

Use the wasButtonPressed() or wasButtonReleased() methods to test whether button input has occurred since the last frame:

if (gameController.wasButtonPressed(GameController.BUTTON_A)
    || gameController.wasButtonPressed(GameController.BUTTON_DPAD_CENTER)){
    // draw a circle at the current player's x,y position
}

Use isButtonPressed() to test the current state of any button:

//Dpad button presses, move the player's position:
x[n] += gameController.isButtonPressed(GameController.BUTTON_DPAD_RIGHT) ? +5 : 0;
x[n] += gameController.isButtonPressed(GameController.BUTTON_DPAD_LEFT) ? -5 : 0;
y[n] += gameController.isButtonPressed(GameController.BUTTON_DPAD_DOWN) ? +5 : 0;
y[n] += gameController.isButtonPressed(GameController.BUTTON_DPAD_UP) ? -5 : 0;

GameController Event Constants

The GameController class includes a set of static event constants that represent button and axis event types. These constants are identical to the standard constants from the KeyEvent and MotionEvent classes, but are the same for any controller, and are often more logically named. For example, GameController.BUTTON_A is identical to KeyEvent.KEYCODE_BUTTON_A . Use these constants with the frame-based controller model described in Frame-Based Input Events.

Note: For standard controller buttons (Back, Media Play/FF/Rewind), manage those events with standard Android KeyEvent and MotionEvent events. The Home and GameCircle buttons are system events and cannot be captured by your app.

Game Controller Control Analog Movement: wasAxisChanged() getAxisValue()
Button Presses:
wasButtonPressed()
wasButtonReleased()
isButtonPressed()
Default Behavior (if the event is not handled by your code)
D-Pad
(Left/Right)
AXIS_HAT_X
(>0 is right)
none Move the focus in the user interface in the given direction.
D-Pad
(Up/Down)
AXIS_HAT_Y
(>0 is down)
none Move the focus in the user interface in the given direction.
Left Stick
(Left/Right)
AXIS_STICK_LEFT_X
(>0 is right)
none Move the focus in the user interface in the given direction.
Left Stick
(Up/Down)
AXIS_STICK_LEFT_Y
(>0 is down)
none Move the focus in the user interface in the given direction.
Left Stick Press none BUTTON_STICK_LEFT Do nothing.
Right Stick
(Left/Right)
AXIS_STICK_RIGHT_X
(>0 is right)
none Do nothing.
Right Stick
(Up/Down)
AXIS_STICK_RIGHT_Y
(>0 is down)
none Do nothing.
Right Stick Press none BUTTON_STICK_RIGHT Do nothing.
A none BUTTON_A Select the item with the current focus.
B none BUTTON_B Go back to the previous screen (Activity) (Same as Back).
X none BUTTON_X Do nothing.
Y none BUTTON_Y Do nothing.
Left Trigger (L2) AXIS_TRIGGER_LEFT none Select the item with the current focus.
Left Shoulder (L1) none BUTTON_SHOULDER_LEFT Do nothing.
Right Trigger (R2) AXIS_TRIGGER_RIGHT none Select the item with the current focus.
Right Shoulder (R1) none BUTTON_SHOULDER_RIGHT Do nothing.