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

Контроллеры выполняют два типа действий:

  • KeyEvent используется для любой кнопки с бинарным состоянием «включено» и «выключено».
  • MotionEvent используется для любой оси, которая возвращает диапазон значений. Например, от -1 до 1 для аналоговых джойстиков или от 0 до 1 для аналоговых триггеров.

Вы можете прочитать эти данные из View , которое находится в focus .

Котлин

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
  if (event.isFromSource(SOURCE_GAMEPAD)
      && event.repeatCount == 0
  ) {
      Log.d("GameView", "Gamepad key pressed: $keyCode")
      return true
  }

  return super.onKeyDown(keyCode, event)
}

override fun onGenericMotionEvent(event: MotionEvent): Boolean {
  if (event.isFromSource(SOURCE_JOYSTICK)) {
      Log.d("GameView", "Gamepad event: $event")
      return true
  }

  return super.onGenericMotionEvent(event)
}

Java

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
  if (event.isFromSource(SOURCE_GAMEPAD)
          && event.getRepeatCount() == 0
  ) {
      Log.d("GameView", "Gamepad key pressed: " + keyCode);
      return true;
  }

  return super.onKeyDown(keyCode, event);
}

@Override
public boolean onGenericMotionEvent(MotionEvent event) {
  if (event.isFromSource(SOURCE_JOYSTICK)) {
      Log.d("GameView", "Gamepad event: " + event);
      return true;
  }
  return super.onGenericMotionEvent(event);
}

При необходимости вы можете считывать события непосредственно из Activity .

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

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

Чтобы убедиться, что подключенное InputDevice является игровым контроллером, используйте функцию supportsSource(int) :

  • Тип источника SOURCE_GAMEPAD указывает на наличие кнопок контроллера на устройстве ввода (например, KEYCODE_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 (supportsSource(SOURCE_GAMEPAD)
              || supportsSource(SOURCE_JOYSTICK)) {
              // This device is a game controller. Store its device ID.
              gameControllerDeviceIds
                  .takeIf { !it.contains(deviceId) }
                  ?.add(deviceId)
          }
      }
  }
  return gameControllerDeviceIds
}

Java

 public ArrayList<Integer> getGameControllerIds() {
  ArrayList<Integer> gameControllerDeviceIds = new ArrayList<Integer>();
  int[] deviceIds = InputDevice.getDeviceIds();
  for (int deviceId : deviceIds) {
      InputDevice dev = InputDevice.getDevice(deviceId);

      if (dev == null) {
          continue;
      }

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

Входы контроллера процесса

В этом разделе описаны типы игровых контроллеров, поддерживаемых на Android.

Разработчикам на C++ следует использовать библиотеку Game Controller . Она объединяет все контроллеры в наиболее распространенный набор функций и обеспечивает согласованный интерфейс между ними, включая возможность определения расположения кнопок.

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

Стандартный игровой контроллер с обозначенными элементами управления, включая крестовину, аналоговые стики и кнопки.
Рисунок 1. Профиль стандартного игрового контроллера.

В таблице перечислены стандартные названия и типы событий для игровых контроллеров. Полный список событий см. в разделе «Распространенные варианты» . Система отправляет события MotionEvent через onGenericMotionEvent , а события KeyEvent через onKeyDown и onKeyUp .

Вход контроллера Ключевое событие MotionEvent
1. D-Pad
AXIS_HAT_X
(горизонтальный ввод)
AXIS_HAT_Y
(вертикальный ввод)
2. Левый аналоговый джойстик
KEYCODE_BUTTON_THUMBL
(при нажатии)
AXIS_X
(горизонтальное движение)
AXIS_Y
(вертикальное движение)
3. Правый аналоговый джойстик
KEYCODE_BUTTON_THUMBR
(при нажатии)
AXIS_Z
(горизонтальное движение)
AXIS_RZ
(вертикальное движение)
4. Кнопка X KEYCODE_BUTTON_X
5. Кнопка KEYCODE_BUTTON_A
6. Кнопка Y KEYCODE_BUTTON_Y
7. Кнопка B KEYCODE_BUTTON_B
8. Правый бампер
KEYCODE_BUTTON_R1
9. Правый курок
AXIS_RTRIGGER
10. Левый курок AXIS_LTRIGGER
11. Левый бампер KEYCODE_BUTTON_L1
12. Начало KEYCODE_BUTTON_START
13. Выберите KEYCODE_BUTTON_SELECT

Нажатия кнопок на рукоятке

Поскольку Android распознает нажатия кнопок контроллера так же, как и нажатия кнопок клавиатуры, вам необходимо:

  • Убедитесь, что событие исходит от SOURCE_GAMEPAD .
  • Убедитесь, что событие нажатия кнопки получено только один раз с помощью KeyEvent.getRepeatCount() , Android будет отправлять повторные события нажатия клавиш так же, как если бы вы удерживали клавишу на клавиатуре.
  • Укажите, что событие обработано, вернув true .
  • Передайте необработанные события в super , чтобы убедиться в корректной работе различных слоев совместимости Android.

    Котлин

    class GameView : View {
    // ...
    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        event.apply {
            var handled = false
    
            // make sure we're handling gamepad events
            if (isFromSource(SOURCE_GAMEPAD)) {
    
                // avoid processing the keycode repeatedly
                if (repeatCount == 0) {
                    when (keyCode) {
                        // handle the "A" button
                        KEYCODE_BUTTON_A -> {
                          handled = true
                        }
                    }
                    // ...
                }
            }
            if (handled) {
                return true
            }
       }
       return super.onKeyDown(keyCode, event)
      }
    }
    

    Java

    public class GameView extends View {
    // ...
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        // make sure we're handling gamepad events
        if (event.isFromSource(SOURCE_GAMEPAD)) {
            // avoid processing the keycode repeatedly
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    case KEYCODE_BUTTON_A:
                        // handle the "A" button
                        handled = true;
                        break;
                    // ...
                }
            }
            // mark this event as handled
            if (handled) {
                return true;
            }
        }
        // Always do this instead of "return false"
        // it allows Android's input compatibility layers to work
        return super.onKeyDown(keyCode, event);
      }
    }
    

