Entrada do mouse

Este tópico aborda como implementar a entrada do mouse no Google Play Games no PC para jogos em que o modo de conversão de entrada não oferece aos uma experiência ideal aos jogadores.

No PC, normalmente há um teclado e um mouse em vez de uma tela touchscreen, o que faz com que seja importante considerar se o jogo acomoda a entrada do mouse. Por padrão, o Google Play Games no PC converte qualquer evento de clique com o botão esquerdo do mouse em um único evento de toque virtual. Isso é conhecido como "modo de conversão de entrada".

Embora esse modo deixe seu jogo funcional com poucas mudanças, ele não oferece aos jogadores de PC uma experiência nativa. Para isso, recomendamos que você implemente o seguinte:

  • Estados de passar o cursor sobre menus de contexto em vez de ações de tocar e pressionar
  • O ato de clicar com o botão direito do mouse para ações alternativas que ocorrem ao tocar e manter pressionado ou em um menu de contexto
  • Controle de visão do mouse para jogos de ação em primeira ou terceira pessoa, em vez de um evento de pressionar e arrastar

Para oferecer suporte a padrões de interface comuns em PCs, desative o modo de conversão de entrada.

O processamento de entrada do Google Play Games no PC é idêntico ao do ChromeOS. As mudanças que podem ser feitas em PCs também melhoram o jogo para o Android.

Desativar o modo de conversão de entrada

No arquivo AndroidManifest.xml, declare o recurso android.hardware.type.pc. Isso indica que o jogo usa o hardware de PC e desativa o modo de conversão de entrada. Além disso, a adição de required="false" ajuda a garantir que o jogo ainda possa ser instalado em smartphones e tablets sem um mouse. Por exemplo:

<manifest ...>
  <uses-feature
      android:name="android.hardware.type.pc"
      android:required="false" />
  ...
</manifest>

A versão de produção do Google Play Games no PC alterna para o modo correto quando um jogo é iniciado. Ao executar no emulador do desenvolvedor, você precisa clicar com o botão direito do mouse no ícone da barra de tarefas, selecionar Developer Options e PC mode(KiwiMouse) para receber entrada bruta do mouse.

Captura de tela do &quot;PC mode(KiwiMouse)&quot; selecionado no menu de contexto

Depois disso, o movimento do mouse será informado por View.onGenericMotionEvent com a origem SOURCE_MOUSE, indicando que esse é um evento de mouse.

Kotlin

gameView.setOnGenericMotionListener { _, motionEvent ->
    var handled = false
    if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
        // handle the mouse event here
        handled = true
    }
    handled
}

Java

gameView.setOnGenericMotionListener((view, motionEvent) -> {
    if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
        // handle the mouse event here
        return true;
    }
    return false;
});

Para ver detalhes sobre como lidar com a entrada do mouse, consulte a documentação do ChromeOS.

Como processar o movimento do mouse

Para identificar o movimento do mouse, é necessário detectar os eventos ACTION_HOVER_ENTER, ACTION_HOVER_EXIT e ACTION_HOVER_MOVE.

Isso ajuda a identificar quando o usuário passa o cursor sobre botões ou objetos em um jogo, dando a você a chance de mostrar uma caixa de dicas ou implementar um estado de mouseover para destacar o que o jogador está prestes a selecionar. Por exemplo:

Kotlin

gameView.setOnGenericMotionListener { _, motionEvent ->
   var handled = false
   if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
       when(motionEvent.action) {
           MotionEvent.ACTION_HOVER_ENTER -> Log.d("MA", "Mouse entered at ${motionEvent.x}, ${motionEvent.y}")
           MotionEvent.ACTION_HOVER_EXIT -> Log.d("MA", "Mouse exited at ${motionEvent.x}, ${motionEvent.y}")
           MotionEvent.ACTION_HOVER_MOVE -> Log.d("MA", "Mouse hovered at ${motionEvent.x}, ${motionEvent.y}")
       }
       handled = true
   }

   handled
}

Java

gameView.setOnGenericMotionListener((view, motionEvent) -> {
    if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_HOVER_ENTER:
                Log.d("MA", "Mouse entered at " + motionEvent.getX() + ", " + motionEvent.getY());
                break;
            case MotionEvent.ACTION_HOVER_EXIT:
                Log.d("MA", "Mouse exited at " + motionEvent.getX() + ", " + motionEvent.getY());
                break;
            case MotionEvent.ACTION_HOVER_MOVE:
                Log.d("MA", "Mouse hovered at " + motionEvent.getX() + ", " + motionEvent.getY());
                break;
        }
        return true;
    }
    return false;
});

Como processar os botões do mouse

Os PCs têm botões os esquerdo e direito no mouse há muito tempo, oferecendo elementos interativos para ações primárias e secundárias. Em um jogo, ações de toque (como tocar em um botão) são associadas ao clique com o botão esquerdo do mouse, enquanto as ações de tocar e manter pressionado ficam mais naturais com o botão direito. Em jogos de estratégia em tempo real, você também pode usar o clique com o botão esquerdo para selecionar e com o botão direito para mover. Os jogos de tiro em primeira pessoa podem atribuir as ações de tiro principal e secundária ao clique com o botão esquerdo e direito, respectivamente. Um jogo de corrida interminável pode usar o clique com o botão esquerdo para pular e com o botão direito para correr mais rápido. Não adicionamos suporte ao evento de clique com a roda do mouse.

Para processar os pressionamentos de botão, use ACTION_DOWN e ACTION_UP. Depois, use getActionButton para determinar qual botão acionou a ação ou getButtonState para saber o estado de todos os botões.

