Obsługuj działania kontrolera

Na poziomie systemu Android zgłasza kody zdarzeń wejściowych z kontrolerów gier jako kody klawiszy i wartości osi Androida. Kody i wartości w grze możesz przekształcić w określone działania.

Gdy gracze podłączą kontroler do swoich urządzeń z Androidem fizycznie lub bezprzewodowo, system automatycznie wykryje kontroler jako urządzenie wejściowe i zacznie zgłaszać zdarzenia związane z wejściami. Gra może odbierać te zdarzenia wprowadzania, wdrażając w aktywnej Activity lub skoncentrowanej View te metody wywołania (powinnaś zaimplementować wywołania Activity lub View, ale nie obydwa):

Zalecane podejście polega na rejestrowaniu zdarzeń z konkretnego obiektu View, z którym użytkownik wchodzi w interakcję. Aby uzyskać informacje o typie otrzymanego zdarzenia wejściowego, sprawdź te obiekty podane przez wywołania zwrotne:

KeyEvent
Obiekt opisujący zdarzenia przycisków kierunkowych (D-pad) i przycisków kontrolera. Kluczowe zdarzenia są opatrzone kodem klucza, który wskazuje konkretny przycisk, np. DPAD_DOWNlub BUTTON_A. Aby uzyskać kod klucza, wywołaj funkcję getKeyCode() lub użyj wywołań zwrotnych kluczowych zdarzeń, takich jak onKeyDown().
MotionEvent
Obiekt opisujący dane wejściowe przekazywane za pomocą joysticka i ruchu spustu po bokach. Zdarzenia ruchu są opatrzone kodem działania i zestawem wartości osi. Kod działania określa stan, który wystąpił, np. ruch joysticka. Wartości na osiach opisują położenie i inne właściwości ruchu w przypadku konkretnego elementu sterującego fizycznego, np. AXIS_X lub AXIS_RTRIGGER. Kod działania możesz uzyskać, wywołując funkcję getAction(), a wartość osi – wywołując funkcję getAxisValue().

W tej lekcji skupiamy się na tym, jak obsługiwać dane wejściowe z najczęstszych typów elementów sterujących (przycisków pada, padów kierunkowych i joysticków) na ekranie gry. Aby to zrobić, należy zaimplementować wspomniane metody wywołania zwrotnego View oraz przetworzyć obiekty KeyEventMotionEvent.

Sprawdź, czy kontroler 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 na ekranie dotykowym generuje zdarzenie AXIS_X, które reprezentuje współrzędną X powierzchni dotykowej, a joystick – zdarzenie AXIS_X reprezentujące pozycję X joysticka. Jeśli Twoja gra obsługuje dane wejściowe z kontrolera, najpierw sprawdź, czy dane te pochodzą z odpowiedniego typu źródła.

Aby sprawdzić, czy połączone urządzenie wejściowe jest kontrolerem do gier, wywołaj funkcję getSources(), aby uzyskać połączone pole bitowe typów źródeł danych obsługiwanych przez to urządzenie. Następnie możesz sprawdzić, czy te pola są prawidłowo skonfigurowane:

  • Typ źródła SOURCE_GAMEPAD oznacza, że urządzenie wejściowe ma przyciski pada do gier (np. BUTTON_A). Pamiętaj, że ten typ źródła nie wskazuje ściśle, czy kontroler ma przyciski pada kierunkowego, ale większość padów do gier ma elementy sterujące kierunkowe.
  • Typ źródła SOURCE_DPAD oznacza, że urządzenie wejściowe ma przyciski na padach kierunkowych (np. DPAD_UP).
  • Typ źródła SOURCE_JOYSTICK wskazuje, że urządzenie wejściowe ma analogowe elementy sterujące (np. joystick, który rejestruje ruchy wzdłuż osi AXIS_XAXIS_Y).

Poniższy fragment kodu pokazuje metodę pomocniczą, która umożliwia sprawdzenie, czy połączone urządzenia wejściowe są kontrolerami gier. Jeśli tak, metoda pobiera identyfikatory urządzeń kontrolerów gier. Następnie możesz powiązać identyfikator każdego urządzenia z graczem w grze i przetwarzać działania w grze osobno dla każdego połączonego gracza. Więcej informacji o obsługiwaniu wielu kontrolerów do gier połączonych jednocześnie na tym samym urządzeniu 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 podłączony kontroler do gier obsługuje indywidualne funkcje wejściowe. Może to być przydatne, jeśli chcesz, aby gra używała tylko danych wejściowych z zestawu obsługiwanych przez nią elementów sterujących.

