Obsługuj działania kontrolera

Na poziomie systemu Android raportuje kody zdarzeń wejściowych z kontrolerów gier jako kody kluczy i wartości osi. Kody i wartości możesz otrzymać w grze, a potem przekształcić je w konkretne działania w grze.

Gdy gracz fizycznie połączy się z urządzeniem z Androidem lub sparuje go bezprzewodowo, system automatycznie wykryje kontroler jako urządzenie wejściowe i rozpocznie raportowanie jego zdarzeń wejściowych. Twoja gra może otrzymywać te zdarzenia wejściowe po zaimplementowaniu tych metod wywołania zwrotnego w aktywnym elemencie Activity lub View (zaimplementuj wywołania zwrotne dla Activity lub View, ale nie w obu tych przypadkach):

Zalecana metoda to przechwytywanie zdarzeń z konkretnego obiektu View, z którym użytkownik wchodzi w interakcję. Aby uzyskać informacje o typie odebranego zdarzenia wejściowego, sprawdź te obiekty udostępniane przez wywołania zwrotne:

KeyEvent
Obiekt opisujący zdarzenia pada kierunkowego (D-pada) i przycisku do gier. Kluczowym zdarzeniom towarzyszy kod klucza, który wskazuje określony przycisk, np. DPAD_DOWN lub BUTTON_A. Kod klucza można uzyskać, wywołując metodę getKeyCode() lub za pomocą wywołań zwrotnych kluczowych zdarzeń, takich jak onKeyDown().
MotionEvent
Obiekt opisujący dane wejściowe joysticka i ruchu ramienia. Zdarzeniom ruchu towarzyszy kod działania i zestaw wartości osi. Kod działania określa zachodzącą zmianę stanu, np. przesuwający się joystick. Wartości osi opisują położenie i inne właściwości ruchu związane z określonymi elementami sterującymi, np. AXIS_X lub AXIS_RTRIGGER. Kod działania możesz uzyskać, wywołując getAction() i wartość osi, wywołując getAxisValue().

Ta lekcja pokazuje, jak obsługiwać na ekranie gry najpopularniejsze fizyczne elementy sterujące (przyciski do gier, pady kierunkowe i joysticki) przez wdrożenie wspomnianych wyżej metod wywołań zwrotnych View oraz przetwarzania obiektów KeyEvent i MotionEvent.

Sprawdzanie, czy kontroler do gier jest podłączony

Podczas raportowania zdarzeń wejściowych Android nie rozróżnia zdarzeń z kontrolera innego niż kontroler do gier. Na przykład działanie ekranu dotykowego generuje zdarzenie AXIS_X reprezentujące współrzędną X powierzchni dotykowej, ale joystick generuje zdarzenie AXIS_X reprezentujące pozycję X joysticka. Jeśli Twoja gra wymaga obsługi danych wejściowych kontrolera gry, najpierw sprawdź, czy zdarzenie wejściowe pochodzi z odpowiedniego typu źródła.

Aby sprawdzić, czy podłączone urządzenie wejściowe jest kontrolerem gier, wywołaj getSources() w celu uzyskania połączonego pola bitowego typów źródeł wejściowych obsługiwanych przez to urządzenie. Następnie możesz sprawdzić, czy ustawione są te pola:

  • Typ źródła SOURCE_GAMEPAD oznacza, że urządzenie wejściowe ma przyciski pada do gier (np. BUTTON_A). Ten typ źródła nie wskazuje wyraźnie, czy kontroler gier ma przyciski pada kierunkowego, ale większość padów do gier zwykle ma sterowanie kierunkowe.
  • Typ źródła SOURCE_DPAD oznacza, że urządzenie wejściowe ma przyciski pada kierunkowego (np. DPAD_UP).
  • Typ źródła SOURCE_JOYSTICK wskazuje, że urządzenie wejściowe ma analogowe gałki analogowe (np. joystick, który rejestruje ruchy wzdłuż AXIS_X i AXIS_Y).