Обработка входного сигнала направленной панели

Четырехпозиционная крестовина, или D-pad, — это распространенный физический элемент управления во многих игровых контроллерах. Android регистрирует нажатия D-pad UP и DOWN как события AXIS_HAT_Y , где -1.0 означает нажатие вверх, а 1.0 — вниз. Нажатия D-pad LEFT или RIGHT регистрируются как события AXIS_HAT_X , где -1.0 означает нажатие влево, а 1.0 — влево.

Некоторые контроллеры вместо этого сообщают о нажатиях D-pad с помощью кода клавиши. Если ваша игра учитывает нажатия D-pad, следует рассматривать события оси хай-хэта и коды клавиш 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)

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

Котлин

class Dpad {

    private var directionPressed = -1 // initialized to -1

    fun getDirectionPressed(event: InputEvent): Int {
        if (!isDpadDevice(event)) {
            return -1
        }

        // If the input event is a MotionEvent, check its hat axis values.
        (event as? MotionEvent)?.apply {

            // Use the hat axis value to find the D-pad direction
            val xaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_X)
            val yaxis: Float = event.getAxisValue(MotionEvent.AXIS_HAT_Y)

            directionPressed = when {
                // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
                // LEFT and RIGHT direction accordingly.
                xaxis.compareTo(-1.0f) == 0 -> Dpad.LEFT
                xaxis.compareTo(1.0f) == 0 -> Dpad.RIGHT
                // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
                // UP and DOWN direction accordingly.
                yaxis.compareTo(-1.0f) == 0 -> Dpad.UP
                yaxis.compareTo(1.0f) == 0 -> Dpad.DOWN
                else -> directionPressed
            }
        }
        // If the input event is a KeyEvent, check its key code.
        (event as? KeyEvent)?.apply {

            // Use the key code to find the D-pad direction.
            directionPressed = when(event.keyCode) {
                KeyEvent.KEYCODE_DPAD_LEFT -> Dpad.LEFT
                KeyEvent.KEYCODE_DPAD_RIGHT -> Dpad.RIGHT
                KeyEvent.KEYCODE_DPAD_UP -> Dpad.UP
                KeyEvent.KEYCODE_DPAD_DOWN -> Dpad.DOWN
                KeyEvent.KEYCODE_DPAD_CENTER ->  Dpad.CENTER
                else -> directionPressed
            }
        }
        return directionPressed
    }

    companion object {
        internal const val UP = 0
        internal const val LEFT = 1
        internal const val RIGHT = 2
        internal const val DOWN = 3
        internal const val CENTER = 4

        fun isDpadDevice(event: InputEvent): Boolean =
            // Check that input comes from a device with directional pads.
            return event.isFromSource(InputDevice.SOURCE_DPAD)
    }
}

Java

public class Dpad {
    final static int UP       = 0;
    final static int LEFT     = 1;
    final static int RIGHT    = 2;
    final static int DOWN     = 3;
    final static int CENTER   = 4;

    int directionPressed = -1; // initialized to -1

    public int getDirectionPressed(InputEvent event) {
        if (!isDpadDevice(event)) {
           return -1;
        }

        // If the input event is a MotionEvent, check its hat axis values.
        if (event instanceof MotionEvent) {

            // Use the hat axis value to find the D-pad direction
            MotionEvent motionEvent = (MotionEvent) event;
            float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
            float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);

            // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
            // LEFT and RIGHT direction accordingly.
            if (Float.compare(xaxis, -1.0f) == 0) {
                directionPressed =  Dpad.LEFT;
            } else if (Float.compare(xaxis, 1.0f) == 0) {
                directionPressed =  Dpad.RIGHT;
            }
            // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
            // UP and DOWN direction accordingly.
            else if (Float.compare(yaxis, -1.0f) == 0) {
                directionPressed =  Dpad.UP;
            } else if (Float.compare(yaxis, 1.0f) == 0) {
                directionPressed =  Dpad.DOWN;
            }
        }