Aby wykryć, czy podłączony kontroler do gier obsługuje określony kod klawisza lub oś, użyj tych metod:

  • W Androidzie 4.4 (poziom interfejsu API 19) lub nowszym możesz sprawdzić, czy kod klawisza jest obsługiwany przez podłączony kontroler do gier, 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 podłączony kontroler do gier, wywołując najpierw funkcję getMotionRanges(). Następnie po każdym zwróconym obiekcie InputDevice.MotionRange wywołaj polecenie getAxis(), aby uzyskać identyfikator jego osi.

Przetwarzanie naciśnięć przycisków kontrolera

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

Rysunek 1. Profil ogólnego kontrolera do gier.

Objaśnienia na ilustracji odnoszą się do tych informacji:

Typowe kody klawiszy generowane przez naciśnięcia przycisków kontrolera to BUTTON_A, BUTTON_B, BUTTON_SELECTBUTTON_START. Niektóre kontrolery uruchamiają kod klawisza DPAD_CENTER, gdy naciśniesz środkową część krzyżaka. Gra może sprawdzać kod klucza, wywołując funkcję getKeyCode() lub używając wywołań zwrotnych kluczowego zdarzenia, np. onKeyDown(). Jeśli gra reprezentuje zdarzenie związane z Twoją grą, przetwórz ją jako działanie w grze. Tabela 1 zawiera zalecane działania w przypadku najpopularniejszych przycisków padów do gier.

Tabela 1. Zalecane działania w grze przypisane do przycisków kontrolera

Działanie w grze Kod klawisza przycisku
Uruchomić grę w menu głównym lub wstrzymać/wznowić grę BUTTON_START*
Wyświetl menu BUTTON_SELECT* i KEYCODE_MENU*
To samo zachowanie, co w przypadku przycisku Wstecz na Androidzie, opisane w przewodniku po projektowaniu nawigacji. KEYCODE_BACK
Wróć do poprzedniego elementu w menu BUTTON_B
potwierdzić wybór lub wykonać główne działanie w grze; BUTTON_A i DPAD_CENTER

* Gra nie powinna wymagać obecności przycisków Start, Select ani Menu.

Wskazówka: rozważ dodanie w grze ekranu konfiguracji, aby użytkownicy mogli spersonalizować ich mapy kontrolera do działań w grze.

Ten fragment kodu pokazuje, jak możesz zastąpić funkcję onKeyDown(), aby powiązać naciśnięcia przycisków BUTTON_ADPAD_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 klawisz Androida Wstecz. Jeśli Twoja aplikacja obsługuje te wersje Androida, pamiętaj, aby traktować BUTTON_A jako główne działanie gry. Aby określić bieżącą wersję pakietu Android SDK na urządzeniu, sprawdź wartość Build.VERSION.SDK_INT.

Przetwarzanie danych z pada kierunkowego

4-kierunkowy panel kierunkowy (D-pad) to powszechny element sterujący w wielu kontrolerach do gier. Android zgłasza naciśnięcia przycisku w górę i w dół jako zdarzenia AXIS_HAT_Y o zakresie od -1,0 (góra) do 1,0 (dół), a naciśnięcia przycisku w lewo lub w prawo jako zdarzenia AXIS_HAT_X o zakresie od -1,0 (lewo) do 1,0 (prawo).

Niektóre kontrolery zamiast tego raportują naciśnięcia przycisków D-pad za pomocą kodu klawisza. Jeśli w Twojej grze chodzi o naciśnięcia pada kierunkowego, zdarzenia związane z osią kapelusza i kody klawiszy na padzie kierunkowym należy traktować jak te same zdarzenia wejściowe, co zaleca się w tabeli 2.

Tabela 2. Zalecane domyślne działania w grze dotyczące kodów przycisków D-pad i wartości osi czapki.

Działanie w grze Kod klawisza pada kierunkowego Hat Axis Code
W górę KEYCODE_DPAD_UP AXIS_HAT_Y (wartości od 0 do –1,0)
W dół KEYCODE_DPAD_DOWN AXIS_HAT_Y (wartości od 0 do 1,0)
Przenieś w lewo KEYCODE_DPAD_LEFT AXIS_HAT_X (wartości od 0 do –1,0)
Przenieś w prawo KEYCODE_DPAD_RIGHT AXIS_HAT_X (dla wartości od 0 do 1,0)

Ten fragment kodu zawiera klasę pomocniczą, która pozwala sprawdzić oś 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.
            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;
         }
     }
}

