Gerenciar ações do controle

No nível do sistema, o Android informa os códigos de evento de entrada dos controles de jogos como códigos de tecla e valores de eixo do Android. No seu jogo, você pode receber esses códigos e valores e os converter em ações específicas.

Quando os jogadores se conectam fisicamente ou conectam um controle de jogos a dispositivos Android, o sistema detecta automaticamente o controle como um dispositivo de entrada e começa a relatar eventos desse tipo. Seu jogo pode receber esses eventos de entrada implementando os seguintes métodos de callback na Activity ativa ou na View em foco. Implemente os callbacks para Activity ou View, mas não para ambos:

A abordagem recomendada é capturar os eventos do objeto View específico com que o usuário interage. Inspecione os seguintes objetos fornecidos pelos callbacks para saber informações sobre o tipo de evento de entrada recebido:

KeyEvent
Um objeto que descreve eventos do botão direcional e do gamepad. Os eventos de tecla são acompanhados por um código de tecla que indica o botão específico acionado, como DPAD_DOWN ou BUTTON_A. Você pode conferir o código da chave chamando getKeyCode() ou usando callbacks de eventos de tecla, como onKeyDown().
MotionEvent
Um objeto que descreve entradas a partir do joystick e de movimentos de gatilhos superiores. Os eventos de movimento são acompanhados por um código de ação e um conjunto de valores de eixo. O código de ação especifica a mudança de estado ocorrida, como o movimento de um joystick. Os valores do eixo descrevem a posição e outras propriedades de movimento para um controle físico específico, por exemplo, AXIS_X ou AXIS_RTRIGGER. Para conseguir o código de ação, chame getAction() e o valor do eixo chamando getAxisValue().

Esta lição se concentra na forma como você gerencia as entradas a partir dos tipos mais comuns de controle físico (botões do gamepad, botões direcionais e joysticks) na tela de um jogo implementando os métodos de callback View mencionados acima e os objetos de processamento KeyEvent e MotionEvent.

Verificar se um controle de jogo está conectado

Ao relatar eventos de entrada, o Android não distingue entre eventos provenientes de um controle que não é de jogo e aqueles provenientes de um controle de jogo. Por exemplo, uma ação de tela touchscreen gera um evento AXIS_X que representa a coordenada X da superfície sensível ao toque, mas um joystick gera um evento AXIS_X que representa a posição X do joystick. Se o jogo se preocupa em processar a entrada do controlador, primeiro confira se o evento de entrada vem de um tipo de origem relevante.

Para verificar se um dispositivo de entrada conectado é um controle de jogo, chame getSources() para ter um campo de bits combinados de tipos de fonte de entrada com suporte a esse dispositivo. Em seguida, você pode testar se os seguintes campos estão definidos:

  • Um tipo de origem de SOURCE_GAMEPAD indica que o dispositivo de entrada tem botões de gamepad (por exemplo, BUTTON_A). Esse tipo de origem não indica estritamente se o controle de jogo tem botões D-pad, embora a maioria dos gamepads normalmente tenha controles direcionais.
  • Um tipo de origem SOURCE_DPAD indica que o dispositivo de entrada tem botões D-pad (por exemplo, DPAD_UP).
  • Um tipo de origem de SOURCE_JOYSTICK indica que o dispositivo de entrada tem controles direcionais analógicos, por exemplo, um joystick que registra movimentos ao longo de AXIS_X e AXIS_Y.

O snippet de código a seguir mostra um método auxiliar que permite verificar se os dispositivos de entrada conectados são controles de jogos. Em caso afirmativo, o método recupera os IDs de dispositivo dos controles de jogos. Em seguida, você pode associar cada ID de dispositivo a um jogador no jogo e processar as ações do jogo separadamente para cada jogador conectado. Para saber mais sobre compatibilidade com vários controles de jogos conectados simultaneamente no mesmo dispositivo Android, consulte Oferecer suporte a vários controles de jogos.

Kotlin

