Compatibilidade com vários controles de jogos

Embora a maioria dos jogos seja compatível com um único usuário por dispositivo Android, também é possível oferecer compatibilidade com vários usuários com controles de jogos conectados simultaneamente no mesmo dispositivo Android.

Esta lição aborda algumas técnicas básicas para processar entradas no seu jogo multijogador de um único dispositivo a partir de vários controles conectados. Isso inclui manter um mapeamento entre avatares de jogadores e cada dispositivo de controle e processar eventos de entrada adequadamente.

Mapear jogadores de acordo com códigos de dispositivo dos controles

Quando um controle de jogo está conectado a um dispositivo Android, o sistema atribui a ele um código do dispositivo na forma de um número inteiro. Você pode ver os códigos de dispositivos dos controles de jogos conectados chamando InputDevice.getDeviceIds(), como mostrado em Verificar se um controle de jogo está conectado. Em seguida, você pode associar cada código de dispositivo a um jogador e processar as ações do jogo para cada jogador separadamente.

Observação: em dispositivos com Android 4.1 (API de nível 16) ou versões posteriores, é possível ter um descritor de dispositivo de entrada usando getDescriptor(), que retorna um valor exclusivo de string persistente para o dispositivo de entrada. Diferentemente do código de um dispositivo, o valor do descritor não será alterado, mesmo que o dispositivo de entrada seja desconectado, reconectado ou reconfigurado.

O snippet de código abaixo mostra como usar um SparseArray para associar o avatar de um jogador a um controle específico. Neste exemplo, a variável mShips armazena uma coleção de objetos Ship. Um novo avatar de jogador é criado no jogo quando um novo controle é conectado por um usuário e removido quando o controle associado é removido.

Os métodos de callback onInputDeviceAdded() e onInputDeviceRemoved() fazem parte da camada de abstração apresentada em Compatibilidade de controles em diversas versões do Android. Ao implementar esses callbacks do listener, seu jogo pode identificar o código do dispositivo do controle de jogo quando um controle é adicionado ou removido. Essa detecção é compatível com o Android 2.3 (API de nível 9) ou versões 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);
    }
    

Processar a entrada de vários controles

Seu jogo precisa executar o seguinte loop para processar entradas de vários controles:

  1. Detectar se ocorreu um evento de entrada.
  2. Identificar a origem da entrada e o código do dispositivo correspondente.
  3. Com base na ação indicada pelo código da tecla do evento de entrada ou pelo valor do eixo, atualizar o avatar do jogador associado ao código do dispositivo.
  4. Renderizar e atualizar a interface do usuário.

Os eventos de entrada KeyEvent e MotionEvent têm códigos de dispositivo associados a eles. Seu jogo pode tirar proveito disso para determinar de qual controle o evento de entrada veio e atualizar o avatar do jogador associado a esse controle.

O snippet de código a seguir mostra como você pode ter uma referência de avatar de jogador correspondente a um código de dispositivo de controle de jogo e atualizar o jogo com base no botão do usuário pressionado nesse controle.

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

Observação: como prática recomendada, quando um controle de jogo de um usuário se desconectar, pause o jogo e pergunte se ele quer se reconectar.