Możesz używać tej klasy pomocniczej w swojej grze wszędzie tam, gdzie chcesz przetwarzać dane z krzyżownicy (na przykład w funkcjach 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 gracz porusza joystickiem na kontrolerach do gier, Android przesyła zgłoszenie MotionEvent, które zawiera kod działania ACTION_MOVE i zaktualizowane pozycje osi joysticka. Gra może używać danych udostępnianych przez MotionEvent, aby określić, czy nastąpiło zdarzenie, na którym jej zależy.

Pamiętaj, że zdarzenia ruchu steru mogą zawierać wiele próbek ruchu w ramach jednego obiektu. Obiekt MotionEvent zawiera bieżącą pozycję każdej osi joysticka oraz wiele historycznych pozycji każdej osi. Gdy raportujesz zdarzenia ruchu z kodem działania ACTION_MOVE (np. ruchy joysticka), Android grupowo zapisuje 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 materiałach referencyjnych MotionEvent.

Korzystając z danych historycznych, możesz dokładniej renderować ruch obiektu w grze na podstawie danych z joysticka. Aby pobrać bieżące i historyczne wartości, wywołaj funkcję getAxisValue() lub getHistoricalAxisValue(). Liczba historycznych punktów jest też widoczna w zdarzeniu joysticka po wywołaniu funkcji getHistorySize().

Poniższy fragment kodu pokazuje, jak zastąpić wywołanie zwrotne onGenericMotionEvent(), aby przetworzyć dane wejściowe joysticka. Najpierw przetwórz historyczne wartości osi, a potem 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);
    }
}

Zanim użyjesz sterowania za pomocą joysticka, musisz określić, czy joystick jest wyśrodkowany, a następnie odpowiednio obliczyć ruchy jego osi. W przypadku joysticków zazwyczaj występuje płaski obszar, czyli zakres wartości zbliżonych do współrzędnych (0, 0), w których oś jest uważana za wyśrodkowaną. Jeśli wartość osi zgłaszanej przez Androida znajduje się w płaskim obszarze, ustaw kontroler w stanie spoczynku (czyli nie może się przemieszczać wzdłuż obu osi).

Fragment kodu poniżej zawiera metodę pomocniczą, która oblicza ruch wzdłuż każdej osi. Ten element pomocniczy wywołujesz w metodzie 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 widać, jak można przetwarzać ruchy joysticka w 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, które mają bardziej zaawansowane funkcje niż pojedynczy joystick, postępuj zgodnie z tymi sprawdzonymi metodami:

  • Obsługa 2 joysticków kontrolera. Wiele kontrolerów do gier ma zarówno lewy, jak i prawy joystick. W przypadku lewego drążka Android raportuje 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ługa naciśnięć bocznych przycisków spustowych (i upewnij się, że gra działa z wykorzystaniem zdarzeń AXIS_KEYCODE_BUTTON_). Niektóre kontrolery mają spusty po lewej i prawej krawędzi. Gdy te wyzwalacze są obecne, emitują zdarzenie AXIS_*TRIGGER lub KEYCODE_BUTTON_*2 albo oba te zdarzenia. W przypadku lewego spustu są to AXIS_LTRIGGERKEYCODE_BUTTON_L2. Prawidłowym aktywatorem jest AXIS_RTRIGGERKEYCODE_BUTTON_R2. Zdarzenia związane z osią występują tylko wtedy, gdy aktywator wysyła zakres wartości od 0 do 1, a niektóre kontrolery z wyjściem analogowym generują zdarzenia związane z przyciskiem emisji, a także wtedy, gdy zdarzenia osi Aby zachować zgodność ze wszystkimi popularnymi kontrolerami gier, gry muszą obsługiwać zdarzenia AXIS_KEYCODE_BUTTON_, ale powinny preferować to zdarzenie, które ma największe znaczenie dla rozgrywki, jeśli kontroler zgłasza oba zdarzenia. W Androidzie 4.3 (poziom interfejsu API 18) i nowszych kontroler, który generuje AXIS_LTRIGGER, przekazuje identyczną wartość na osi AXIS_BRAKE. To samo dotyczy elementów AXIS_RTRIGGER i AXIS_GAS. Android raportuje wszystkie naciśnięcia analogowych przełączników z normalizowaną wartością od 0,0 (zwolnienie) do 1,0 (pełne naciśnięcie).
  • Obejmuje to konkretne zachowania i obsługę w środowiskach emulowanych. Emulowane platformy, takie jak Gry Google Play, mogą się nieznacznie różnić pod względem działania 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 w przypadku niektórych innych kontrolerów obsługa może być całkowicie wyłączona.