fun getGameControllerIds(): List<Int> {
    val gameControllerDeviceIds = mutableListOf<Int>()
    val deviceIds = InputDevice.getDeviceIds()
    deviceIds.forEach { deviceId ->
        InputDevice.getDevice(deviceId).apply {

            // Verify that the device has gamepad buttons, control sticks, or both.
            if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
                    || sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK) {
                // This device is a game controller. Store its device ID.
                gameControllerDeviceIds
                        .takeIf { !it.contains(deviceId) }
                        ?.add(deviceId)
            }
        }
    }
    return gameControllerDeviceIds
}

Java

public ArrayList<Integer> getGameControllerIds() {
    ArrayList<Integer> gameControllerDeviceIds = new ArrayList<Integer>();
    int[] deviceIds = InputDevice.getDeviceIds();
    for (int deviceId : deviceIds) {
        InputDevice dev = InputDevice.getDevice(deviceId);
        int sources = dev.getSources();

        // Verify that the device has gamepad buttons, control sticks, or both.
        if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
                || ((sources & InputDevice.SOURCE_JOYSTICK)
                == InputDevice.SOURCE_JOYSTICK)) {
            // This device is a game controller. Store its device ID.
            if (!gameControllerDeviceIds.contains(deviceId)) {
                gameControllerDeviceIds.add(deviceId);
            }
        }
    }
    return gameControllerDeviceIds;
}

Além disso, convém verificar os recursos de entrada individuais compatíveis com um controle de jogo conectado. Isso pode ser útil, por exemplo, se você quer que seu jogo use somente entradas do conjunto de controles físicos que ele entende.

Para detectar se um código de tecla ou código de eixo específico é compatível com um controle de jogo conectado, use estas técnicas:

  • No Android 4.4 (nível 19 da API) ou versões mais recentes, é possível determinar se um código de tecla é compatível com um controle de jogo conectado chamando hasKeys(int...).
  • No Android 3.1 (nível 12 da API) ou versões mais recentes, é possível encontrar todos os eixos disponíveis com suporte em um controle de jogo conectado chamando getMotionRanges() primeiro. Em seguida, em cada objeto InputDevice.MotionRange retornado, chame getAxis() para ter o ID do eixo.

Processar o pressionamento do botão do gamepad

A Figura 1 mostra como o Android mapeia códigos de teclas e valores de eixos para os controles físicos na maioria dos controles de jogos.

Figura 1. Perfil de um controle de jogo genérico.

As frases de destaque na figura referem-se ao seguinte:

Códigos de tecla comuns gerados por pressionamentos de botões do gamepad incluem BUTTON_A, BUTTON_B, BUTTON_SELECT e BUTTON_START. Alguns controles de jogos também acionam o código de tecla DPAD_CENTER quando o centro da barra transversal dos botões direcionais é pressionado. Seu jogo pode inspecionar o código de tecla chamando getKeyCode() ou a partir de callbacks de eventos de tecla, por exemplo, onKeyDown(), e, se ele representar um evento que seja relevante para seu jogo, processar como uma ação do jogo. A Tabela 1 lista as ações de jogo recomendadas para os botões do gamepad mais comuns.

Tabela 1. Ações de jogo recomendadas para os botões do gamepad.

Ação do jogo Código da tecla do botão
Iniciar jogo no menu principal ou pausar/retomar durante o jogo BUTTON_START*
Menu de exibição BUTTON_SELECT* e KEYCODE_MENU*
O mesmo que o comportamento de navegação Voltar do Android descrito no guia de design de Navegação. KEYCODE_BACK
Voltar para um item anterior em um menu BUTTON_B
Confirmar a seleção ou executar a ação principal do jogo BUTTON_A e DPAD_CENTER

* Seu jogo não pode depender da presença dos botões "Iniciar", "Selecionar" ou "Menu".

Dica: ofereça uma tela de configuração no jogo para permitir que os usuários personalizem os próprios mapeamentos de controle de jogo para ações.

O snippet a seguir mostra como você pode substituir onKeyDown() para associar o pressionamento de BUTTON_A e DPAD_CENTER a uma ação de jogo.

Kotlin

