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 es posible admitir varios usuarios con controles para juegos que se conectan simultáneamente en el mismo dispositivo Android.

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

Cómo asignar jugadores a los ID de dispositivos controladores

Cuando se conecta un control de juegos a un dispositivo Android, el sistema le asigna un ID de dispositivo de número entero. Si deseas obtener los IDs de dispositivo de los controles de juegos conectados, llama a InputDevice.getDeviceIds(), como se muestra en Cómo verificar que un control para juegos 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.

Nota: En los dispositivos con Android 4.1 (nivel de API 16) y versiones posteriores, puedes obtener un descriptor del dispositivo de entrada con getDescriptor(), que muestra un valor de cadena persistente único para el dispositivo de entrada. A diferencia de un ID de dispositivo, el valor del descriptor no cambiará incluso si el dispositivo de entrada se desconecta, se vuelve a conectar o se reconfigura.

En el siguiente 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 control nuevo y se quita cuando se quita el controlador asociado.

Los métodos de devolución de llamada onInputDeviceAdded() y onInputDeviceRemoved() forman parte de la capa de abstracción que se introdujo en Cómo brindar compatibilidad con controladores en diferentes versiones de Android. Si implementas estas devoluciones de llamada del objeto de escucha, tu juego puede identificar el ID de dispositivo del control para juegos cuando se agrega o quita un control. Esta detección es compatible con Android 2.3 (nivel de API 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 controles:

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

Los eventos de entrada KeyEvent y MotionEvent tienen IDs de dispositivo asociados. Tu juego puede aprovechar esto para determinar de qué controlador provino el evento de entrada y actualizar el avatar del jugador asociado con ese control.

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

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

Nota: Como práctica recomendada, cuando se desconecte el control de un usuario, debes pausar el juego y preguntarle si quiere volver a conectarse.