Obsługuj działania kontrolera

Kontrolery mają 2 rodzaje działań:

  • KeyEvent używany w przypadku każdego przycisku o dwóch stanach: „włączony” i „wyłączony”.
  • MotionEvent – używany w przypadku osi, która zwraca zakres wartości. np. od -1 do 1 w przypadku drążków analogowych lub od 0 do 1 w przypadku spustów analogowych.

Możesz odczytać te dane wejściowe z View, który ma 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);
}

W razie potrzeby możesz zamiast tego odczytywać zdarzenia bezpośrednio z Activity.

Sprawdzanie, czy kontroler gier jest podłączony

Podczas raportowania zdarzeń wprowadzania danych Android ponownie używa tych samych identyfikatorów klawiszy lub osi w przypadku różnych typów urządzeń wejściowych. Na przykład działanie na ekranie dotykowym generuje zdarzenie AXIS_X, które reprezentuje współrzędną X powierzchni dotykowej, ale gamepad generuje zdarzenie AXIS_X, które reprezentuje pozycję X lewego drążka. Oznacza to, że musisz sprawdzić typ źródła, aby prawidłowo interpretować zdarzenia wejściowe.

Aby sprawdzić, czy podłączone urządzenie InputDevice jest kontrolerem do gier, użyj funkcji supportsSource(int):

  • Typ źródła SOURCE_GAMEPAD oznacza, że urządzenie wejściowe ma przyciski kontrolera (np. KEYCODE_BUTTON_A). Pamiętaj, że ten typ źródła nie wskazuje jednoznacznie, czy kontroler gier ma przyciski D-pad, chociaż większość kontrolerów ma zwykle elementy sterujące kierunkiem.
  • Typ źródła SOURCE_DPAD oznacza, że urządzenie wejściowe ma przyciski D-pad (np. DPAD_UP).
  • Typ źródła SOURCE_JOYSTICK oznacza, że urządzenie wejściowe ma analogowe drążki sterujące (np. joystick rejestrujący ruchy wzdłuż osi AXIS_XAXIS_Y).

Poniższy fragment kodu pokazuje metodę pomocniczą, która umożliwia sprawdzenie, czy podłączone urządzenia wejściowe to kontrolery do gier. Jeśli tak, metoda pobiera identyfikatory urządzeń dla kontrolerów do gier. Następnie możesz powiązać każdy identyfikator urządzenia z graczem w swojej grze i osobno przetwarzać działania w grze dla każdego połączonego gracza. Więcej informacji o obsłudze kilku kontrolerów do gier podłączonych jednocześnie do tego samego urządzenia z Androidem znajdziesz w artykule Obsługa kilku kontrolerów do gier.

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

Przetwarzanie danych wejściowych kontrolera

W tej sekcji opisujemy typy kontrolerów do gier obsługiwane na Androidzie.

Deweloperzy C++ powinni używać biblioteki kontrolera gier. Ujednolica wszystkie kontrolery do najczęstszego podzbioru funkcji i zapewnia spójny interfejs między nimi, w tym możliwość wykrywania układu przycisków.

Ilustracja przedstawiająca, jak może wyglądać typowy kontroler na Androidzie.

Ogólny kontroler do gier z oznaczonymi elementami sterującymi, w tym padem kierunkowym, gałkami analogowymi i przyciskami.
Rysunek 1. Profil ogólnego kontrolera do gier.

Tabela zawiera standardowe nazwy i typy zdarzeń w przypadku kontrolerów do gier. Pełną listę zdarzeń znajdziesz w sekcji Typowe warianty. System wysyła zdarzenia MotionEvent przez onGenericMotionEvent, a zdarzenia KeyEvent przez onKeyDownonKeyUp.

Dane wejściowe kontrolera KeyEvent MotionEvent
1. Pad kierunkowy
AXIS_HAT_X
(poziome dane wejściowe)
AXIS_HAT_Y
(pionowe dane wejściowe)
2. Lewa gałka analogowa
KEYCODE_BUTTON_THUMBL
(po naciśnięciu)
AXIS_X
(ruch poziomy)
AXIS_Y
(ruch pionowy)
3. Prawa gałka analogowa
KEYCODE_BUTTON_THUMBR
(po naciśnięciu)
AXIS_Z
(ruch poziomy)
AXIS_RZ
(ruch pionowy)
4. Przycisk X KEYCODE_BUTTON_X
5. Przycisk A KEYCODE_BUTTON_A
6. Przycisk Y KEYCODE_BUTTON_Y
7. Przycisk B KEYCODE_BUTTON_B
8. Prawy bumper
KEYCODE_BUTTON_R1
9. Prawy spust
AXIS_RTRIGGER
10. Lewy spust AXIS_LTRIGGER
11. Lewy bumper KEYCODE_BUTTON_L1
12. Rozpocznij KEYCODE_BUTTON_START
13. Wybierz KEYCODE_BUTTON_SELECT