Neste exemplo, uma enumeração é usada para ajudar a mostrar o resultado de getActionButton:

Kotlin

enum class MouseButton {
   LEFT,
   RIGHT,
   UNKNOWN;
   companion object {
       fun fromMotionEvent(motionEvent: MotionEvent): MouseButton {
           return when (motionEvent.actionButton) {
               MotionEvent.BUTTON_PRIMARY -> LEFT
               MotionEvent.BUTTON_SECONDARY -> RIGHT
               else -> UNKNOWN
           }
       }
   }
}

Java

enum MouseButton {
    LEFT,
    RIGHT,
    MIDDLE,
    UNKNOWN;
    static MouseButton fromMotionEvent(MotionEvent motionEvent) {
        switch (motionEvent.getActionButton()) {
            case MotionEvent.BUTTON_PRIMARY:
                return MouseButton.LEFT;
            case MotionEvent.BUTTON_SECONDARY:
                return MouseButton.RIGHT;
            default:
                return MouseButton.UNKNOWN;
        }
    }
}

Neste exemplo, a ação é processada de forma semelhante aos eventos de passar o cursor:

Kotlin

// Handle the generic motion event
gameView.setOnGenericMotionListener { _, motionEvent ->
   var handled = false
   if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
       when (motionEvent.action) {
           MotionEvent.ACTION_BUTTON_PRESS -> Log.d(
               "MA",
               "${MouseButton.fromMotionEvent(motionEvent)} pressed at ${motionEvent.x}, ${motionEvent.y}"
           )
           MotionEvent.ACTION_BUTTON_RELEASE -> Log.d(
               "MA",
               "${MouseButton.fromMotionEvent(motionEvent)} released at ${motionEvent.x}, ${motionEvent.y}"
           )
       }
       handled = true
   }

   handled
}

Java

gameView.setOnGenericMotionListener((view, motionEvent) -> {
    if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_BUTTON_PRESS:
                Log.d("MA", MouseButton.fromMotionEvent(motionEvent) + " pressed at " + motionEvent.getX() + ", " + motionEvent.getY());
                break;
            case MotionEvent.ACTION_BUTTON_RELEASE:
                Log.d("MA", MouseButton.fromMotionEvent(motionEvent) + " released at " + motionEvent.getX() + ", " + motionEvent.getY());
                break;
        }
        return true;
    }
    return false;
});

Processar a rolagem da roda do mouse

Recomendamos que você use a roda de rolagem do mouse em vez do gesto de pinça para aplicar zoom ou tocar e arrastar áreas de rolagem no jogo.

Para ler os valores da roda de rolagem, detecte o evento ACTION_SCROLL. O delta desde o último frame pode ser extraído usando getAxisValue com AXIS_VSCROLL para deslocamento vertical e AXIS_HSCROLL para deslocamento horizontal. Por exemplo:

Kotlin

gameView.setOnGenericMotionListener { _, motionEvent ->
   var handled = false
   if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
       when (motionEvent.action) {
           MotionEvent.ACTION_SCROLL -> {
               val scrollX = motionEvent.getAxisValue(MotionEvent.AXIS_HSCROLL)
               val scrollY = motionEvent.getAxisValue(MotionEvent.AXIS_VSCROLL)
               Log.d("MA", "Mouse scrolled $scrollX, $scrollY")
           }
       }
       handled = true
   }
   handled
}

Java

gameView.setOnGenericMotionListener((view, motionEvent) -> {
    if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_SCROLL:
                float scrollX = motionEvent.getAxisValue(MotionEvent.AXIS_HSCROLL);
                float scrollY = motionEvent.getAxisValue(MotionEvent.AXIS_VSCROLL);
                Log.d("MA", "Mouse scrolled " + scrollX + ", " + scrollY);
                break;
        }
        return true;
    }
    return false;
});

Capturar a entrada do mouse

Alguns jogos precisam ter controle total do cursor do mouse, como jogos de ação em primeira ou terceira pessoa que associam o movimento do mouse ao da câmera. Para ter o controle exclusivo do mouse, invoque View.requestPointerCapture().

A função requestPointerCapture() só funciona quando a hierarquia de visualização que contém sua visualização está em foco. Por esse motivo, não é possível conseguir a captura do ponteiro no callback onCreate. Você precisa esperar a interação do jogador para capturar o ponteiro do mouse, como ao interagir com o menu principal, ou usar o callback onWindowFocusChanged. Exemplos:

Kotlin

override fun onWindowFocusChanged(hasFocus: Boolean) {
   super.onWindowFocusChanged(hasFocus)

   if (hasFocus) {
       gameView.requestPointerCapture()
   }
}

Java

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);

    if (hasFocus) {
        View gameView = findViewById(R.id.game_view);
        gameView.requestPointerCapture();
    }
}

Os eventos capturados por requestPointerCapture() são enviados para a visualização focalizável que registrou OnCapturedPointerListener. Por exemplo:

Kotlin

gameView.focusable = View.FOCUSABLE
gameView.setOnCapturedPointerListener { _, motionEvent ->
    Log.d("MA", "${motionEvent.x}, ${motionEvent.y}, ${motionEvent.actionButton}")
    true
}

Java

gameView.setFocusable(true);
gameView.setOnCapturedPointerListener((view, motionEvent) -> {
    Log.d("MA", motionEvent.getX() + ", " + motionEvent.getY() + ", " + motionEvent.getActionButton());
    return true;
});

Para liberar a captura exclusiva do mouse, por exemplo, para permitir que os jogadores interajam com um menu de pausa, invoque View.releasePointerCapture().