Gestire le azioni del controller

I controller hanno due tipi di azioni:

  • KeyEvent utilizzato per qualsiasi pulsante con uno stato binario "on" e "off"
  • MotionEvent utilizzato per qualsiasi asse che restituisce un intervallo di valori. Ad esempio, da -1 a 1 per le levette analogiche o da 0 a 1 per i grilletti analogici.

Puoi leggere questi input dalla View che ha 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);
}

Se necessario, puoi leggere gli eventi direttamente da Activity.

Verificare che un controller di gioco sia connesso

Quando segnala gli eventi di input, Android riutilizza gli stessi ID di tasti o assi per diversi tipi di dispositivi di input. Ad esempio, un'azione del touchscreen genera un AXIS_X evento che rappresenta la coordinata X della superficie touch, ma un gamepad genera un AXIS_X evento che rappresenta la posizione X della levetta analogica sinistra. Ciò significa che devi controllare il tipo di origine per interpretare correttamente gli eventi di input.

Per verificare che un InputDevice connesso sia un controller di gioco, utilizza la supportsSource(int) funzione:

  • Un tipo di origine SOURCE_GAMEPAD indica che il dispositivo di input ha i pulsanti del controller (ad esempio, KEYCODE_BUTTON_A). Tieni presente che questo tipo di origine non indica rigorosamente se il controller di gioco ha i pulsanti del D-pad, anche se la maggior parte dei controller in genere ha controlli direzionali.
  • Un tipo di origine SOURCE_DPAD indica che il dispositivo di input ha i pulsanti del D-pad (ad esempio, DPAD_UP).
  • Un tipo di origine SOURCE_JOYSTICK indica che il dispositivo di input ha levette di controllo analogiche (ad esempio, un joystick che registra i movimenti lungo AXIS_X e AXIS_Y).

Il seguente snippet di codice mostra un metodo helper che ti consente di verificare se i dispositivi di input connessi sono controller di gioco. In caso affermativo, il metodo recupera gli ID dispositivo per i controller di gioco. Puoi quindi associare ogni ID dispositivo a un giocatore nel tuo gioco ed elaborare le azioni di gioco per ogni giocatore connesso separatamente. Per saperne di più sul supporto di più controller di gioco connessi contemporaneamente sullo stesso dispositivo Android, vedi Supportare più controller di gioco.

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

Elaborare gli input del controller

Questa sezione descrive i tipi di controller di gioco supportati su Android.

Gli sviluppatori C++ devono utilizzare la libreria del controller di gioco. Unifica tutti i controller nel sottoinsieme più comune di funzionalità e fornisce un'interfaccia coerente tra loro, inclusa la possibilità di rilevare il layout dei pulsanti.

Questa figura mostra l'aspetto di un controller comune su Android per uno sviluppatore di giochi Android.

Controller di gioco generico con input etichettati, tra cui D-pad, levette analogiche e pulsanti
Figura 1. Profilo per un controller di gioco generico.

La tabella elenca i nomi e i tipi di eventi standard per i controller di gioco. Per un elenco completo degli eventi, vedi Varianti comuni. Il sistema invia gli eventi MotionEvent tramite onGenericMotionEvent e gli eventi KeyEvent tramite onKeyDown e onKeyUp.

Input del controller KeyEvent MotionEvent
1. D-pad
AXIS_HAT_X
(input orizzontale)
AXIS_HAT_Y
(input verticale)
2. Levetta analogica sinistra
KEYCODE_BUTTON_THUMBL
(quando viene premuta)
AXIS_X
(movimento orizzontale)
AXIS_Y
(movimento verticale)
3. Levetta analogica destra
KEYCODE_BUTTON_THUMBR
(quando viene premuta)
AXIS_Z
(movimento orizzontale)
AXIS_RZ
(movimento verticale)
4. Pulsante X KEYCODE_BUTTON_X
5. Pulsante A KEYCODE_BUTTON_A
6. Pulsante Y KEYCODE_BUTTON_Y
7. Pulsante B KEYCODE_BUTTON_B
8. Pulsante dorsale destro
KEYCODE_BUTTON_R1
9. Grilletto destro
AXIS_RTRIGGER
10. Grilletto sinistro AXIS_LTRIGGER
11. Pulsante dorsale sinistro KEYCODE_BUTTON_L1
12. Inizia KEYCODE_BUTTON_START
13. Seleziona KEYCODE_BUTTON_SELECT

