Cómo administrar las acciones de los controles

Los controladores tienen dos tipos de acciones:

  • KeyEvent Se usa para cualquier botón con un estado binario de "activado" y "desactivado".
  • MotionEvent se usa para cualquier eje que devuelva un rango de valores. Por ejemplo, de -1 a 1 para los joysticks analógicos o de 0 a 1 para los gatillos analógicos.

Puedes leer estas entradas desde el View que tiene focus.

Kotlin

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
  if (event.isFromSource(SOURCE_GAMEPAD)
      && event.repeatCount == 0
  ) {
      Log.d("GameView", "Gamepad key pressed: $keyCode")
      return true
  }

  return super.onKeyDown(keyCode, event)
}

override fun onGenericMotionEvent(event: MotionEvent): Boolean {
  if (event.isFromSource(SOURCE_JOYSTICK)) {
      Log.d("GameView", "Gamepad event: $event")
      return true
  }

  return super.onGenericMotionEvent(event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
  if (event.isFromSource(SOURCE_GAMEPAD)
          && event.getRepeatCount() == 0
  ) {
      Log.d("GameView", "Gamepad key pressed: " + keyCode);
      return true;
  }

  return super.onKeyDown(keyCode, event);
}

@Override
public boolean onGenericMotionEvent(MotionEvent event) {
  if (event.isFromSource(SOURCE_JOYSTICK)) {
      Log.d("GameView", "Gamepad event: " + event);
      return true;
  }
  return super.onGenericMotionEvent(event);
}

Si es necesario, puedes leer eventos directamente desde Activity.

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

Cuando se informan eventos de entrada, Android reutiliza los mismos IDs de clave o eje para diferentes tipos de dispositivos de entrada. Por ejemplo, una acción de pantalla táctil genera un evento AXIS_X que representa la coordenada X de la superficie táctil, pero un gamepad genera un evento AXIS_X que representa la posición X de la palanca izquierda. Esto significa que debes verificar el tipo de fuente para interpretar correctamente los eventos de entrada.

Para verificar que un InputDevice conectado sea un control para videojuegos, usa la función supportsSource(int):

  • Un tipo de fuente de SOURCE_GAMEPAD indica que el dispositivo de entrada tiene botones de control (por ejemplo, KEYCODE_BUTTON_A). Ten en cuenta que este tipo de fuente no indica estrictamente si el control para videojuegos tiene botones de pad direccional, aunque la mayoría de los controles 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 sticks de mando analógicos (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 videojuegos. 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 juegos que se conectan simultáneamente en el mismo dispositivo con Android, consulta Cómo brindar compatibilidad con varios controles para juegos.

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 (supportsSource(SOURCE_GAMEPAD)
              || supportsSource(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);

      if (dev == null) {
          continue;
      }

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

Cómo procesar las entradas del controlador

En esta sección, se describen los tipos de controles de juegos que se admiten en Android.

Los desarrolladores de C++ deben usar la biblioteca de controles para juegos. Unifica todos los controladores en el subconjunto más común de funciones y proporciona una interfaz coherente entre ellos, incluida la capacidad de detectar el diseño de los botones.

En esta figura, se muestra cómo se ve un controlador común en Android, según lo que puede esperar un desarrollador de juegos para Android.

Control de juegos genérico con entradas etiquetadas, incluidos el pad direccional, los sticks analógicos y los botones
Figura 1: Perfil de un control para videojuegos genérico

En la tabla, se enumeran los nombres y tipos de eventos estándar para los controles de juegos. Para obtener una lista completa de los eventos, consulta Variantes comunes. El sistema envía eventos MotionEvent a través de eventos onGenericMotionEvent y KeyEvent a través de onKeyDown y onKeyUp.

Entrada del controlador KeyEvent MotionEvent
1. Pad direccional
AXIS_HAT_X
(entrada horizontal)
AXIS_HAT_Y
(entrada vertical)
2. Palanca analógica izquierda
KEYCODE_BUTTON_THUMBL
(cuando se presiona)
AXIS_X
(movimiento horizontal)
AXIS_Y
(movimiento vertical)
3. Joystick analógico derecho
KEYCODE_BUTTON_THUMBR
(cuando se presiona)
AXIS_Z
(movimiento horizontal)
AXIS_RZ
(movimiento vertical)
4. Botón X KEYCODE_BUTTON_X
5. Botón A KEYCODE_BUTTON_A
6. Botón Y KEYCODE_BUTTON_Y
7. Botón B KEYCODE_BUTTON_B
8. Botón superior derecho
KEYCODE_BUTTON_R1
9. Gatillo derecho
AXIS_RTRIGGER
10. Gatillo izquierdo AXIS_LTRIGGER
11. Botón superior izquierdo KEYCODE_BUTTON_L1
12. Iniciar KEYCODE_BUTTON_START
13. Seleccionar KEYCODE_BUTTON_SELECT

Cómo controlar las presiones de los botones

Dado que Android informa las pulsaciones de los botones del control de la misma manera que las pulsaciones de los botones del teclado, debes hacer lo siguiente:

  • Valida que el evento provenga de un SOURCE_GAMEPAD.
  • Asegúrate de recibir el botón solo una vez con KeyEvent.getRepeatCount(). Android enviará eventos de teclas repetidos como si mantuvieras presionada una tecla del teclado.
  • Para indicar que se controló un evento, devuelve true.
  • Pasa los eventos no controlados a super para verificar que las distintas capas de compatibilidad de Android funcionen de forma adecuada.

    Kotlin

    class GameView : View {
    // ...
    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        event.apply {
            var handled = false
    
            // make sure we're handling gamepad events
            if (isFromSource(SOURCE_GAMEPAD)) {
    
                // avoid processing the keycode repeatedly
                if (repeatCount == 0) {
                    when (keyCode) {
                        // handle the "A" button
                        KEYCODE_BUTTON_A -> {
                          handled = true
                        }
                    }
                    // ...
                }
            }
            if (handled) {
                return true
            }
       }
       return super.onKeyDown(keyCode, event)
      }
    }
    

    Java

    public class GameView extends View {
    // ...
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        // make sure we're handling gamepad events
        if (event.isFromSource(SOURCE_GAMEPAD)) {
            // avoid processing the keycode repeatedly
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    case KEYCODE_BUTTON_A:
                        // handle the "A" button
                        handled = true;
                        break;
                    // ...
                }
            }
            // mark this event as handled
            if (handled) {
                return true;
            }
        }
        // Always do this instead of "return false"
        // it allows Android's input compatibility layers to work
        return super.onKeyDown(keyCode, event);
      }
    }
    

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, en los que -1.0 indica arriba y 1.0 indica abajo. Informa las pulsaciones de las teclas IZQUIERDA o DERECHA del pad direccional como eventos AXIS_HAT_X, en los que -1.0 indica la izquierda y 1.0 indica la 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 de eje del pulsador y del código de tecla desde un evento de entrada a fin de 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.
            return event.isFromSource(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.
        return event.isFromSource(InputDevice.SOURCE_DPAD);
     }
}

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 para videojuegos, 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 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 detalles.

