Обрабатывать действия контроллера

На системном уровне Android сообщает входные коды событий с игровых контроллеров в виде кодов клавиш Android и значений осей. В своей игре вы можете получать эти коды и значения и конвертировать их в определенные внутриигровые действия.

Когда игроки физически подключают или подключают по беспроводной сети игровой контроллер к своим устройствам на базе Android, система автоматически определяет контроллер как устройство ввода и начинает сообщать о событиях ввода. Ваша игра может получать эти входные события, реализуя следующие методы обратного вызова в активном Activity или сфокусированном View (вы должны реализовать обратные вызовы либо для Activity , либо View , но не для обоих):

Рекомендуемый подход — фиксировать события из конкретного объекта View , с которым взаимодействует пользователь. Проверьте следующие объекты, предоставленные обратными вызовами, чтобы получить информацию о типе полученного события ввода:

KeyEvent
Объект, описывающий события крестовины и кнопок геймпада. Ключевые события сопровождаются кодом клавиши , который указывает на срабатывание конкретной кнопки, например DPAD_DOWN или BUTTON_A . Вы можете получить код ключа, вызвав getKeyCode() или обратные вызовы ключевых событий, такие как onKeyDown() .
MotionEvent
Объект, описывающий ввод данных от движений джойстика и триггера плеча. События движения сопровождаются кодом действия и набором значений оси . Код действия указывает произошедшее изменение состояния, например перемещение джойстика. Значения оси описывают положение и другие свойства движения для конкретного физического элемента управления, например AXIS_X или AXIS_RTRIGGER . Вы можете получить код действия, вызвав getAction() , а значение оси, вызвав getAxisValue() .

В этом уроке основное внимание уделяется тому, как можно обрабатывать ввод от наиболее распространенных типов физических элементов управления (кнопок геймпада, кнопок управления и джойстиков) на игровом экране, реализуя вышеупомянутые методы обратного вызова View и обрабатывая объекты KeyEvent и MotionEvent .

Убедитесь, что игровой контроллер подключен

Сообщая о событиях ввода, Android не различает события, исходящие от устройства, не являющегося игровым контроллером, и события, исходящие от игрового контроллера. Например, действие сенсорного экрана генерирует событие AXIS_X , которое представляет координату X сенсорной поверхности, а джойстик генерирует событие AXIS_X , которое представляет положение X джойстика. Если ваша игра заботится об обработке входных данных игрового контроллера, вам следует сначала убедиться, что событие ввода поступает из соответствующего типа источника.

Чтобы убедиться, что подключенное устройство ввода является игровым контроллером, вызовите getSources() чтобы получить комбинированное битовое поле типов источников ввода, поддерживаемых на этом устройстве. Затем вы можете проверить, установлены ли следующие поля:

  • Тип источника SOURCE_GAMEPAD указывает, что устройство ввода имеет кнопки геймпада (например, BUTTON_A ). Обратите внимание, что этот тип источника не указывает строго, есть ли на игровом контроллере кнопки D-pad, хотя большинство геймпадов обычно имеют элементы управления направлением.
  • Тип источника SOURCE_DPAD указывает, что устройство ввода имеет кнопки D-pad (например, DPAD_UP ).
  • Тип источника SOURCE_JOYSTICK указывает, что устройство ввода имеет аналоговые джойстики управления (например, джойстик, записывающий перемещения по AXIS_X и AXIS_Y ).

В следующем фрагменте кода показан вспомогательный метод, позволяющий проверить, являются ли подключенные устройства ввода игровыми контроллерами. Если это так, метод получает идентификаторы устройств для игровых контроллеров. Затем вы можете связать каждый идентификатор устройства с игроком в вашей игре и обрабатывать игровые действия для каждого подключенного игрока отдельно. Дополнительные сведения о поддержке нескольких игровых контроллеров, одновременно подключенных к одному устройству Android, см. в разделе Поддержка нескольких игровых контроллеров .

Котлин

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
}

Ява

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

Кроме того, вы можете проверить отдельные возможности ввода, поддерживаемые подключенным игровым контроллером. Это может быть полезно, например, если вы хотите, чтобы ваша игра использовала только входные данные из набора понятных ей физических элементов управления.

Чтобы определить, поддерживается ли подключенным игровым контроллером конкретный код клавиши или код оси, используйте следующие методы:

  • В Android 4.4 (уровень API 19) или выше вы можете определить, поддерживается ли код ключа на подключенном игровом контроллере, вызвав hasKeys(int...) .
  • В Android 3.1 (уровень API 12) или выше вы можете найти все доступные оси, поддерживаемые подключенным игровым контроллером, сначала вызвав getMotionRanges() . Затем для каждого возвращенного объекта InputDevice.MotionRange вызовите getAxis() чтобы получить идентификатор его оси.