Gestire le pressioni dei pulsanti

Poiché Android segnala le pressioni dei pulsanti del controller in modo identico a quelle dei pulsanti della tastiera, devi:

  • Verificare che l'evento provenga da un SOURCE_GAMEPAD.
  • Assicurarti di ricevere il pulsante una sola volta con KeyEvent.getRepeatCount(), Android invierà eventi di tasti ripetuti come se avessi tenuto premuto un tasto della tastiera.
  • Indicare che un evento è gestito restituendo true.
  • Passare gli eventi non gestiti a super per verificare che i vari livelli di compatibilità di Android funzionino correttamente.

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

Elaborare l'input del pad direzionale

Il pad direzionale a 4 vie, o D-pad, è un controllo fisico comune in molti controller di gioco. Android segnala le pressioni del D-pad SU e GIÙ come eventi AXIS_HAT_Y, con -1.0 che indica su e 1.0 che indica giù. Segnala le pressioni del D-pad SINISTRA o DESTRA come eventi AXIS_HAT_X, con -1.0 che indica sinistra e 1.0 che indica destra.

Alcuni controller segnalano invece le pressioni del D-pad con un codice tasto. Se il tuo gioco si occupa delle pressioni del D-pad, devi trattare gli eventi dell'asse del cappello e i codici tasto del D-pad come gli stessi eventi di input, come consigliato nella tabella 2.

Tabella 2. Azioni di gioco predefinite consigliate per i codici tasto del D-pad e i valori dell'asse del cappello.

Azione di gioco Codice tasto del D-pad Codice dell'asse del cappello
Sposta su KEYCODE_DPAD_UP AXIS_HAT_Y (per valori da 0 a -1.0)
Sposta giù KEYCODE_DPAD_DOWN AXIS_HAT_Y (per valori da 0 a 1.0)
Sposta a sinistra KEYCODE_DPAD_LEFT AXIS_HAT_X (per valori da 0 a -1.0)
Sposta a destra KEYCODE_DPAD_RIGHT AXIS_HAT_X (per valori da 0 a 1.0)

Il seguente snippet di codice mostra una classe helper che ti consente di controllare i valori dell'asse del cappello e del codice tasto di un evento di input per determinare la direzione del D-pad.

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

Puoi utilizzare questa classe helper nel tuo gioco ovunque tu voglia elaborare l'input del D-pad (ad esempio, nei onGenericMotionEvent() o onKeyDown() callback).

Ad esempio:

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

Elaborare i movimenti del joystick

Quando i giocatori spostano un joystick sui controller di gioco, Android segnala un MotionEvent che contiene il ACTION_MOVE codice azione e le posizioni aggiornate degli assi del joystick. Il tuo gioco può utilizzare i dati forniti da MotionEvent per determinare se si è verificato un movimento del joystick di cui si occupa.

Tieni presente che gli eventi di movimento del joystick possono raggruppare più campioni di movimento in un singolo oggetto. L' MotionEvent oggetto contiene la posizione attuale di ogni asse del joystick, nonché più posizioni storiche per ogni asse. Quando segnala gli eventi di movimento con il codice azione ACTION_MOVE (ad esempio i movimenti del joystick), Android raggruppa i valori degli assi per efficienza. I valori storici di un asse sono costituiti dall'insieme di valori distinti più vecchi del valore dell'asse attuale e più recenti dei valori segnalati in eventuali eventi di movimento precedenti. Per i dettagli, consulta il MotionEvent riferimento.