Para renderizar con precisión el movimiento de un objeto del juego en función de la entrada del joystick, puedes usar la información histórica que proporcionan los objetos MotionEvent.

Puedes recuperar los valores actuales y los históricos con los siguientes métodos:

En el siguiente fragmento, se muestra cómo puedes anular la 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 más adelante en la siguiente muestra:

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 para 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 controlar ambos sticks del control en tu código.
  • Administra las pulsaciones de los gatillos (y asegúrate de que tu 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ían AXIS_LTRIGGER y KEYCODE_BUTTON_L2. Para el activador derecho, serían AXIS_RTRIGGER y KEYCODE_BUTTON_R2. Los eventos de eje solo ocurren si el activador emite un rango de valores entre 0 y 1, y algunos controles con salida analógica emiten eventos de botón además de eventos de eje. Los juegos deben admitir eventos AXIS_ y KEYCODE_BUTTON_ para seguir siendo compatibles con todos los controles de juegos comunes, pero se prefiere el evento que tenga más sentido para tu jugabilidad 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 análogos con un valor normalizado desde 0.0 (liberado) hasta 1.0 (completamente presionado).
  • Es posible que los comportamientos y la asistencia específicos difieran en los entornos emulados. Las plataformas emuladas, como Google Play Juegos, pueden diferir ligeramente en su comportamiento según las capacidades del sistema operativo host. Por ejemplo, algunos controles que emiten eventos AXIS_ y KEYCODE_BUTTON_ solo emiten eventos AXIS_, y es posible que falte la compatibilidad con algunos controles por completo.

Variantes comunes

Con la gran variedad de compatibilidad que Android tiene para los controles, es posible que no esté claro cómo compilar y probar para verificar que tu juego funcione sin errores entre tu base de jugadores. Descubrimos que, a pesar de esta aparente variedad, los fabricantes de controles de todo el mundo suelen adherirse de forma constante a tres estilos diferentes de controles. Algunos proporcionan interruptores de hardware entre dos o más de estos.

Esto significa que puedes realizar pruebas con tan solo tres controles entre tu equipo de desarrollo y tener la certeza de que tu juego se puede jugar sin recurrir a listas de bloqueo y de permiso.

Tipos de controles comunes

El estilo más común de los controles tiende a imitar los diseños de las consolas de videojuegos populares. Esto es estético en las etiquetas y el diseño de los botones, y funcional por los eventos que se generan. Los controles con interruptores de hardware entre diferentes tipos de consolas cambiarán los eventos que envían y, a menudo, incluso su diseño lógico de botones.

Cuando realices pruebas, te recomendamos que valides que tu juego funcione con un control de cada categoría. Puedes optar por realizar pruebas con controles propios o con los de fabricantes externos populares. En general, asignaremos los controladores más populares a la definición anterior con el mejor esfuerzo.

Tipo de controlador Diferencias de comportamiento Variaciones de etiquetas
Controles estilo Xbox

Estos son controles que suelen fabricarse para la plataforma Microsoft Xbox y Windows*.

Estos controladores coinciden con el conjunto de funciones que se describen en Cómo procesar las entradas del controlador. Los botones L2/R2 de estos controles están etiquetados como LT/RT.
Interruptores de estilo

Por lo general, estos controles están diseñados para la familia de consolas Nintendo Switch*.

Estos controladores envían los KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent Los botones L2/R2 de estos controles están etiquetados como ZL/ZR.

Estos controles también intercambian los botones A y B, y los botones X y Y, por lo que KEYCODE_BUTTON_A es el botón etiquetado como B y viceversa.

Controles estilo PlayStation

Por lo general, estos controles están diseñados para la familia de consolas Sony PlayStation*.

Estos controles envían MotionEvents, como los controles de estilo Xbox, pero también envían KeyEvents, como los controles de estilo Switch, cuando se presionan por completo. Estos controles usan un conjunto diferente de glifos para los botones frontales.

* Microsoft, Xbox y Windows son marcas registradas de Microsoft; Nintendo Switch es una marca registrada de Nintendo of America Inc.; PlayStation es una marca registrada de Sony Interactive Entertainment Inc.

Desambiguar los botones de activación

Algunos controles envían AXIS_LTRIGGER y AXIS_RTRIGGER, otros envían KEYCODE_BUTTON_L2 y KEYCODE_BUTTON_R2, y otros envían todos estos eventos según sus capacidades de hardware. Para maximizar la compatibilidad, admite todos estos eventos.

Todos los controles que envían AXIS_LTRIGGER también enviarán AXIS_BRAKE, de manera similar para AXIS_RTRIGGER y AXIS_GAS, para ayudar a maximizar la compatibilidad entre los volantes de carreras y los controles de juegos típicos. Por lo general, esto no causará problemas, pero ten en cuenta las funciones como las pantallas de reasignación de teclas.

Trigger MotionEvent KeyEvent
Gatillo izquierdo AXIS_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
Gatillo derecho AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

Se debe tener cuidado para verificar que el juego pueda controlar tanto KeyEvent como MotionEvent para mantener la compatibilidad con la mayor cantidad posible de controles y que los eventos no se dupliquen.

Controles compatibles

Cuando realices pruebas, te recomendamos que valides que tu juego funcione con un control de cada categoría.

  • Estilo de Xbox
  • Estilo de Nintendo Switch
  • Estilo de PlayStation

Puedes realizar pruebas con controles de origen o de fabricantes externos populares, y, por lo general, asignamos los controles más populares a la definición lo más cerca posible.