Controlleraktionen verarbeiten

Auf Systemebene meldet Android Eingabeereigniscodes von Gamecontrollern als Android-Schlüsselcodes und Achsenwerte. In Ihrem Spiel können Sie diese Codes und Werte empfangen und in bestimmte In-Game-Aktionen umwandeln.

Wenn Spieler einen Gamecontroller physisch mit ihrem Android-Gerät verbinden oder kabellos koppeln, erkennt das System den Controller automatisch als Eingabegerät und beginnt, seine Eingabeereignisse zu melden. Ihr Spiel kann diese Eingabeereignisse empfangen, indem Sie die folgenden Rückrufmethoden in Ihrer aktiven Activity oder fokussierten View implementieren. Sie sollten die Rückrufe entweder für die Activity oder die View implementieren, aber nicht für beide:

Wir empfehlen, die Ereignisse für das spezifische View-Objekt aufzuzeichnen, mit dem der Nutzer interagiert. Prüfe die folgenden Objekte, die von den Callbacks bereitgestellt werden, um Informationen zum Typ des empfangenen Eingabeereignisses zu erhalten:

KeyEvent
Ein Objekt, das Ereignisse für das Steuerkreuz und die Schaltflächen des Gamepads beschreibt. Schlüsselereignisse werden von einem Schlüsselcode begleitet, der die ausgelöste Schaltfläche angibt, z. B. DPAD_DOWN oder BUTTON_A. Sie können den Schlüsselcode durch Aufrufen von getKeyCode() oder über Rückrufe für wichtige Ereignisse wie onKeyDown() abrufen.
MotionEvent
Ein Objekt, das die Eingabe durch Bewegungen des Joysticks und des Schulterauslösers beschreibt. Bewegungsereignisse werden von einem Aktionscode und einer Reihe von Achsenwerten begleitet. Der Aktionscode gibt die aufgetretene Statusänderung an, z. B. die Bewegung eines Joysticks. Die Achsenwerte beschreiben die Position und andere Bewegungseigenschaften für ein bestimmtes physisches Steuerelement, z. B. AXIS_X oder AXIS_RTRIGGER. Sie können den Aktionscode durch Aufrufen von getAction() und den Achsenwert durch Aufrufen von getAxisValue() abrufen.

In dieser Lektion erfahren Sie, wie Sie die Eingaben der gängigsten Arten von physischen Steuerelementen (Gamepad-Tasten, Richtungstasten und Joysticks) auf einem Spielbildschirm verarbeiten, indem Sie die oben genannten View-Callback-Methoden implementieren und KeyEvent- und MotionEvent-Objekte verarbeiten.

Prüfen, ob ein Gamecontroller verbunden ist

Bei der Meldung von Eingabeereignissen unterscheidet Android nicht zwischen Ereignissen, die von einem Gerät ohne Gamecontroller stammen, und Ereignissen, die von einem Gamecontroller stammen. Eine Touchscreenaktion generiert beispielsweise ein AXIS_X-Ereignis, das die X-Koordinate der Touchoberfläche darstellt, während ein Joystick ein AXIS_X-Ereignis generiert, das die X-Position des Joysticks darstellt. Wenn in Ihrem Spiel die Eingabe über einen Gamecontroller verarbeitet werden soll, sollten Sie zuerst prüfen, ob das Eingabeereignis von einem relevanten Quelltyp stammt.

Wenn du prüfen möchtest, ob ein verbundenes Eingabegerät ein Gamecontroller ist, ruf getSources() auf, um ein kombiniertes Bitfeld der auf diesem Gerät unterstützten Eingabequellentypen zu erhalten. Sie können dann testen, ob die folgenden Felder festgelegt sind:

  • Ein Quelltyp von SOURCE_GAMEPAD gibt an, dass das Eingabegerät Gamepad-Tasten hat (z. B. BUTTON_A). Dieser Quelltyp gibt nicht genau an, ob der Gamecontroller D-Pad-Tasten hat, obwohl die meisten Gamepads in der Regel Richtungssteuerelemente haben.
  • Ein Quelltyp von SOURCE_DPAD gibt an, dass das Eingabegerät D-Pad-Tasten hat (z. B. DPAD_UP).
  • Ein Quelltyp von SOURCE_JOYSTICK weist darauf hin, dass das Eingabegerät analoge Steuerknüppel hat (z. B. einen Joystick, der Bewegungen entlang von AXIS_X und AXIS_Y erfasst).