class GameView(...) : View(...) {
    ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        var handled = false
        if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
            if (event.repeatCount == 0) {
                when (keyCode) {
                    // Handle gamepad and D-pad button presses to navigate the ship
                    ...

                    else -> {
                        keyCode.takeIf { isFireKey(it) }?.run {
                            // Update the ship object to fire lasers
                            ...
                            handled = true
                        }
                    }
                }
            }
            if (handled) {
                return true
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    // Here we treat Button_A and DPAD_CENTER as the primary action
    // keys for the game.
    private fun isFireKey(keyCode: Int): Boolean =
            keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_BUTTON_A
}

Java

public class GameView extends View {
    ...

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    // Handle gamepad and D-pad button presses to
                    // navigate the ship
                    ...

                    default:
                         if (isFireKey(keyCode)) {
                             // Update the ship object to fire lasers
                             ...
                             handled = true;
                         }
                     break;
                }
            }
            if (handled) {
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private static boolean isFireKey(int keyCode) {
        // Here we treat Button_A and DPAD_CENTER as the primary action
        // keys for the game.
        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                || keyCode == KeyEvent.KEYCODE_BUTTON_A;
    }
}

Observação: no Android 4.2 (nível 17 da API) e versões anteriores, o sistema trata BUTTON_A como a tecla Voltar do Android por padrão. Se o app for compatível com essas versões do Android, lembre-se de tratar BUTTON_A como a principal ação do jogo. Para determinar a versão atual do SDK do Android no dispositivo, consulte o valor Build.VERSION.SDK_INT.

Processar entradas pelo botão direcional

O botão direcional de quatro direções é um controle físico comum em muitos controles de jogos. O Android informa o pressionamento do botão direcional para cima e para baixo como eventos AXIS_HAT_Y com um intervalo de -1,0 (para cima) a 1,0 (para baixo), e o pressionamento do botão direcional para a ESQUERDA ou DIREITA como eventos AXIS_HAT_X com um intervalo de -1,0 (esquerda) a 1,0 (direita).

Alguns controles informam o pressionamento do botão direcional com um código de tecla. Se o jogo usar os pressionamentos do botão direcional, trate os eventos do botão do ângulo de visão e os códigos do botão direcional como os mesmos eventos de entrada, conforme recomendado na tabela 2.

Tabela 2. Ações do jogo padrão recomendadas para códigos de botão direcional e valores do eixo do chapéu.

Ação do jogo Código do botão direcional Código do botão do ângulo de visão
Mover para cima KEYCODE_DPAD_UP AXIS_HAT_Y (para valores de 0 a -1,0)
Mover para baixo KEYCODE_DPAD_DOWN AXIS_HAT_Y (para valores de 0 a 1,0)
Mover para a esquerda KEYCODE_DPAD_LEFT AXIS_HAT_X (para valores de 0 a -1,0)
Mover para a direita KEYCODE_DPAD_RIGHT AXIS_HAT_X (para valores de 0 a 1,0)

O snippet de código a seguir mostra uma classe auxiliar que permite verificar os valores do botão do ângulo de visão e do código de tecla de um evento de entrada para determinar a direção do botão direcional.

Kotlin

class Dpad {

    private var directionPressed = -1 // initialized to -1

    fun getDirectionPressed(event: InputEvent): Int {
        if (!isDpadDevice(event)) {
            return -1
        }

        // If the input event is a MotionEvent, check its hat axis values.
        (event as? MotionEvent)?.apply {

            // Use the hat axis value to find the D-pad direction
            val xaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_X)
            val yaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_Y)

            directionPressed = when {
                // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
                // LEFT and RIGHT direction accordingly.
                xaxis.compareTo(-1.0f) == 0 -> Dpad.LEFT
                xaxis.compareTo(1.0f) == 0 -> Dpad.RIGHT
                // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
                // UP and DOWN direction accordingly.
                yaxis.compareTo(-1.0f) == 0 -> Dpad.UP
                yaxis.compareTo(1.0f) == 0 -> Dpad.DOWN
                else -> directionPressed
            }
        }
        // If the input event is a KeyEvent, check its key code.
        (event as? KeyEvent)?.apply {

            // Use the key code to find the D-pad direction.
            directionPressed = when(event.keyCode) {
                KeyEvent.KEYCODE_DPAD_LEFT -> Dpad.LEFT
                KeyEvent.KEYCODE_DPAD_RIGHT -> Dpad.RIGHT
                KeyEvent.KEYCODE_DPAD_UP -> Dpad.UP
                KeyEvent.KEYCODE_DPAD_DOWN -> Dpad.DOWN
                KeyEvent.KEYCODE_DPAD_CENTER ->  Dpad.CENTER
                else -> directionPressed
            }
        }
        return directionPressed
    }

    companion object {
        internal const val UP = 0
        internal const val LEFT = 1
        internal const val RIGHT = 2
        internal const val DOWN = 3
        internal const val CENTER = 4

        fun isDpadDevice(event: InputEvent): Boolean =
            // Check that input comes from a device with directional pads.
            event.source and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD
    }
}