Obsługa naciśnięć przycisków

Android zgłasza naciśnięcia przycisków kontrolera w taki sam sposób jak naciśnięcia przycisków klawiatury, dlatego musisz:

  • Sprawdź, czy wydarzenie pochodzi z SOURCE_GAMEPAD.
  • Upewnij się, że przycisk jest odbierany tylko raz za pomocą KeyEvent.getRepeatCount(). Android będzie wysyłać powtarzające się zdarzenia klawiszy tak samo, jak w przypadku przytrzymania klawisza na klawiaturze.
  • Aby wskazać, że zdarzenie zostało obsłużone, zwróć wartość true.
  • Przekazuj nieobsłużone zdarzenia do super, aby sprawdzić, czy różne warstwy zgodności Androida działają prawidłowo.

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

Przetwarzanie danych wejściowych z pada kierunkowego

4-kierunkowy pad kierunkowy to popularny element sterujący w wielu kontrolerach do gier. Android zgłasza naciśnięcia przycisków D-pad GÓRA i DÓŁ jako zdarzenia AXIS_HAT_Y, przy czym wartość -1,0 oznacza naciśnięcie przycisku GÓRA, a wartość 1,0 oznacza naciśnięcie przycisku DÓŁ. Naciśnięcia lewego lub prawego przycisku pada kierunkowego są raportowane jako zdarzenia AXIS_HAT_X, przy czym wartość –1,0 oznacza lewy przycisk, a 1,0 – prawy.

Niektóre kontrolery zgłaszają naciśnięcia pada kierunkowego za pomocą kodu klawisza. Jeśli Twoja gra reaguje na naciśnięcia pada kierunkowego, traktuj zdarzenia osi kapelusza i kody klawiszy pada kierunkowego jako te same zdarzenia wejściowe, zgodnie z zaleceniami w tabeli 2.

Tabela 2. Zalecane domyślne działania w grze dla kodów klawiszy pada kierunkowego i wartości osi kapelusza.

Akcja w grze Kod klawisza pada kierunkowego Kod osi kapelusza
Przenieś w górę KEYCODE_DPAD_UP AXIS_HAT_Y (dla wartości od 0 do -1,0)
Przenieś w dół KEYCODE_DPAD_DOWN AXIS_HAT_Y (wartości od 0 do 1,0)
Przenieś w lewo KEYCODE_DPAD_LEFT AXIS_HAT_X (dla wartości od 0 do -1,0)
Przenieś w prawo KEYCODE_DPAD_RIGHT AXIS_HAT_X (wartości od 0 do 1,0)

Poniższy fragment kodu pokazuje klasę pomocniczą, która umożliwia sprawdzanie osi kapelusza i wartości kodu klucza ze zdarzenia wejściowego w celu określenia kierunku pada kierunkowego.

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

Tej klasy pomocniczej możesz używać w grze wszędzie tam, gdzie chcesz przetwarzać dane wejściowe z pada kierunkowego (np. w wywołaniach zwrotnych onGenericMotionEvent() lub onKeyDown()).

Na przykład:

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

Przetwarzanie ruchów joysticka

Gdy gracze przesuwają joystick na kontrolerach do gier, Android zgłasza MotionEvent, który zawiera kod ACTION_MOVE działania i zaktualizowane położenia osi joysticka. Twoja gra może używać danych dostarczanych przez interfejs MotionEvent, aby określić, czy nastąpił ruch joysticka, który ją interesuje.

Pamiętaj, że zdarzenia ruchu joysticka mogą łączyć wiele próbek ruchu w jednym obiekcie. Obiekt MotionEvent zawiera bieżące położenie każdej osi joysticka, a także wiele historycznych położeń każdej osi. Podczas raportowania zdarzeń ruchu z kodem działaniaACTION_MOVE (np. ruchów joysticka) Android grupuje wartości osi w celu zwiększenia wydajności. Wartości historyczne osi to zbiór różnych wartości starszych niż bieżąca wartość osi i nowszych niż wartości zgłoszone w poprzednich zdarzeniach ruchu. Więcej informacji znajdziesz w MotionEvent.