Poniższy fragment kodu pokazuje metodę pomocniczą, która pozwala sprawdzić, czy podłączone urządzenia wejściowe to kontrolery gier. W takim przypadku metoda pobiera identyfikatory urządzeń kontrolerów gier. Potem możesz powiązać każdy identyfikator urządzenia z graczem w grze i przetwarzać akcje w grze dla każdego połączonego gracza z osobna. Więcej informacji o obsłudze wielu kontrolerów do gier, które są jednocześnie połączone z tym samym urządzeniem z Androidem, znajdziesz w artykule Obsługa wielu 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 (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
                    || sources and InputDevice.SOURCE_JOYSTICK == InputDevice.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);
        int sources = dev.getSources();

        // Verify that the device has gamepad buttons, control sticks, or both.
        if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
                || ((sources & InputDevice.SOURCE_JOYSTICK)
                == InputDevice.SOURCE_JOYSTICK)) {
            // This device is a game controller. Store its device ID.
            if (!gameControllerDeviceIds.contains(deviceId)) {
                gameControllerDeviceIds.add(deviceId);
            }
        }
    }
    return gameControllerDeviceIds;
}

Możesz też sprawdzić, czy poszczególne funkcje wejściowe obsługiwane przez podłączony kontroler gier. Może to być przydatne np. wtedy, gdy chcesz, aby gra używała tylko danych wejściowych z rozpoznawanego przez siebie zestawu fizycznych elementów sterujących.

Aby wykryć, czy połączony kontroler gier obsługuje konkretny kod klucza lub osi, skorzystaj z tych metod:

  • W Androidzie 4.4 (poziom interfejsu API 19) lub nowszym możesz sprawdzić, czy połączony kontroler gier obsługuje kod klucza, wywołując funkcję hasKeys(int...).
  • W Androidzie 3.1 (poziom interfejsu API 12) lub nowszym możesz znaleźć wszystkie dostępne osie obsługiwane przez połączony kontroler do gier, najpierw wywołując funkcję getMotionRanges(). Następnie dla każdego zwróconego obiektu InputDevice.MotionRange wywołaj getAxis(), aby uzyskać identyfikator osi.

Przetwarzanie naciśnięć przycisku na padzie do gier

Rysunek 1 pokazuje, jak Android mapuje kody kluczy i wartości osi na elementy sterujące większością kontrolerów do gier.

Rysunek 1. Profil ogólnego kontrolera do gier.

Znaczenie objaśnień na ilustracji:

Typowe kody klawiszy generowane po naciśnięciu przycisku na padzie do gier to BUTTON_A, BUTTON_B, BUTTON_SELECT i BUTTON_START. Niektóre kontrolery do gier uruchamiają też kod klawisza DPAD_CENTER po naciśnięciu środkowej paska poziomego na padzie kierunkowym. Gra może sprawdzać kod klucza, wywołując funkcję getKeyCode() lub za pomocą wywołań zwrotnych kluczowych zdarzeń, np. onKeyDown(). Jeśli przedstawia zdarzenie powiązane z Twoją grą, przetwarza je jako działanie w grze. Tabela 1 zawiera listę zalecanych działań w grze w przypadku najpopularniejszych przycisków pada do gier.

Tabela 1. Zalecane działania w grze dla przycisków pada do gier.

Akcja w grze Kod przycisku
Uruchom grę w menu głównym albo wstrzymaj lub wznów działanie w jej trakcie BUTTON_START*
Wyświetl menu BUTTON_SELECT* i KEYCODE_MENU*
Takie same jak w przypadku nawigacji Wstecz w Androidzie opisanej w przewodniku po projektowaniu Nawigacji. KEYCODE_BACK
Powrót do poprzedniego elementu w menu BUTTON_B
Potwierdź wybór lub wykonaj główną akcję w grze BUTTON_A i DPAD_CENTER

* Gra nie powinna korzystać z przycisków Start, Wybierz i Menu.

Wskazówka: zastanów się nad udostępnieniem w grze ekranu konfiguracji, aby użytkownicy mogli spersonalizować własne mapowania kontrolerów gier na potrzeby działań w grze.