Das folgende Code-Snippet zeigt eine Hilfsmethode, mit der Sie prüfen können, ob die verbundenen Eingabegeräte Gamecontroller sind. In diesem Fall ruft die Methode die Geräte-IDs für die Gamecontroller ab. Sie können dann jede Geräte-ID einem Spieler in Ihrem Spiel zuordnen und Spielaktionen für jeden verbundenen Spieler separat verarbeiten. Weitere Informationen zur Unterstützung mehrerer Gamecontroller, die gleichzeitig mit demselben Android-Gerät verbunden sind, finden Sie unter Mehrere Gamecontroller unterstützen.

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

Außerdem können Sie auch nach einzelnen Eingabefunktionen suchen, die von einem angeschlossenen Controller unterstützt werden. Das kann beispielsweise nützlich sein, wenn Sie möchten, dass in Ihrem Spiel nur Eingaben von physischen Steuerelementen verwendet werden, die es versteht.

So prüfen Sie, ob ein bestimmter Tasten- oder Achsencode von einem verbundenen Gamecontroller unterstützt wird:

  • Unter Android 4.4 (API-Level 19) oder höher können Sie mithilfe von hasKeys(int...) feststellen, ob ein Tastencode auf einem verbundenen Gamecontroller unterstützt wird.
  • Unter Android 3.1 (API-Level 12) oder höher können Sie alle verfügbaren Achsen auf einem verbundenen Gamecontroller aufrufen, indem Sie zuerst getMotionRanges() drücken. Rufen Sie dann für jedes zurückgegebene InputDevice.MotionRange-Objekt getAxis() auf, um die Achsen-ID abzurufen.

Gamepad-Tastendrücke verarbeiten

Abbildung 1 zeigt, wie Android Tastencodes und Achsenwerte den physischen Steuerelementen der meisten Gamecontroller zuordnet.

Abbildung 1. Profil für einen generischen Controller.

Die Zusatzinformationen in der Abbildung beziehen sich auf Folgendes:

Gängige Tastencodes, die durch das Drücken von Gamepad-Tasten generiert werden, sind BUTTON_A, BUTTON_B, BUTTON_SELECT und BUTTON_START. Bei einigen Gamecontrollern wird der Tastencode DPAD_CENTER auch ausgelöst, wenn die Mitte des Steuerkreuzes gedrückt wird. Ihr Spiel kann den Schlüsselcode prüfen, indem getKeyCode() aufgerufen wird oder über Rückrufe für Schlüsselereignisse wie onKeyDown(). Wenn es sich um ein Ereignis handelt, das für Ihr Spiel relevant ist, wird es als Spielaktion verarbeitet. In Tabelle 1 sind die empfohlenen Spielaktionen für die gängigsten Gamepad-Tasten aufgeführt.

Tabelle 1 Empfohlene Spielaktionen für Gamepad-Tasten.

Spielaktion Code für die Taste
Spiel im Hauptmenü starten oder während des Spiels pausieren/pausierung aufheben BUTTON_START*
Menü einblenden BUTTON_SELECT* und KEYCODE_MENU*
Entspricht dem Verhalten der Android-Schaltfläche Zurück, das im Designleitfaden Navigation beschrieben ist. KEYCODE_BACK
Zurück zu einem vorherigen Menüpunkt wechseln BUTTON_B
Auswahl bestätigen oder primäre Spielaktion ausführen BUTTON_A und DPAD_CENTER

* Ihr Spiel darf nicht von der Anwesenheit der Start-, Auswahl- oder Menütaste abhängen.

Tipp : Sie können in Ihrem Spiel einen Konfigurationsbildschirm einrichten, über den Nutzer ihre eigenen Gamecontroller-Zuordnungen für Spielaktionen anpassen können.

Im folgenden Snippet wird gezeigt, wie Sie onKeyDown() überschreiben, um die Tastendrücke BUTTON_A und DPAD_CENTER mit einer Spielaktion zu verknüpfen.

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

