Cómo brindar compatibilidad con varios controladores para juegos

Si bien la mayoría de los juegos están diseñados para admitir un solo usuario por dispositivo Android, también se pueden admitir varios usuarios con controles en juegos que se conectan simultáneamente en el mismo dispositivo Android.

En esta lección, se explican algunas técnicas básicas para administrar la entrada de varios controles conectados en tu juego multijugador en un dispositivo único. Entre ellas, se incluyen mantener una asignación entre los avatares de los jugadores y cada dispositivo controlador, y procesar los eventos de entrada de los controladores de manera adecuada.

Cómo asignar jugadores a los ID de dispositivos controladores

Cuando se conecta un control de juego a un dispositivo con Android, el sistema le asigna un ID de dispositivo entero. Si quieres obtener los IDs de dispositivos de los controles para juegos conectados, llama a InputDevice.getDeviceIds(), como se muestra en Cómo verificar si un control para videojuegos está conectado. Luego, puedes asociar cada ID de dispositivo con un jugador en tu juego y procesar las acciones del juego para cada jugador por separado.

En el fragmento de código, se muestra cómo usar un SparseArray para asociar el avatar de un jugador con un controlador específico. En este ejemplo, la variable mShips almacena una colección de objetos Ship. Se crea un avatar de jugador nuevo en el juego cuando el usuario conecta un controlador nuevo y se lo quita cuando se desconecta el controlador asociado.

Los métodos de devolución de llamada onInputDeviceAdded() y onInputDeviceRemoved() forman parte de la capa de abstracción que se presenta en Cómo brindar compatibilidad con controles en diferentes versiones de Android. Al implementar estas devoluciones de llamadas del objeto de escucha, tu juego puede identificar el ID de dispositivo del control para videojuegos cuando se agrega o quita un control. Esta detección es compatible con Android 2.3 (API nivel 9) y versiones posteriores.

Kotlin

private val ships = SparseArray<Ship>()

override fun onInputDeviceAdded(deviceId: Int) {
    getShipForID(deviceId)
}

override fun onInputDeviceRemoved(deviceId: Int) {
    removeShipForID(deviceId)
}

private fun getShipForID(shipID: Int): Ship {
    return ships.get(shipID) ?: Ship().also {
        ships.append(shipID, it)
    }
}

private fun removeShipForID(shipID: Int) {
    ships.remove(shipID)
}

Java

private final SparseArray<Ship> ships = new SparseArray<Ship>();

@Override
public void onInputDeviceAdded(int deviceId) {
    getShipForID(deviceId);
}

@Override
public void onInputDeviceRemoved(int deviceId) {
    removeShipForID(deviceId);
}

private Ship getShipForID(int shipID) {
    Ship currentShip = ships.get(shipID);
    if ( null == currentShip ) {
        currentShip = new Ship();
        ships.append(shipID, currentShip);
    }
    return currentShip;
}

private void removeShipForID(int shipID) {
    ships.remove(shipID);
}

Cómo procesar la entrada de varios controladores

Tu juego debería ejecutar el siguiente bucle para procesar la entrada de varios controladores:

  1. Detecta si hubo un evento de entrada.
  2. Identifica la fuente de entrada y su ID de dispositivo.
  3. En función de la acción que indica el valor del eje o el código de tecla del evento de entrada, actualiza el avatar del jugador asociado con el ID de dispositivo.
  4. Procesa y actualiza la interfaz de usuario.

Los eventos de entrada KeyEvent y MotionEvent tienen IDs de dispositivos asociados a ellos. Tu juego puede aprovechar esta información para determinar desde qué controlador se produjo el evento de entrada y actualizar el avatar del jugador asociado a este.

En el siguiente fragmento de código, se muestra cómo puedes obtener una referencia del avatar de un jugador que corresponde al ID de dispositivo del controlador para juegos y actualizar el juego en función del botón que el usuario presionó en ese controlador.

Kotlin

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
    if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
        event.deviceId.takeIf { it != -1 }?.also { deviceId ->
            val currentShip: Ship = getShipForID(deviceId)
            // Based on which key was pressed, update the player avatar
            // (e.g. set the ship headings or fire lasers)
            return true
        }
    }
    return super.onKeyDown(keyCode, event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
        int deviceId = event.getDeviceId();
        if (deviceId != -1) {
            Ship currentShip = getShipForId(deviceId);
            // Based on which key was pressed, update the player avatar
            // (e.g. set the ship headings or fire lasers)
            ...
            return true;
        }
    }
    return super.onKeyDown(keyCode, event);
}