Обрабатывать нажатия кнопок геймпада

На рис. 1 показано, как Android сопоставляет коды клавиш и значения осей с физическими элементами управления на большинстве игровых контроллеров.

Рисунок 1. Профиль обычного игрового контроллера.

Условные обозначения на рисунке относятся к следующему:

Общие коды клавиш, генерируемые нажатием кнопок геймпада, включают BUTTON_A , BUTTON_B , BUTTON_SELECT и BUTTON_START . Некоторые игровые контроллеры также активируют код клавиши DPAD_CENTER при нажатии на центр перекладины крестовины. Ваша игра может проверить код клавиши, вызвав getKeyCode() или обратные вызовы ключевых событий, такие как onKeyDown() , и, если он представляет событие, имеющее отношение к вашей игре, обработать его как игровое действие. В таблице 1 приведены рекомендуемые игровые действия для наиболее распространенных кнопок геймпада.

Таблица 1. Рекомендуемые игровые действия для кнопок геймпада.

Игровое действие Код кнопки кнопки
Запустите игру в главном меню или приостановите/возобновите паузу во время игры. BUTTON_START *
Меню дисплея BUTTON_SELECT * и KEYCODE_MENU *
То же, что и поведение навигации «Назад» в Android, описанное в руководстве по проектированию навигации . KEYCODE_BACK
Вернуться к предыдущему элементу меню BUTTON_B
Подтвердите выбор или выполните основное игровое действие. BUTTON_A и DPAD_CENTER

* Ваша игра не должна зависеть от наличия кнопок «Пуск», «Выбор» или «Меню».

Совет: Рассмотрите возможность предоставления экрана конфигурации в вашей игре, чтобы пользователи могли персонализировать сопоставления своих игровых контроллеров для игровых действий.

В следующем фрагменте показано, как можно переопределить onKeyDown() чтобы связать нажатия кнопок BUTTON_A и DPAD_CENTER с игровым действием.

Котлин

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
}

Ява

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

Примечание. В Android 4.2 (уровень API 17) и более ранних версиях система по умолчанию рассматривает BUTTON_A как клавишу Android Back . Если ваше приложение поддерживает эти версии Android, обязательно рассматривайте BUTTON_A как основное игровое действие. Чтобы определить текущую версию Android SDK на устройстве, обратитесь к значению Build.VERSION.SDK_INT .

Обработка ввода с помощью навигационной панели

4-позиционная навигационная панель (D-pad) является обычным физическим элементом управления во многих игровых контроллерах. Android сообщает о нажатиях крестовины ВВЕРХ и ВНИЗ как о событиях AXIS_HAT_Y с диапазоном от -1,0 (вверх) до 1,0 (вниз), а о нажатиях крестовины ВЛЕВО или ВПРАВО как о событиях AXIS_HAT_X с диапазоном от -1,0 (влево) до 1,0 ( верно).

Некоторые контроллеры вместо этого сообщают о нажатиях D-pad с кодом клавиши. Если ваша игра учитывает нажатия крестовины, вам следует рассматривать события оси шляпы и коды клавиш крестовины как одни и те же события ввода, как рекомендовано в таблице 2.

Таблица 2. Рекомендуемые игровые действия по умолчанию для кодов клавиш D-pad и значений оси шляпы.

Игровое действие Код клавиши D-pad Код оси шляпы
Двигаться вверх KEYCODE_DPAD_UP AXIS_HAT_Y (для значений от 0 до -1,0)
Вниз KEYCODE_DPAD_DOWN AXIS_HAT_Y (для значений от 0 до 1,0)
Двигаться влево KEYCODE_DPAD_LEFT AXIS_HAT_X (для значений от 0 до -1,0)
Двигаться вправо KEYCODE_DPAD_RIGHT AXIS_HAT_X (для значений от 0 до 1,0)

В следующем фрагменте кода показан вспомогательный класс, который позволяет проверять ось шляпы и значения кода клавиши из события ввода, чтобы определить направление крестовины.

Котлин

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

Ява

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

Вы можете использовать этот вспомогательный класс в своей игре везде, где хотите обрабатывать ввод с помощью крестовины (например, в обратных вызовах onGenericMotionEvent() или onKeyDown() ).

Например:

Котлин

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

Ява

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

Обработка движений джойстика