Ten fragment kodu pokazuje, jak możesz zastąpić onKeyDown(), by powiązać naciśnięcia przycisku BUTTON_A i DPAD_CENTER z działaniem w grze.

Kotlin

class GameView(...) : View(...) {
    ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        var handled = false
        if (event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD) {
            if (event.repeatCount == 0) {
                when (keyCode) {
                    // Handle gamepad and D-pad button presses to navigate the ship
                    ...

                    else -> {
                        keyCode.takeIf { isFireKey(it) }?.run {
                            // Update the ship object to fire lasers
                            ...
                            handled = true
                        }
                    }
                }
            }
            if (handled) {
                return true
            }
        }
        return super.onKeyDown(keyCode, event)
    }

    // Here we treat Button_A and DPAD_CENTER as the primary action
    // keys for the game.
    private fun isFireKey(keyCode: Int): Boolean =
            keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_BUTTON_A
}

Java

public class GameView extends View {
    ...

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    // Handle gamepad and D-pad button presses to
                    // navigate the ship
                    ...

                    default:
                         if (isFireKey(keyCode)) {
                             // Update the ship object to fire lasers
                             ...
                             handled = true;
                         }
                     break;
                }
            }
            if (handled) {
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private static boolean isFireKey(int keyCode) {
        // Here we treat Button_A and DPAD_CENTER as the primary action
        // keys for the game.
        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                || keyCode == KeyEvent.KEYCODE_BUTTON_A;
    }
}

Uwaga: w Androidzie 4.2 (poziom interfejsu API 17) i starszych system domyślnie traktuje BUTTON_A jako klucz Wstecz. Jeśli Twoja aplikacja obsługuje te wersje Androida, pamiętaj, aby traktować BUTTON_A jako główną akcję w grze. Aby określić aktualną wersję pakietu Android SDK na urządzeniu, sprawdź wartość Build.VERSION.SDK_INT.

Przetwarzaj dane z pada kierunkowego

Czterokierunkowy pad kierunkowy (D-pad) to typowy element sterujący w wielu kontrolerach gier. Android zgłasza naciśnięcia przycisków W GÓRĘ i W DÓŁ jako zdarzenia AXIS_HAT_Y w zakresie od -1,0 (w górę) do 1,0 (w dół), a naciśnięcia W LEWO i W PRAWO na padzie kierunkowym jako zdarzenia AXIS_HAT_X w zakresie od -1,0 (w lewo) do 1,0 (w prawo).

Niektóre kontrolery zamiast tego zgłaszają naciśnięcia na padzie kierunkowym z kodem klawisza. Jeśli w grze chodzi o naciśnięcia przycisków pada kierunkowego, zdarzenia związane z osią kapelusza i kody klawiszy pada kierunkowego należy traktować 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 kapeluszy.

Akcja w grze Kod klawisza pada kierunkowego Kod osi kapelusza
W górę KEYCODE_DPAD_UP AXIS_HAT_Y (dla wartości od 0 do -1,0)
W dół KEYCODE_DPAD_DOWN AXIS_HAT_Y (dla 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 (dla wartości od 0 do 1,0)

Poniższy fragment kodu pokazuje klasę pomocniczą, która pozwala sprawdzić oś kapelusza i wartości kodu klucza ze zdarzenia wejściowego, aby określić kierunek 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.
            event.source and InputDevice.SOURCE_DPAD != 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.
        if ((event.getSource() & InputDevice.SOURCE_DPAD)
             != InputDevice.SOURCE_DPAD) {
             return true;
         } else {
             return false;
         }
     }
}

Tej klasy pomocniczej możesz używać w grze wszędzie tam, gdzie chcesz przetwarzać dane wejściowe na padzie kierunkowym (np. w wywołaniach 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.
    ...
}

Przetwarzaj ruchy joysticka

Gdy gracz przesuwa joystickiem na kontrolerze do gier, Android zgłasza MotionEvent zawierający kod akcji ACTION_MOVE i zaktualizowane pozycje osi joysticka. Gra może wykorzystać dane dostarczone przez MotionEvent, aby określić, czy wystąpił oczekiwany ruch joysticka.