Java

public class Dpad {
    final static int UP       = 0;
    final static int LEFT     = 1;
    final static int RIGHT    = 2;
    final static int DOWN     = 3;
    final static int CENTER   = 4;

    int directionPressed = -1; // initialized to -1

    public int getDirectionPressed(InputEvent event) {
        if (!isDpadDevice(event)) {
           return -1;
        }

        // If the input event is a MotionEvent, check its hat axis values.
        if (event instanceof MotionEvent) {

            // Use the hat axis value to find the D-pad direction
            MotionEvent motionEvent = (MotionEvent) event;
            float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
            float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);

            // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
            // LEFT and RIGHT direction accordingly.
            if (Float.compare(xaxis, -1.0f) == 0) {
                directionPressed =  Dpad.LEFT;
            } else if (Float.compare(xaxis, 1.0f) == 0) {
                directionPressed =  Dpad.RIGHT;
            }
            // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
            // UP and DOWN direction accordingly.
            else if (Float.compare(yaxis, -1.0f) == 0) {
                directionPressed =  Dpad.UP;
            } else if (Float.compare(yaxis, 1.0f) == 0) {
                directionPressed =  Dpad.DOWN;
            }
        }

        // If the input event is a KeyEvent, check its key code.
        else if (event instanceof KeyEvent) {

           // Use the key code to find the D-pad direction.
            KeyEvent keyEvent = (KeyEvent) event;
            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                directionPressed = Dpad.LEFT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                directionPressed = Dpad.RIGHT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                directionPressed = Dpad.UP;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                directionPressed = Dpad.DOWN;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
                directionPressed = Dpad.CENTER;
            }
        }
        return directionPressed;
    }

    public static boolean isDpadDevice(InputEvent event) {
        // Check that input comes from a device with directional pads.
        if ((event.getSource() & InputDevice.SOURCE_DPAD)
             != InputDevice.SOURCE_DPAD) {
             return true;
         } else {
             return false;
         }
     }
}

Você pode usar essa classe auxiliar no jogo sempre que quiser processar a entrada do botão direcional (por exemplo, nos callbacks onGenericMotionEvent() ou onKeyDown()).

Exemplo:

Kotlin