Когда игроки перемещают джойстик на своих игровых контроллерах, Android сообщает о MotionEvent , содержащем код действия ACTION_MOVE и обновленные положения осей джойстика. Ваша игра может использовать данные, предоставленные MotionEvent , чтобы определить, произошло ли нужное ей движение джойстика.

Обратите внимание, что события движения джойстика могут объединять несколько образцов движения в один объект. Объект MotionEvent содержит текущую позицию для каждой оси джойстика, а также несколько исторических позиций для каждой оси. При сообщении о событиях движения с кодом действия ACTION_MOVE (например, о движениях джойстика) Android группирует значения осей для повышения эффективности. Исторические значения для оси состоят из набора отдельных значений, которые старше текущего значения оси и более поздних, чем значения, указанные в любых предыдущих событиях движения. Подробности см. в справочнике MotionEvent .

Вы можете использовать историческую информацию для более точной визуализации движения игрового объекта на основе ввода с джойстика. Чтобы получить текущие и исторические значения, вызовите getAxisValue() или getHistoricalAxisValue() . Вы также можете узнать количество исторических точек в событии джойстика, вызвав getHistorySize() .

В следующем фрагменте показано, как можно переопределить обратный вызов onGenericMotionEvent() для обработки ввода с джойстика. Сначала следует обработать исторические значения оси, а затем обработать ее текущее положение.

Котлин

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

Ява

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

Прежде чем использовать ввод с помощью джойстика, вам необходимо определить, центрирован ли джойстик, а затем соответствующим образом рассчитать перемещения его оси. Джойстики обычно имеют плоскую область, то есть диапазон значений вблизи координаты (0,0), в которой ось считается центрированной. Если значение оси, сообщаемое Android, попадает в плоскую область, вам следует считать контроллер неподвижным (то есть неподвижным по обеим осям).

Во фрагменте ниже показан вспомогательный метод, который вычисляет движение вдоль каждой оси. Вы вызываете этот помощник в методеprocessJoystickInput processJoystickInput() описанном ниже.

Котлин

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
}

Ява

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

Собрав все это вместе, вот как вы можете обрабатывать движения джойстика в своей игре:

Котлин

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
}

Ява

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
}

Для поддержки игровых контроллеров с более сложными функциями, помимо одного джойстика, следуйте следующим рекомендациям:

  • Управляйте двойными джойстиками. Многие игровые контроллеры имеют как левый, так и правый джойстик. Для левого джойстика Android сообщает о горизонтальных перемещениях как о событиях AXIS_X , а о вертикальных перемещениях как о событиях AXIS_Y . Для правого джойстика Android сообщает о горизонтальных перемещениях как о событиях AXIS_Z , а о вертикальных перемещениях как о событиях AXIS_RZ . Обязательно обрабатывайте оба контроллера в своем коде.
  • Обрабатывайте нажатия триггера на плечо (и убедитесь, что ваша игра работает с событиями AXIS_ и KEYCODE_BUTTON_ ). Некоторые контроллеры имеют триггеры для левого и правого плеча. Когда эти триггеры присутствуют, они выдают событие AXIS_*TRIGGER или KEYCODE_BUTTON_*2 или оба. Для левого триггера это будут AXIS_LTRIGGER и KEYCODE_BUTTON_L2 . Для правильного триггера это будут AXIS_RTRIGGER и KEYCODE_BUTTON_R2 . События оси происходят только в том случае, если триггер выдает диапазон значений от 0 до 1, а некоторые контроллеры с аналоговым выходом выдают события кнопки в дополнение к событиям оси. Игры должны поддерживать события AXIS_ и KEYCODE_BUTTON_ , чтобы оставаться совместимыми со всеми распространенными игровыми контроллерами, но отдавайте предпочтение событию, которое имеет наибольший смысл для вашего игрового процесса, если контроллер сообщает об обоих. В Android 4.3 (уровень API 18) и выше контроллер, создающий AXIS_LTRIGGER также сообщает идентичное значение для оси AXIS_BRAKE . То же самое верно для AXIS_RTRIGGER и AXIS_GAS . Android сообщает обо всех нажатиях аналоговых триггеров с нормализованным значением от 0,0 (отпущен) до 1,0 (полностью нажат).
  • Конкретное поведение и поддержка могут отличаться в эмулируемых средах . Эмулируемые платформы, такие как Google Play Games , могут незначительно отличаться в поведении в зависимости от возможностей операционной системы хоста. Например, некоторые контроллеры, которые генерируют события AXIS_ и KEYCODE_BUTTON_ генерируют только события AXIS_ , а поддержка некоторых контроллеров может полностью отсутствовать.