Hinweis : Unter Android 4.2 (API-Ebene 17) und niedriger wird BUTTON_A standardmäßig als Android-Zurück-Taste behandelt. Wenn Ihre App diese Android-Versionen unterstützt, müssen Sie BUTTON_A als primäre Spielaktion behandeln. Die aktuelle Version des Android SDK auf dem Gerät findest du unter dem Wert Build.VERSION.SDK_INT.

Eingaben über die Richtungstasten verarbeiten

Das Steuerkreuz ist eine gängige physische Steuerung in vielen Gamecontrollern. In Android wird das Drücken der Tasten nach oben und unten als AXIS_HAT_Y-Ereignisse in einem Bereich von -1,0 (aufwärts) bis 1,0 (nach unten) angegeben. Beim Drücken der Tasten nach links oder rechts wird das Steuerkreuz als AXIS_HAT_X-Ereignisse mit einem Bereich von -1,0 (links) bis 1,0 (rechts) angegeben.

Einige Controller melden D-Pad-Aktionen stattdessen mit einem Tastencode. Wenn in Ihrem Spiel die Druckbefehle des Steuerkreuzes berücksichtigt werden, sollten Sie die Ereignisse der Richtungsachse und die Tastencodes des Steuerkreuzes wie in Tabelle 2 empfohlen als Eingabeereignisse behandeln.

Tabelle 2: Empfohlene Standardspielaktionen für D-Pad-Tastencodes und Werte der Richtungsachse.

Spielaktion Tastencode des Steuerkreuzes Hat-Achsencode
Nach oben KEYCODE_DPAD_UP AXIS_HAT_Y (für Werte von 0 bis -1,0)
Nach unten KEYCODE_DPAD_DOWN AXIS_HAT_Y (für Werte von 0 bis 1,0)
Nach links bewegen KEYCODE_DPAD_LEFT AXIS_HAT_X (für Werte von 0 bis -1,0)
Nach rechts bewegen KEYCODE_DPAD_RIGHT AXIS_HAT_X (für Werte von 0 bis 1,0)

Das folgende Code-Snippet zeigt eine Hilfsklasse, mit der Sie die Hutachse und Schlüsselcodewerte eines Eingabeereignisses prüfen können, um die Richtung des Steuerkreuzes zu bestimmen.

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

Sie können diese Hilfsklasse in Ihrem Spiel überall dort verwenden, wo Sie die Eingabe über das Steuerkreuz verarbeiten möchten, z. B. in den onGenericMotionEvent()- oder onKeyDown()-Callbacks.

Beispiel:

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

Joystickbewegungen verarbeiten

Wenn Spieler einen Joystick auf ihrer Gamecontroller bewegen, meldet Android eine MotionEvent, die den ACTION_MOVE-Aktionskode und die aktualisierten Positionen der Achsen des Joysticks enthält. Ihr Spiel kann die von der MotionEvent bereitgestellten Daten verwenden, um festzustellen, ob eine Joystickbewegung, die für das Spiel relevant ist, stattgefunden hat.

Beachten Sie, dass bei Joystick-Bewegungsereignissen mehrere Bewegungsmuster in einem einzigen Objekt zusammengefasst werden können. Das MotionEvent-Objekt enthält die aktuelle Position für jede Joystickachse sowie mehrere bisherige Positionen für jede Achse. Wenn Bewegungsereignisse mit dem Aktionscode ACTION_MOVE (z. B. Joystickbewegungen) erfasst werden, werden die Achsenwerte von Android aus Effizienzgründen in einem Batch gesendet. Die bisherigen Werte für eine Achse bestehen aus den einzelnen Werten, die älter als der aktuelle Achsenwert sind und jünger als die Werte, die in früheren Bewegungsereignissen erfasst wurden. Weitere Informationen finden Sie in der Referenz zu MotionEvent.

Anhand der Verlaufsdaten können Sie die Bewegung eines Spielobjekts basierend auf der Joystickeingabe genauer rendern. Wenn Sie die aktuellen und bisherigen Werte abrufen möchten, rufen Sie getAxisValue() oder getHistoricalAxisValue() auf. Die Anzahl der bisherigen Punkte finden Sie auch im Joystick-Ereignis, indem Sie getHistorySize() aufrufen.