Per eseguire il rendering accurato del movimento di un oggetto di gioco in base all'input del joystick, puoi utilizzare le informazioni storiche fornite dagli oggetti MotionEvent.

Puoi recuperare i valori attuali e storici utilizzando i seguenti metodi:

Il seguente snippet mostra come potresti sostituire il onGenericMotionEvent() callback per elaborare l'input del joystick. Devi prima elaborare i valori storici di un asse, quindi la sua posizione attuale.

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

Prima di utilizzare l'input del joystick, devi determinare se il joystick è centrato, quindi calcolare i movimenti degli assi di conseguenza. I joystick in genere hanno un'area piatta, ovvero un intervallo di valori vicino alla coordinata (0,0) in cui l'asse è considerato centrato. Se il valore dell'asse segnalato da Android rientra nell'area piatta, devi considerare che il controller è a riposo (ovvero immobile lungo entrambi gli assi).

Lo snippet mostra un metodo helper che calcola il movimento lungo ogni asse. Richiami questo helper nel metodo processJoystickInput() descritto più avanti nell'esempio seguente:

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

Mettendo insieme tutto, ecco come potresti elaborare i movimenti del joystick nel tuo gioco:

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
}

Per supportare i controller di gioco con funzionalità più sofisticate oltre a un singolo joystick, segui queste best practice:

  • Gestisci le levette dei controller doppi. Molti controller di gioco hanno una levetta sinistra e una destra. Per la levetta sinistra, Android segnala i movimenti orizzontali come AXIS_X eventi e i movimenti verticali come AXIS_Y eventi. Per la levetta destra, Android segnala i movimenti orizzontali come AXIS_Z eventi e i movimenti verticali come AXIS_RZ eventi. Assicurati di gestire entrambe le levette del controller nel codice.
  • Gestisci le pressioni dei grilletti dorsali (e assicurati che il gioco funzioni con gli eventi AXIS_ e KEYCODE_BUTTON_). Alcuni controller hanno grilletti dorsali sinistro e destro. Quando questi grilletti sono presenti, emettono un evento AXIS_*TRIGGER o KEYCODE_BUTTON_*2 o entrambi. Per il grilletto sinistro, si tratta di e AXIS_LTRIGGER e KEYCODE_BUTTON_L2. Per il grilletto destro, si tratta di AXIS_RTRIGGER e KEYCODE_BUTTON_R2. Gli eventi dell'asse si verificano solo se il grilletto emette un intervallo di valori compreso tra 0 e 1 e alcuni controller con output analogico emettono eventi di pulsanti oltre agli eventi dell'asse. I giochi devono supportare sia gli eventi AXIS_ sia KEYCODE_BUTTON_ per rimanere compatibili con tutti i controller di gioco comuni, ma preferiscono l'evento più sensato per il gameplay se un controller segnala entrambi. Su Android 4.3 (livello API 18) e versioni successive, un controller che produce un AXIS_LTRIGGER segnala anche un valore identico per l' AXIS_BRAKE asse. Lo stesso vale per AXIS_RTRIGGER e AXIS_GAS. Android segnala tutte le pressioni dei grilletti analogici con un valore normalizzato da 0.0 (rilasciato) a 1.0 (premuto completamente).
  • I comportamenti e il supporto specifici possono variare negli ambienti emulati. Le piattaforme emulati, come Google Play Games, possono variare leggermente nel comportamento in base alle funzionalità del sistema operativo host. Ad esempio, alcuni controller che emettono sia eventi AXIS_ sia KEYCODE_BUTTON_ emettono solo eventi AXIS_ e il supporto per alcuni controller potrebbe mancare completamente.

Varianti comuni