Aby dokładnie renderować ruch obiektu w grze na podstawie danych wejściowych z joysticka, możesz użyć informacji historycznych dostarczanych przez obiekty MotionEvent.

Aktualne i historyczne wartości możesz pobrać za pomocą tych metod:

Poniższy fragment kodu pokazuje, jak zastąpić wywołanie zwrotne onGenericMotionEvent() w celu przetworzenia danych wejściowych z joysticka. Najpierw przetwórz wartości historyczne osi, a potem jej bieżące położenie.

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

Zanim zaczniesz używać joysticka, musisz sprawdzić, czy jest wyśrodkowany, a potem odpowiednio obliczyć ruchy osi. Joysticki mają zwykle płaski obszar, czyli zakres wartości w pobliżu współrzędnej (0, 0), w którym oś jest uważana za wyśrodkowaną. Jeśli wartość osi zgłoszona przez Androida mieści się w płaskim obszarze, należy uznać, że kontroler jest w spoczynku (czyli nieruchomy wzdłuż obu osi).

Fragment kodu pokazuje metodę pomocniczą, która oblicza ruch wzdłuż każdej osi. Wywołujesz tę funkcję pomocniczą w metodzie processJoystickInput() opisanej w dalszej części tego przykładu:

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

Łącząc to wszystko w całość, oto jak możesz przetwarzać ruchy joysticka w swojej grze:

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
}

Aby obsługiwać kontrolery do gier z bardziej zaawansowanymi funkcjami niż pojedynczy joystick, postępuj zgodnie z tymi sprawdzonymi metodami:

  • Obsługuj dwa drążki kontrolera. Wiele kontrolerów do gier ma zarówno lewy, jak i prawy joystick. W przypadku lewego drążka Android zgłasza ruchy poziome jako zdarzenia AXIS_X, a ruchy pionowe jako zdarzenia AXIS_Y. W przypadku prawego drążka Android zgłasza ruchy poziome jako zdarzenia AXIS_Z, a ruchy pionowe jako zdarzenia AXIS_RZ. Pamiętaj, aby w kodzie obsługiwać oba drążki kontrolera.
  • Obsługuj naciśnięcia przycisków na ramionach (i upewnij się, że Twoja gra działa ze zdarzeniami AXIS_KEYCODE_BUTTON_). Niektóre kontrolery mają lewy i prawy przycisk na górze. Gdy te wyzwalacze są obecne, emitują zdarzenie AXIS_*TRIGGER lub KEYCODE_BUTTON_*2 albo oba te zdarzenia. W przypadku lewego spustu będą to AXIS_LTRIGGERKEYCODE_BUTTON_L2. W przypadku prawego spustu będą to:AXIS_RTRIGGERKEYCODE_BUTTON_R2. Zdarzenia osi występują tylko wtedy, gdy wyzwalacz emituje zakres wartości od 0 do 1, a niektóre kontrolery z wyjściem analogowym emitują zdarzenia przycisków oprócz zdarzeń osi. Aby zachować zgodność ze wszystkimi popularnymi kontrolerami do gier, gry muszą obsługiwać zdarzenia AXIS_KEYCODE_BUTTON_. Jeśli kontroler zgłasza oba te zdarzenia, wybierz to, które najlepiej pasuje do rozgrywki. Na Androidzie 4.3 (poziom interfejsu API 18) i nowszych wersjach kontroler, który generuje wartość AXIS_LTRIGGER dla osi AXIS_BRAKE, zgłasza identyczną wartość. To samo dotyczy znaków AXIS_RTRIGGERAXIS_GAS. Android zgłasza wszystkie naciśnięcia analogowych przycisków z znormalizowaną wartością od 0,0 (zwolniony) do 1,0 (w pełni naciśnięty).
  • Konkretne zachowania i obsługa mogą się różnić w środowiskach emulowanych. Emulowane platformy, takie jak Gry Play na Google, mogą się nieznacznie różnić w działaniu w zależności od możliwości systemu operacyjnego hosta. Na przykład niektóre kontrolery, które emitują zdarzenia AXIS_KEYCODE_BUTTON_, emitują tylko zdarzenia AXIS_, a obsługa niektórych kontrolerów może w ogóle nie być dostępna.

Typowe warianty