Pamiętaj, że zdarzenia ruchu joysticka mogą zgrupować wiele próbek ruchu w jednym obiekcie. Obiekt MotionEvent zawiera bieżące położenie każdej osi joysticka oraz wiele pozycji historycznych dla każdej osi. Podczas raportowania zdarzeń ruchu z kodem działania ACTION_MOVE (np. ruchy joysticka) Android grupuje wartości osi, aby zwiększyć wydajność. Historyczne wartości osi składa się z zestawu unikalnych wartości, które są starsze niż obecna wartość osi i nowszych od wartości zgłoszonych w poprzednich zdarzeniach ruchu. Więcej informacji znajdziesz w dokumentacji MotionEvent.

Możesz wykorzystać dane historyczne, aby precyzyjniej renderować ruch obiektu gry na podstawie danych wejściowych joysticka. Aby pobrać wartości bieżące i historyczne, wywołaj metodę getAxisValue() lub getHistoricalAxisValue(). Liczbę punktów historycznych w zdarzeniu joysticka możesz też sprawdzić, wywołując funkcję getHistorySize().

Poniższy fragment kodu pokazuje, jak możesz zastąpić wywołanie zwrotne onGenericMotionEvent() w celu przetworzenia danych wejściowych joysticka. Najpierw przetwórz wartości historyczne osi, a następnie przetwórz jej bieżącą pozycję.

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

Przed użyciem danych joysticka należy ustalić, czy joystick jest wyśrodkowany, a następnie odpowiednio obliczyć ruchy jego osi. Joysticki mają zazwyczaj obszar płaski, czyli zakres wartości w pobliżu współrzędnych (0, 0), w których oś jest uznawana za wyśrodkowaną. Jeśli wartość osi raportowana przez Androida mieści się w płaskim obszarze, należy traktować kontroler jako w spoczynku (czyli nieruchomy wzdłuż obu osi).

We fragmencie kodu poniżej widać metodę pomocniczą, która oblicza ruch wzdłuż każdej osi. Wywołujesz go za pomocą metody processJoystickInput() opisanej poniżej.

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

Po połączeniu wszystkich elementów w grę możesz przetwarzać ruchy joystickiem:

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, które mają bardziej zaawansowane funkcje poza jednym joystickiem, postępuj zgodnie z tymi sprawdzonymi metodami:

  • Obsługuj 2 gałki do kontrolera. Wiele kontrolerów do gier ma lewy i prawy joystick. W przypadku lewej gałki Android zgłasza ruchy poziome jako zdarzenia AXIS_X, a ruchy w pionie jako zdarzenia AXIS_Y. W przypadku prawej gałki Android ruchy w poziomie są rejestrowane jako zdarzenia AXIS_Z, a ruchy w pionie jako zdarzenia AXIS_RZ. Pamiętaj, aby umieścić w kodzie oba gałki kontrolera.
  • Obsługuj naciśnięcia aktywatorów barkowych (ale podaj alternatywne metody wprowadzania). Niektóre kontrolery mają spusty po lewej i prawej stronie. Jeśli występują te reguły, Android raportuje naciśnięcie lewej reguły jako zdarzenie AXIS_LTRIGGER, a naciśnięcie prawego aktywatora jako zdarzenie AXIS_RTRIGGER. W Androidzie 4.3 (poziom interfejsu API 18) kontroler, który generuje AXIS_LTRIGGER, zgłasza też identyczną wartość osi AXIS_BRAKE. To samo dotyczy AXIS_RTRIGGER i AXIS_GAS. Android raportuje wszystkie analogowe naciśnięcia aktywatora o znormalizowanej wartości od 0,0 (zwolniono) do 1,0 (do pełnego naciśnięcia). Nie wszystkie kontrolery mają wyzwalacze, więc rozważ umożliwienie graczom wykonywania tych działań w grze za pomocą innych przycisków.