Cómo administrar las acciones de los controles

En el nivel del sistema, Android registra los códigos de eventos de entrada de controles para videojuegos como códigos de teclas y valores de ejes de Android. En tu juego, puedes recibir estos códigos y valores, y convertirlos en acciones específicas del juego.

Cuando los jugadores conectan físicamente o sincronizan de manera inalámbrica un control para videojuegos con sus dispositivos Android, el sistema detecta automáticamente el control como dispositivo de entrada y comienza a registrar los eventos de entrada. Tu juego puede recibir estos eventos de entrada implementando los siguientes métodos de devolución de llamada en tu Activity activo o View enfocado (debes implementar las devoluciones de llamada para Activity o View, pero no para ambos):

El enfoque recomendado es capturar los eventos desde el objeto View específico con el que el usuario interactúa. Inspecciona los siguientes objetos que proporcionan las devoluciones de llamadas para obtener información sobre el tipo de evento de entrada recibido:

KeyEvent
Corresponde a un objeto que describe los eventos de botones de un pad direccional (D-pad) y de un control de juegos. Los eventos de teclas van acompañados de un código de tecla que indica qué botón se activó, como DPAD_DOWN o BUTTON_A. Puedes obtener el código de tecla llamando a getKeyCode() o desde las devoluciones de llamadas de eventos de teclas, como onKeyDown().
MotionEvent
Objeto que describe la entrada del joystick y los movimientos de los gatillos dorsales. Los eventos de movimiento van acompañados de un código de acción y un conjunto de valores de ejes. El código de acción especifica el cambio de estado que se produjo a medida que se movía el joystick. Los valores de los ejes describen la posición y otras propiedades de movimiento de un control físico específico, como AXIS_X o AXIS_RTRIGGER. Para obtener el código de acción, llama a getAction(); para obtener el valor de los ejes, llama a getAxisValue().

Esta lección se enfoca en cómo puedes manejar la entrada de los tipos más comunes de controles físicos (botones de controles de juegos, mandos de dirección y joysticks) en la pantalla de un juego mediante la implementación de los métodos de devolución de llamada View mencionados anteriormente y el procesamiento de los objetos KeyEvent y MotionEvent.

Cómo verificar si un control para videojuegos está conectado

Cuando registra eventos de entrada, Android no distingue entre los que provienen de un control que no es para videojuegos y los que provienen de un control para videojuegos. Por ejemplo, una acción de pantalla táctil genera un evento AXIS_X que representa la coordenada X de la superficie táctil; en cambio, un joystick genera un evento AXIS_X que representa la posición X del joystick. Si tu juego se encarga de administrar la entrada de controles para videojuegos, primero deberías verificar que el evento de entrada provenga de un tipo de fuente relevante.

Si quieres verificar que un dispositivo de entrada conectado es un control de juegos, llama a getSources() para obtener un campo de bits combinado de los tipos de fuentes de entrada que admite ese dispositivo. Luego, puedes realizar una prueba para ver si se configuraron los siguientes campos:

  • Un tipo de fuente de SOURCE_GAMEPAD indica que el dispositivo de entrada tiene botones de control de juegos (por ejemplo, BUTTON_A). Ten en cuenta que este tipo de fuente no indica estrictamente si el control de juegos tiene botones de pad direccional, aunque la mayoría de los controles de juegos suelen tener controles direccionales.
  • Un tipo de fuente de SOURCE_DPAD indica que el dispositivo de entrada tiene botones de pad direccional (por ejemplo, DPAD_UP).
  • Un tipo de fuente de SOURCE_JOYSTICK indica que el dispositivo de entrada tiene un stick de mando analógico (por ejemplo, un joystick que registra movimientos en AXIS_X y AXIS_Y).

En el siguiente fragmento de código, se muestra un método auxiliar que te permite comprobar si los dispositivos de entrada conectados son controles para videojuegos. Si es así, el método recupera los IDs de dispositivo de los controles para juegos. Luego, puedes asociar cada ID de dispositivo con un jugador en tu juego y procesar por separado las acciones del juego para cada jugador conectado. Para obtener más información sobre cómo admitir varios controles para videojuegos que se conectan simultáneamente en el mismo dispositivo Android, consulta Cómo brindar compatibilidad con varios controles para videojuegos.

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;
}

Además, es posible que quieras buscar capacidades de entrada individuales admitidas por un control para videojuegos conectado. Esto puede resultar útil, por ejemplo, si quieres que tu juego use solamente la entrada del conjunto de controles físicos que conoce.