private val dpad = Dpad()
...
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
    if (Dpad.isDpadDevice(event)) {
        when (dpad.getDirectionPressed(event)) {
            Dpad.LEFT -> {
                // Do something for LEFT direction press
                ...
                return true
            }
            Dpad.RIGHT -> {
                // Do something for RIGHT direction press
                ...
                return true
            }
            Dpad.UP -> {
                // Do something for UP direction press
                ...
                return true
            }
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

Java

Dpad dpad = new Dpad();
...
@Override
public boolean onGenericMotionEvent(MotionEvent event) {

    // Check if this event if from a D-pad and process accordingly.
    if (Dpad.isDpadDevice(event)) {

       int press = dpad.getDirectionPressed(event);
       switch (press) {
            case LEFT:
                // Do something for LEFT direction press
                ...
                return true;
            case RIGHT:
                // Do something for RIGHT direction press
                ...
                return true;
            case UP:
                // Do something for UP direction press
                ...
                return true;
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

Processar movimentos do joystick

Quando os jogadores movem um joystick nos controles de jogos, o Android informa uma MotionEvent que contém o código de ação ACTION_MOVE e as posições atualizadas dos eixos do joystick. Seu jogo pode usar os dados fornecidos por MotionEvent para determinar se houve um movimento no joystick.

Os eventos de movimento do joystick podem agrupar várias amostras de movimento em um único objeto. O objeto MotionEvent contém a posição atual de cada eixo do joystick, bem como várias posições históricas para cada eixo. Ao relatar eventos de movimento com o código de ação ACTION_MOVE (como movimentos do joystick), o Android agrupa os valores dos eixos para maior eficiência. Os valores históricos de um eixo consistem no conjunto de valores distintos mais antigos do que o valor do eixo atual e mais recentes do que os valores informados em qualquer evento de movimento anterior. Consulte a referência do MotionEvent para mais detalhes.

Você pode usar as informações do histórico para renderizar com mais precisão o movimento de um objeto do jogo com base na entrada do joystick. Para extrair os valores atuais e históricos, chame getAxisValue() ou getHistoricalAxisValue(). Você também pode encontrar o número de pontos históricos no evento do joystick. Para isso, chame getHistorySize().

O snippet abaixo mostra como substituir o callback onGenericMotionEvent() para processar a entrada do joystick. Primeiro, processe os valores históricos de um eixo e, em seguida, processe a posição atual dele.

Kotlin

class GameView(...) : View(...) {

    override fun onGenericMotionEvent(event: MotionEvent): Boolean {

        // Check that the event came from a game controller
        return if (event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
                && event.action == MotionEvent.ACTION_MOVE) {

            // Process the movements starting from the
            // earliest historical position in the batch
            (0 until event.historySize).forEach { i ->
                // Process the event at historical position i
                processJoystickInput(event, i)
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1)
            true
        } else {
            super.onGenericMotionEvent(event)
        }
    }
}

Java

public class GameView extends View {

    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {

        // Check that the event came from a game controller
        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
                InputDevice.SOURCE_JOYSTICK &&
                event.getAction() == MotionEvent.ACTION_MOVE) {

            // Process all historical movement samples in the batch
            final int historySize = event.getHistorySize();

            // Process the movements starting from the
            // earliest historical position in the batch
            for (int i = 0; i < historySize; i++) {
                // Process the event at historical position i
                processJoystickInput(event, i);
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1);
            return true;
        }
        return super.onGenericMotionEvent(event);
    }
}

Antes de usar a entrada do joystick, é necessário determinar se ele está centralizado e calcular os movimentos do eixo de maneira adequada. Os joysticks normalmente têm uma área plana, ou seja, um intervalo de valores próximo à coordenada (0,0) em que o eixo é considerado centralizado. Se o valor do eixo relatado pelo Android estiver dentro da área plana, você precisará tratar o controle como em repouso, ou seja, imóvel ao longo dos dois eixos.

O snippet abaixo mostra um método auxiliar que calcula o movimento ao longo de cada eixo. Você invoca esse auxiliar no método processJoystickInput() descrito mais abaixo.

Kotlin

private fun getCenteredAxis(
        event: MotionEvent,
        device: InputDevice,
        axis: Int,
        historyPos: Int
): Float {
    val range: InputDevice.MotionRange? = device.getMotionRange(axis, event.source)

    // A joystick at rest does not always report an absolute position of
    // (0,0). Use the getFlat() method to determine the range of values
    // bounding the joystick axis center.
    range?.apply {
        val value: Float = if (historyPos < 0) {
            event.getAxisValue(axis)
        } else {
            event.getHistoricalAxisValue(axis, historyPos)
        }

        // Ignore axis values that are within the 'flat' region of the
        // joystick axis center.
        if (Math.abs(value) > flat) {
            return value
        }
    }
    return 0f
}

Java

private static float getCenteredAxis(MotionEvent event,
        InputDevice device, int axis, int historyPos) {
    final InputDevice.MotionRange range =
            device.getMotionRange(axis, event.getSource());

    // A joystick at rest does not always report an absolute position of
    // (0,0). Use the getFlat() method to determine the range of values
    // bounding the joystick axis center.
    if (range != null) {
        final float flat = range.getFlat();
        final float value =
                historyPos < 0 ? event.getAxisValue(axis):
                event.getHistoricalAxisValue(axis, historyPos);

        // Ignore axis values that are within the 'flat' region of the
        // joystick axis center.
        if (Math.abs(value) > flat) {
            return value;
        }
    }
    return 0;
}

Juntando tudo, veja como você pode processar os movimentos do joystick no seu jogo:

Kotlin

private fun processJoystickInput(event: MotionEvent, historyPos: Int) {

    val inputDevice = event.device

    // Calculate the horizontal distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat axis, or the right control stick.
    var x: Float = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_X, historyPos)
    if (x == 0f) {
        x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_X, historyPos)
    }
    if (x == 0f) {
        x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Z, historyPos)
    }

    // Calculate the vertical distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat switch, or the right control stick.
    var y: Float = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Y, historyPos)
    if (y == 0f) {
        y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_Y, historyPos)
    }
    if (y == 0f) {
        y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_RZ, historyPos)
    }

    // Update the ship object based on the new x and y values
}