        // If the input event is a KeyEvent, check its key code.
        else if (event instanceof KeyEvent) {

           // Use the key code to find the D-pad direction.
            KeyEvent keyEvent = (KeyEvent) event;
            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                directionPressed = Dpad.LEFT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                directionPressed = Dpad.RIGHT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                directionPressed = Dpad.UP;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                directionPressed = Dpad.DOWN;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
                directionPressed = Dpad.CENTER;
            }
        }
        return directionPressed;
    }

    public static boolean isDpadDevice(InputEvent event) {
        // Check that input comes from a device with directional pads.
        return event.isFromSource(InputDevice.SOURCE_DPAD);
     }
}

Этот вспомогательный класс можно использовать в вашей игре везде, где требуется обработка ввода с D-pad (например, в коллбэках 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.
    ...
}

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

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

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

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

Для точного отображения движения игрового объекта на основе ввода с джойстика можно использовать историческую информацию, предоставляемую объектами MotionEvent .

Вы можете получить текущие и исторические значения, используя следующие методы:

Следующий фрагмент кода показывает, как можно переопределить функцию обратного вызова 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)
        }
    }
}

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

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

Приведённый фрагмент кода демонстрирует вспомогательный метод, который вычисляет перемещение вдоль каждой оси. Вызов этого вспомогательного метода происходит в методе 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
}

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

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

Котлин

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
}

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

  • Обрабатывайте движения двух джойстиков контроллера. Многие игровые контроллеры имеют как левый, так и правый джойстик. Для левого джойстика 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 . События Axis возникают только в том случае, если триггер генерирует диапазон значений от 0 до 1, а некоторые контроллеры с аналоговым выходом генерируют события кнопок в дополнение к событиям Axis. Игры должны поддерживать как события 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_ , а поддержка некоторых контроллеров может полностью отсутствовать.

Распространенные варианты

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

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

Типичные типы контроллеров

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

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

Тип контроллера Различия в поведении Варианты маркировки
Контроллеры в стиле Xbox

Это контроллеры, обычно предназначенные для платформ Microsoft Xbox и Windows*.

Эти контроллеры соответствуют набору функций, описанных в разделе «Входы контроллера процесса». Кнопки L2/R2 на этих контроллерах обозначены как LT/RT.
Контроллеры в стиле переключателей

Эти контроллеры, как правило, предназначены для консолей семейства Nintendo Switch*.

Эти контроллеры отправляют KeyEvent KEYCODE_BUTTON_R2 KEYCODE_BUTTON_L2 MotionEvent s Кнопки L2/R2 на этих контроллерах обозначены как ZL/ZR.

Эти контроллеры также меняют местами кнопки A и B , а также кнопки X и Y , поэтому KEYCODE_BUTTON_A обозначает кнопку с меткой B , и наоборот.

Контроллеры в стиле PlayStation

Эти контроллеры, как правило, предназначены для консолей семейства Sony PlayStation*.

Эти контроллеры отправляют события MotionEvent , как и контроллеры в стиле Xbox , а также события KeyEvent , как и контроллеры в стиле Switch, при полном нажатии. В этих контроллерах для кнопок на лицевой панели используется другой набор символов.

* Microsoft, Xbox и Windows являются зарегистрированными товарными знаками Microsoft; Nintendo Switch является зарегистрированным товарным знаком Nintendo of America Inc.; PlayStation является зарегистрированным товарным знаком Sony Interactive Entertainment Inc.

Устранение неоднозначности кнопок триггера

Некоторые контроллеры отправляют события AXIS_LTRIGGER и AXIS_RTRIGGER , другие — KEYCODE_BUTTON_L2 и KEYCODE_BUTTON_R2 , а третьи — все эти события в зависимости от возможностей своего оборудования. Для обеспечения максимальной совместимости необходимо поддерживать все эти события.

Все контроллеры, отправляющие AXIS_LTRIGGER , также будут отправлять AXIS_BRAKE , аналогично для AXIS_RTRIGGER и AXIS_GAS чтобы обеспечить максимальную совместимость между гоночными рулями и обычными игровыми контроллерами. Как правило, это не вызовет проблем, но следует учитывать такие функции, как переназначение клавиш на экранах.

Курок MotionEvent KeyEvent
Левый курок AXIS_LTRIGGER
AXIS_BRAKE
KEYCODE_BUTTON_L2
Правый курок AXIS_RTRIGGER
AXIS_GAS
KEYCODE_BUTTON_R2

Следует убедиться, что ваша игра может обрабатывать как KeyEvent , так и MotionEvent , чтобы обеспечить совместимость с как можно большим количеством контроллеров, а также исключить дублирование событий.

Поддерживаемые контроллеры

При тестировании мы рекомендуем убедиться, что ваша игра работает с одним контроллером в каждой из категорий.

  • В стиле Xbox
  • Стиль Nintendo Switch
  • В стиле PlayStation

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