Si quieres detectar si un control para videojuegos conectado admite un código de tecla o un código de eje específico, usa las siguientes técnicas:

  • En Android 4.4 (nivel de API 19) o versiones posteriores, puedes determinar si un código de tecla es compatible con un control para videojuegos conectado llamando a hasKeys(int...).
  • En Android 3.1 (nivel de API 12) o versiones posteriores, si quieres encontrar todos los ejes disponibles que se admiten en un control para videojuegos conectado, primero debes llamar a getMotionRanges(). Luego, en cada objeto InputDevice.MotionRange que se muestre, llama a getAxis() para obtener su ID de eje.

Cómo procesar la presión de los botones del control de juegos

En la Figura 1, se muestra cómo Android asigna códigos de teclas y valores de ejes a los controles físicos en la mayoría de los controles para videojuegos.

Figura 1: Perfil de un control para videojuegos genérico

Las leyendas de la figura hacen referencia a lo siguiente:

Entre los códigos de teclas comunes que se generan cuando se presiona el botón del control de juegos, se incluyen BUTTON_A, BUTTON_B, BUTTON_SELECT y BUTTON_START. Algunos controles para videojuegos también activan el código de tecla DPAD_CENTER cuando se presiona el centro de la barra del pad direccional. Tu juego puede inspeccionar el código de tecla mediante una llamada a getKeyCode() o desde devoluciones de llamadas de eventos de teclas, como onKeyDown(), y, si este representa un evento que es relevante para tu juego, deberás procesarlo como una acción del juego. En la tabla 1, se enumeran las acciones del juego recomendadas para la mayoría de los botones de controles de juegos comunes.

Tabla 1: Acciones de juegos recomendadas para los botones de controles de juegos

Acción del juego Código de tecla del botón
Iniciar juego en el menú principal o pausar/reanudar durante el juego BUTTON_START*
Mostrar menú BUTTON_SELECT* y KEYCODE_MENU*
Igual al comportamiento de la navegación hacia Atrás de Android que se describe en la guía de diseño de Navegación KEYCODE_BACK
Volver al elemento de menú anterior BUTTON_B
Confirmar la selección o realizar una acción principal del juego BUTTON_A y DPAD_CENTER

* Tu juego no debería depender de la presencia de los botones "Comenzar", "Seleccionar" o "Menú".

Sugerencia: Considera proporcionar una pantalla de configuración en tu juego de manera que los usuarios puedan personalizar las asignaciones de las acciones del juego en los controles para videojuegos.

En el siguiente fragmento, se muestra cómo puedes anular onKeyDown() para asociar las pulsaciones de los botones BUTTON_A y DPAD_CENTER con una acción del juego.

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;
    }
}

Nota: En Android 4.2 (nivel de API 17) y versiones anteriores, el sistema trata al BUTTON_A como la tecla Atrás de Android de manera predeterminada. Si tu app admite estas versiones de Android, asegúrate de tratar a BUTTON_A como la acción principal del juego. Para determinar la versión actual del SDK de Android en el dispositivo, consulta el valor Build.VERSION.SDK_INT.

Cómo procesar la entrada de un pad direccional

El pad direccional (D-pad) de 4 posiciones es un control físico común en muchos controles para videojuegos. Android informa las pulsaciones de las teclas ARRIBA y ABAJO en el pad direccional como eventos AXIS_HAT_Y con un rango de -1.0 (arriba) a 1.0 (abajo) y las pulsaciones de las teclas IZQUIERDA o DERECHA como eventos AXIS_HAT_X con un rango de -1.0 (izquierda) a 1.0 (derecha).

Sin embargo, algunos controles informan las pulsaciones en el pad direccional con un código de tecla. Si tu juego tiene en cuenta las pulsaciones del pad direccional, deberías tratar los eventos de ejes del pulsador y los códigos de teclas del pad direccional como eventos de entrada iguales, como se recomienda en la tabla 2.

Tabla 2: Acciones del juego predeterminadas recomendadas para los códigos de teclas y los valores de ejes del pulsador

Acción del juego Código de tecla del pad direccional Código de eje del pulsador
Subir KEYCODE_DPAD_UP AXIS_HAT_Y (para los valores de 0 a -1.0)
Bajar KEYCODE_DPAD_DOWN AXIS_HAT_Y (para los valores 0 a 1.0)
Mover hacia la izquierda KEYCODE_DPAD_LEFT AXIS_HAT_X (para los valores de 0 a -1.0)
Mover hacia la derecha KEYCODE_DPAD_RIGHT AXIS_HAT_X (para los valores 0 a 1.0)

En el siguiente fragmento de código, se muestra una clase auxiliar que te permite comprobar los valores del eje del pulsador y del código de tecla desde un evento de entrada para determinar la dirección del pad direccional.

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;
         }
     }
}

Puedes usar esta clase auxiliar en tu juego en donde quieras procesar la entrada del pad direccional (por ejemplo, en las devoluciones de llamadas onGenericMotionEvent() o onKeyDown()).