Im folgenden Snippet wird gezeigt, wie Sie den onGenericMotionEvent()-Callback überschreiben, um die Joystick-Eingabe zu verarbeiten. Sie sollten zuerst die bisherigen Werte für eine Achse und dann ihre aktuelle Position verarbeiten.

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

Bevor Sie die Joystick-Eingabe verwenden, müssen Sie feststellen, ob der Joystick zentriert ist, und dann die Achsenbewegungen entsprechend berechnen. Joysticks haben in der Regel einen flachen Bereich, d. h. einen Wertebereich in der Nähe der Koordinate (0, 0), bei der die Achse als zentriert betrachtet wird. Wenn der von Android gemeldete Achsenwert in den flachen Bereich fällt, sollte der Controller im Ruhezustand sein, d. h. sich entlang beider Achsen bewegen.

Das folgende Snippet zeigt eine Hilfsmethode, mit der die Bewegung entlang jeder Achse berechnet wird. Dieser Helfer wird in der unten beschriebenen processJoystickInput()-Methode aufgerufen.

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

Zusammenfassend können Sie Joystickbewegungen in Ihrem Spiel so verarbeiten:

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
}

Wenn Sie Gamecontroller mit komplexeren Funktionen als einem einzelnen Joystick unterstützen möchten, beachten Sie die folgenden Best Practices:

  • Dual-Controller-Sticks verarbeiten Viele Gamecontroller haben sowohl einen linken als auch einen rechten Joystick. Für den linken Stick meldet Android horizontale Bewegungen als AXIS_X-Ereignisse und vertikale Bewegungen als AXIS_Y-Ereignisse. Für den rechten Stick meldet Android horizontale Bewegungen als AXIS_Z-Ereignisse und vertikale Bewegungen als AXIS_RZ-Ereignisse. Achten Sie darauf, in Ihrem Code beide Controllersticks zu verarbeiten.
  • Geben Sie eine Reaktion auf das Drücken des Schulter-Triggers aus und achten Sie darauf, dass Ihr Spiel mit den Ereignissen AXIS_ und KEYCODE_BUTTON_ funktioniert. Einige Controller haben Auslöser für die linke und rechte Schulter. Wenn diese Trigger vorhanden sind, wird ein AXIS_*TRIGGER- oder KEYCODE_BUTTON_*2-Ereignis oder beides gesendet. Für den linken Trigger wären das AXIS_LTRIGGER und KEYCODE_BUTTON_L2. Für den rechten Abzug wären das AXIS_RTRIGGER und KEYCODE_BUTTON_R2. Achsenereignisse treten nur auf, wenn der Trigger einen Wertebereich zwischen 0 und 1 ausgibt. Einige Controller mit analogem Ausgang geben zusätzlich zu Achsenereignissen auch Tastenereignisse aus. Spiele müssen sowohl AXIS_- als auch KEYCODE_BUTTON_-Ereignisse unterstützen, um mit allen gängigen Gamecontrollern kompatibel zu bleiben. Wenn ein Controller beide meldet, sollten Sie jedoch das Ereignis verwenden, das für Ihr Gameplay am sinnvollsten ist. Unter Android 4.3 (API-Ebene 18) und höher meldet ein Controller, der eine AXIS_LTRIGGER erzeugt, auch einen identischen Wert für die AXIS_BRAKE-Achse. Dasselbe gilt für AXIS_RTRIGGER und AXIS_GAS. Android meldet alle analogen Auslöserdrücke mit einem normalisierten Wert von 0,0 (losgelassen) bis 1,0 (vollständig gedrückt).
  • Das Verhalten und die Unterstützung können in emulierten Umgebungen variieren. Emulierte Plattformen wie Google Play Spiele können sich je nach den Funktionen des Host-Betriebssystems leicht unterscheiden. Einige Controller, die sowohl AXIS_- als auch KEYCODE_BUTTON_-Ereignisse senden, senden beispielsweise nur AXIS_-Ereignisse. Für einige Controller fehlt die Unterstützung möglicherweise vollständig.