Android obsługuje wiele różnych kontrolerów, więc może nie być jasne, jak tworzyć i testować grę, aby mieć pewność, że działa bez błędów u wszystkich graczy. Okazuje się, że pomimo tej pozornej różnorodności producenci kontrolerów na całym świecie zwykle trzymają się 3 różnych stylów. Niektóre z nich mają przełączniki sprzętowe, które umożliwiają przełączanie się między co najmniej 2 z tych trybów.

Oznacza to, że możesz przeprowadzać testy na zaledwie 3 kontrolerach w zespole deweloperskim i mieć pewność, że w Twoją grę można grać bez konieczności korzystania z list dozwolonych i zabronionych.

Typowe rodzaje kontrolerów

Najpopularniejsze kontrolery mają układ podobny do tych z popularnych konsol do gier. Dotyczy to zarówno estetyki etykiet i układu przycisków, jak i funkcjonalności, czyli tego, jakie zdarzenia są wywoływane. Kontrolery z przełącznikami sprzętowymi między różnymi typami konsol będą zmieniać wysyłane zdarzenia, a często nawet logiczny układ przycisków.

Podczas testowania zalecamy sprawdzenie, czy gra działa z jednym kontrolerem z każdej kategorii. Możesz przeprowadzić testy z użyciem kontrolerów własnych lub popularnych kontrolerów innych firm. Zazwyczaj staramy się przypisać najpopularniejsze kontrolery do definicji powyżej.

Typ kontrolera Różnice w zachowaniu Różne etykiety
Kontrolery w stylu Xbox

Są to kontrolery zwykle przeznaczone na platformy Microsoft Xbox i Windows*.

Te kontrolery są zgodne z zestawem funkcji opisanym w sekcji Przetwarzanie danych wejściowych kontrolera. Przyciski L2/R2 na tych kontrolerach są oznaczone jako LT/RT.
Kontrolery w stylu Switcha

Te kontrolery są zwykle przeznaczone do konsol z rodziny Nintendo Switch*.

Te kontrolery wysyłają KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent Przyciski L2/R2 na tych kontrolerach są oznaczone jako ZL/ZR.

Na tych kontrolerach przyciski AB oraz XY są zamienione miejscami, więc KEYCODE_BUTTON_A to przycisk oznaczony jako B i odwrotnie.

Kontrolery w stylu PlayStation

Zwykle są one przeznaczone do konsol z rodziny Sony PlayStation*.

Te kontrolery wysyłają MotionEvent, np. kontrolery w stylu Xboxa, ale też KeyEvent, np. kontrolery w stylu Switcha, gdy są w pełni wciśnięte. Te kontrolery mają inny zestaw symboli na przyciskach na górze.

* Microsoft, Xbox i Windows są zastrzeżonymi znakami towarowymi firmy Microsoft; Nintendo Switch jest zastrzeżonym znakiem towarowym firmy Nintendo of America Inc.; PlayStation jest zastrzeżonym znakiem towarowym firmy Sony Interactive Entertainment Inc.

Rozróżnianie przycisków aktywujących

Niektóre kontrolery wysyłają zdarzenia AXIS_LTRIGGERAXIS_RTRIGGER, inne – KEYCODE_BUTTON_L2KEYCODE_BUTTON_R2, a jeszcze inne – wszystkie te zdarzenia w zależności od możliwości sprzętowych. Aby zmaksymalizować zgodność, obsługuj wszystkie te zdarzenia.

Wszystkie kontrolery, które wysyłają AXIS_LTRIGGER, będą też wysyłać AXIS_BRAKE, podobnie jak AXIS_RTRIGGERAXIS_GAS, aby zmaksymalizować zgodność między kierownicami wyścigowymi a typowymi kontrolerami do gier. Zwykle nie powoduje to problemów, ale należy pamiętać o tym w przypadku funkcji takich jak ekrany ponownego mapowania klawiszy.

Wyzwalacz MotionEvent KeyEvent
Lewy spust AXIS_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
Prawy spust AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

Aby zachować zgodność z jak największą liczbą kontrolerów, należy sprawdzić, czy gra obsługuje zarówno KeyEvent, jak i MotionEvent, oraz czy zdarzenia nie są duplikowane.

Obsługiwane kontrolery

Podczas testowania zalecamy sprawdzenie, czy gra działa z jednym kontrolerem z każdej kategorii.

  • Styl Xbox
  • Nintendo Switch Style
  • Styl PlayStation

Możesz testować kontrolery własne lub popularne kontrolery innych producentów. Zazwyczaj mapujemy najpopularniejsze kontrolery do definicji tak dokładnie, jak to możliwe.