Java

private void processJoystickInput(MotionEvent event,
        int historyPos) {

    InputDevice inputDevice = event.getDevice();

    // Calculate the horizontal distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat axis, or the right control stick.
    float x = getCenteredAxis(event, inputDevice,
            MotionEvent.AXIS_X, historyPos);
    if (x == 0) {
        x = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_HAT_X, historyPos);
    }
    if (x == 0) {
        x = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_Z, historyPos);
    }

    // Calculate the vertical distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat switch, or the right control stick.
    float y = getCenteredAxis(event, inputDevice,
            MotionEvent.AXIS_Y, historyPos);
    if (y == 0) {
        y = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_HAT_Y, historyPos);
    }
    if (y == 0) {
        y = getCenteredAxis(event, inputDevice,
                MotionEvent.AXIS_RZ, historyPos);
    }

    // Update the ship object based on the new x and y values
}

Para oferecer compatibilidade com controles de jogos que têm recursos mais sofisticados além de um único joystick, siga estas práticas recomendadas:

  • Gerencie dois controles direcionais analógicos. Muitos controles de jogos têm um joystick esquerdo e direito. Para o direcional analógico esquerdo, o Android relata movimentos horizontais como eventos AXIS_X e movimentos verticais como eventos AXIS_Y. Para o direcional analógico direito, o Android relata movimentos horizontais como eventos AXIS_Z e movimentos verticais como eventos AXIS_RZ. Lembre-se de processar os dois controles no seu código.
  • Gerencie o pressionamento dos gatilhos superiores e verifique se o jogo funciona com eventos AXIS_ e KEYCODE_BUTTON_. Alguns controles têm gatilhos no lado esquerdo e direito. Quando esses gatilhos estão presentes, eles emitem um evento AXIS_*TRIGGER, KEYCODE_BUTTON_*2 ou ambos. Para o gatilho esquerdo, seriam AXIS_LTRIGGER e KEYCODE_BUTTON_L2. Para o acionador direito, seria AXIS_RTRIGGER e KEYCODE_BUTTON_R2. Os eventos do eixo só ocorrem se o acionador emitir um intervalo de valores entre 0 e 1, e alguns controladores com saída analógica emitem eventos de botão além dos eventos do eixo. Os jogos precisam oferecer suporte aos eventos AXIS_ e KEYCODE_BUTTON_ para continuarem compatíveis com todos os controles de jogo comuns, mas prefiram o evento mais adequado para a jogabilidade caso um controle mostre ambos. No Android 4.3 (nível 18 da API) e versões mais recentes, um controle que produz um AXIS_LTRIGGER também informa um valor idêntico para o eixo AXIS_BRAKE. O mesmo vale para AXIS_RTRIGGER e AXIS_GAS. O Android informa todos os pressionamentos de gatilhos analógicos com um valor normalizado de 0,0 (liberado) a 1,0 (totalmente pressionado).
  • O suporte e os comportamentos específicos podem ser diferentes em ambientes emulados. Plataformas emuladas, como o Google Play Games, podem ter um comportamento ligeiramente diferente com base nos recursos do sistema operacional host. Por exemplo, alguns controles que emitem eventos AXIS_ e KEYCODE_BUTTON_ só emitem eventos AXIS_, e a compatibilidade com alguns controles pode estar totalmente ausente.