Por ejemplo:

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.
    ...
}

Cómo procesar los movimientos del joystick

Cuando los jugadores mueven un joystick en sus controles de juegos, Android informa un MotionEvent que contiene el código de acción ACTION_MOVE y las posiciones actualizadas de los ejes del joystick. Tu juego puede usar los datos que proporciona el MotionEvent para determinar si se produjo un movimiento importante del joystick.

Ten en cuenta que los eventos de movimiento del joystick pueden agrupar varios ejemplos de movimientos por lotes con un único objeto. El objeto MotionEvent contiene la posición actual de cada eje del joystick, además de varias posiciones históricas de cada eje. Cuando se informan los eventos de movimiento con el código de acción ACTION_MOVE (como movimientos del joystick), Android agrupa los valores de ejes por lotes a fin de mejorar la eficacia. Los valores históricos de un eje consisten en un conjunto de valores distintos más antiguos que el valor de eje actual y más recientes que los valores informados en cualquier evento de movimiento anterior. Consulta la referencia de MotionEvent para obtener más detalles.

Puedes usar la información histórica para renderizar con mayor precisión el movimiento de un objeto del juego en la entrada del joystick. Para recuperar los valores históricos y actuales, llama a getAxisValue() o getHistoricalAxisValue(). También puedes llamar a getHistorySize() para buscar la cantidad de puntos históricos en el evento del joystick.

En el siguiente fragmento, se muestra cómo puedes anular el método de devolución de llamada onGenericMotionEvent() para procesar la entrada del joystick. Primero debes procesar los valores históricos de un eje y, luego, su posición actual.

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 la entrada del joystick, debes determinar si está centrado y, luego, calcular los movimientos de sus ejes según corresponda. Los joysticks suelen tener un área plana, es decir, un rango de valores cerca de la coordenada (0,0) en el que se considera que el eje está centrado. Si el valor de eje que informa Android se encuentra dentro de esta área plana, deberías considerar que el control está en reposo (es decir, no se perciben movimientos en ninguno de los dos ejes).

En el siguiente fragmento, se muestra un método auxiliar que calcula el movimiento en cada eje. Este auxiliar se invoca en el método processJoystickInput() que se describe a continuación.

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;
}

En resumen, aquí se muestra cómo puedes procesar los movimientos del joystick en tu juego:

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
}

Si quieres admitir controles para videojuegos que tengan funciones más sofisticadas además de un solo joystick, sigue estas prácticas recomendadas:

  • Administra los controles de dos sticks. Muchos controles de juegos tienen un joystick izquierdo y otro derecho. En el caso del stick izquierdo, Android informa los movimientos horizontales como eventos AXIS_X y los movimientos verticales como eventos AXIS_Y. En el caso del stick derecho, Android informa los movimientos horizontales como eventos AXIS_Z y los movimientos verticales como eventos AXIS_RZ. Asegúrate de administrar los dos sticks del control en tu código.
  • Administra las pulsaciones de los gatillos (y asegúrate de que el juego funcione con los eventos AXIS_ y KEYCODE_BUTTON_). Algunos controles tienen gatillos. Cuando estos activadores están presentes, emiten un evento AXIS_*TRIGGER o KEYCODE_BUTTON_*2, o ambos. Para el activador izquierdo, sería AXIS_LTRIGGER y KEYCODE_BUTTON_L2. Para el gatillo derecho, sería AXIS_RTRIGGER y KEYCODE_BUTTON_R2. Los eventos del eje solo ocurren si el activador emite un rango de valores entre 0 y 1, y algunos controladores con salida analógica emiten eventos de botones además de los eventos de ejes. Los juegos deben admitir los eventos AXIS_ y KEYCODE_BUTTON_ para seguir siendo compatibles con todos los controles de juegos comunes, pero prefiere el evento que sea más adecuado para tu juego si un control informa ambos. En Android 4.3 (nivel de API 18) y versiones posteriores, un controlador que produce un AXIS_LTRIGGER también informa un valor idéntico para el eje de AXIS_BRAKE. Lo mismo sucede con AXIS_RTRIGGER y AXIS_GAS. Android informa todas las pulsaciones de los gatillos analógicos con un valor normalizado de 0.0 (liberado) a 1.0 (completamente presionado).
  • Los comportamientos y la compatibilidad específicos pueden diferir en los entornos emulados. Las plataformas emuladas, como Google Play Juegos, pueden tener un comportamiento ligeramente diferente según las capacidades del sistema operativo host. Por ejemplo, algunos controladores que emiten eventos AXIS_ y KEYCODE_BUTTON_ solo emiten eventos AXIS_, y es posible que no se admita ningún controlador.