Con la vasta gamma di supporto di Android per i controller, potrebbe non essere chiaro come creare e testare per verificare che il gioco funzioni senza bug nella tua base di giocatori. Abbiamo scoperto che, nonostante questa apparente varietà, i produttori di controller di tutto il mondo tendono ad aderire costantemente a tre diversi stili di controller. Alcuni forniscono interruttori hardware tra due o più di questi.

Ciò significa che puoi eseguire test con un minimo di tre controller nel tuo team di sviluppo e rimanere sicuro che il tuo gioco sia giocabile senza ricorrere a elenchi di autorizzazione e blocco.

Tipi di controller comuni

Lo stile più comune di controller tende a imitare i layout delle console di gioco più diffuse. Questo è sia estetico nelle etichette e nel layout dei pulsanti sia funzionale in base agli eventi generati. I controller con interruttori hardware tra diversi tipi di console cambieranno gli eventi inviati e spesso anche il layout logico dei pulsanti.

Durante i test, ti consigliamo di verificare che il gioco funzioni con un controller in ciascuna delle categorie. Puoi scegliere di eseguire test con controller proprietari o con produttori di terze parti più diffusi. In genere, mappiamo i controller più diffusi alla definizione sopra indicata nel miglior modo possibile.

Tipo di controller Differenze comportamentali Varianti di etichettatura
Controller in stile Xbox

Questi controller sono in genere realizzati per la piattaforma Microsoft Xbox e Windows* platform.

Questi controller corrispondono al set di funzionalità descritto in Elaborare gli input del controller I pulsanti L2/R2 di questi controller sono etichettati LT/RT
Controller in stile Switch

Questi controller sono in genere progettati per la famiglia di console Nintendo Switch* .

Questi controller inviano gli KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvents I pulsanti L2/R2 di questi controller sono etichettati ZL/ZR.

Questi controller scambiano anche i pulsanti A e B e i pulsanti X e Y, quindi KEYCODE_BUTTON_A è il pulsante etichettato B e viceversa.

Controller in stile PlayStation

Questi controller sono in genere progettati per la famiglia di console Sony PlayStation* .

Questi controller inviano MotionEvents come i controller in stile Xbox, ma inviano anche KeyEvents come i controller in stile Switch quando sono completamente premuti. Questi controller utilizzano un set di glifi diverso per i pulsanti frontali.

* Microsoft, Xbox e Windows sono marchi registrati di Microsoft; Nintendo Switch è un marchio registrato di Nintendo of America Inc.; PlayStation è un marchio registrato di Sony Interactive Entertainment Inc.

Distinguere i pulsanti dei grilletti

Alcuni controller inviano AXIS_LTRIGGER e AXIS_RTRIGGER, altri inviano KEYCODE_BUTTON_L2 e KEYCODE_BUTTON_R2, mentre altri inviano tutti questi eventi in base alle funzionalità hardware. Massimizza la compatibilità supportando tutti questi eventi.

Tutti i controller che inviano AXIS_LTRIGGER invieranno anche AXIS_BRAKE, analogamente per AXIS_RTRIGGER e AXIS_GAS per massimizzare la compatibilità tra i volanti e i controller di gioco tipici. In genere, questo non causa problemi, ma tieni presente che per funzionalità come le schermate di rimappatura dei tasti.

Grilletto MotionEvent KeyEvent
Grilletto sinistro AXIS_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
Grilletto destro AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

È necessario verificare che il gioco possa gestire sia KeyEvent sia MotionEvent per mantenere la compatibilità con il maggior numero possibile di controller e che gli eventi siano deduplicati.

Controller supportati

Durante i test, ti consigliamo di verificare che il gioco funzioni con un controller in ciascuna delle categorie.

  • Stile Xbox
  • Stile Nintendo Switch
  • Stile PlayStation

Puoi eseguire test con controller proprietari o con produttori di terze parti più diffusi e in genere mappiamo i controller più diffusi alla definizione nel modo più preciso possibile.