Начало работы с Input SDK

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

Прежде чем начать

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

Input SDK предоставляет Google Play Games на ПК информацию о том, какие элементы управления используются в вашей игре, чтобы их можно было отобразить пользователю. Он также может дополнительно разрешить переназначение клавиатуры для пользователей.

Каждый элемент управления представляет собой InputAction (например, «J» для «Jump»), и вы организуете свои InputActions в InputGroups . Группа InputGroup может представлять другой режим в вашей игре, например «Вождение», «Ходьба» или «Главное меню». Вы также можете использовать InputContexts , чтобы указать, какие группы активны в разные моменты игры.

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

Следующая диаграмма последовательности описывает, как работает API входного SDK:

Диаграмма последовательности реализации игры, которая вызывает API входного SDK и его взаимодействие с Android устройство.

Если в вашей игре реализован входной SDK, ваши элементы управления отображаются в оверлее Google Play Games on PC.

Оверлей Google Play Games на ПК

Наложение «Google Play Игры для ПК» («наложение») отображает элементы управления, определенные вашей игрой. Пользователи получают доступ к наложению в любое время, нажав Shift + Tab .

Наложение Google Play Games на ПК.

Лучшие практики по разработке привязок клавиш

При разработке привязок клавиш учитывайте следующие рекомендации:

  • Группируйте свои InputActions в логически связанные InputGroups чтобы улучшить навигацию и удобство обнаружения элементов управления во время игры.
  • Назначьте каждую InputGroup не более чем одному InputContext . Детализированная InputMap обеспечивает более удобную навигацию по элементам управления в наложении.
  • Создайте InputContext для каждого типа сцены вашей игры. Обычно вы можете использовать один InputContext для всех сцен, похожих на меню. Используйте разные InputContexts для любых мини-игр в вашей игре или для альтернативных элементов управления для одной сцены.
  • Если два действия предназначены для использования одной и той же клавиши в одном и том же InputContext , используйте строку метки, например «Взаимодействие/Пожар».
  • Если две клавиши предназначены для привязки к одному и тому же InputAction , используйте два разных InputActions , которые выполняют одно и то же действие в вашей игре. Вы можете использовать одну и ту же строку метки для обоих InputActions , но ее идентификатор должен быть разным.
  • Если к набору клавиш применяется клавиша-модификатор, рассмотрите возможность использования одного InputAction с клавишей-модификатором вместо нескольких InputActions , объединяющих клавишу-модификатор (пример: используйте Shift и W, A, S, D вместо Shift + W, Shift + А, Shift+S, Shift+D ).
  • Перераспределение ввода отключается автоматически, когда пользователь пишет в текстовые поля. Следуйте рекомендациям по реализации текстовых полей Android, чтобы Android мог обнаруживать текстовые поля в вашей игре и не допускать взаимодействия с ними переназначенных клавиш. Если в вашей игре необходимо использовать нестандартные текстовые поля, вы можете использовать setInputContext() с InputContext содержащим пустой список InputGroups , чтобы отключить переназначение вручную.
  • Если ваша игра поддерживает переназначение, рассмотрите возможность обновления привязок клавиш — это конфиденциальная операция, которая может конфликтовать с сохраненными пользователем версиями. По возможности избегайте изменения идентификаторов существующих элементов управления.

Функция переназначения

Google Play Games для ПК поддерживает переназначение элементов управления клавиатурой на основе привязок клавиш, которые ваша игра предоставляет с помощью Input SDK. Это необязательно и может быть полностью отключено. Например, вы можете предоставить собственный интерфейс переназначения клавиатуры. Чтобы отключить переназначение для вашей игры, вам просто нужно указать опцию переназначения, отключенную для вашего InputMap (дополнительную информацию см. в разделе «Создание InputMap» ).

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

Попытка переназначить ключ

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

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

Ограничения переназначения

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

  • InputActions с несколькими клавишами, которые не состоят из клавиши-модификатора + клавиши-немодификатора. Например, Shift + A допустимо, а A + B , Ctrl + Alt или Shift + A + Tab – нет.
  • InputMap содержит InputActions , InputGroups или InputContexts с повторяющимися уникальными идентификаторами.

Ограничения переназначения

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

  • Переназначение комбинаций клавиш не поддерживается. Например, пользователи не могут переназначить Shift+A на Ctrl+B или A на Shift+A .
  • Переназначение не поддерживается для InputActions с помощью кнопок мыши. Например, Shift + щелчок правой кнопкой мыши нельзя переназначить.

Тестовое переназначение клавиш в эмуляторе Google Play Games на ПК

Вы можете включить функцию переназначения в эмуляторе Google Play Games на ПК в любое время, введя следующую команду adb:

adb shell dumpsys input_mapping_service --set RemappingFlagValue true

Наложение изменится, как показано на следующем изображении:

Оверлей с включенным переназначением клавиш.

Добавить SDK

Установите Input SDK в соответствии с вашей платформой разработки.

Ява и Котлин

Получите входной SDK для Java или Kotlin, добавив зависимость в файл build.gradle на уровне модуля:

dependencies {
  implementation 'com.google.android.libraries.play.games:inputmapping:1.1.1-beta'
  ...
}

Единство

Входной SDK — это стандартный пакет Unity с несколькими зависимостями.

Требуется установка пакета со всеми зависимостями. Существует несколько способов установки пакетов.

Установите пакет .unitypackage

Загрузите файл unitypackage входного SDK со всеми его зависимостями. Вы можете установить пакет .unitypackage , выбрав «Ресурсы» > «Импортировать пакет» > «Пользовательский пакет» и найдя загруженный файл.

Установить с помощью UPM

Альтернативно вы можете установить пакет с помощью диспетчера пакетов Unity , загрузив .tgz и установив его зависимости:

Установить с помощью OpenUPM

Вы можете установить пакет с помощью OpenUPM .

$ openupm add com.google.android.libraries.play.games.inputmapping

Примеры игр

Примеры интеграции с Input SDK см. в разделах AGDK Tunnel для игр Kotlin или Java и Trivial Kart для игр Unity.

Создайте свои привязки клавиш

Зарегистрируйте привязки клавиш, создав InputMap и вернув его с помощью InputMappingProvider . В следующем примере описывается InputMappingProvider :

Котлин

class InputSDKProvider : InputMappingProvider {
  override fun onProvideInputMap(): InputMap {
    TODO("Not yet implemented")
  }
}

Ява

public class InputSDKProvider implements InputMappingProvider {
    private static final String INPUTMAP_VERSION = "1.0.0";

    @Override
    @NonNull
    public InputMap onProvideInputMap() {
        // TODO: return an InputMap
    }
}

С#

#if PLAY_GAMES_PC
using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;

public class InputSDKProvider : InputMappingProviderCallbackHelper
{
    public static readonly string INPUT_MAP_VERSION = "1.0.0";

    public override InputMap OnProvideInputMap()
    {
        // TODO: return an InputMap
    }
}
#endif

Определите действия ввода

Класс InputAction используется для сопоставления клавиши или комбинации клавиш с игровым действием. InputActions должны иметь уникальные идентификаторы для всех InputActions .

Если вы поддерживаете переназначение, вы можете определить, какие InputActions можно переназначить. Если ваша игра не поддерживает переназначение, вам следует отключить опцию переназначения для всех ваших InputActions , но Input SDK достаточно умен, чтобы отключить переназначение, если вы не поддерживаете его в своем InputMap .

Этот пример отображает космос ключ к действию «Драйв» .

Котлин

companion object {
  private val driveInputAction = InputAction.create(
    "Drive",
    InputActionsIds.DRIVE.ordinal.toLong(),
    InputControls.create(listOf(KeyEvent.KEYCODE_SPACE), emptyList()),
    InputEnums.REMAP_OPTION_ENABLED)
}

Ява

private static final InputAction driveInputAction = InputAction.create(
    "Drive",
    InputEventIds.DRIVE.ordinal(),
    InputControls.create(
            Collections.singletonList(KeyEvent.KEYCODE_SPACE),
            Collections.emptyList()),
    InputEnums.REMAP_OPTION_ENABLED
);

С#

private static readonly InputAction driveInputAction = InputAction.Create(
    "Drive",
    (long)InputEventIds.DRIVE,
    InputControls.Create(
        new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE) }.ToJavaList(),
        new ArrayList<Integer>()),
    InputEnums.REMAP_OPTION_ENABLED
);

Одиночная клавиша InputAction отображается в наложении.

Действия также могут представлять действия мыши. В этом примере щелчок левой кнопкой мыши задается для действия «Переместить» :

Котлин

companion object {
  private val mouseInputAction = InputAction.create(
    "Move",
    InputActionsIds.MOUSE_MOVEMENT.ordinal.toLong(),
    InputControls.create(emptyList(), listOf(InputControls.MOUSE_LEFT_CLICK)),
    InputEnums.REMAP_OPTION_DISABLED)
}

Ява

private static final InputAction mouseInputAction = InputAction.create(
    "Move",
    InputActionsIds.MOUSE_MOVEMENT.ordinal(),
    InputControls.create(
            Collections.emptyList(),
            Collections.singletonList(InputControls.MOUSE_LEFT_CLICK)
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

С#

private static readonly InputAction mouseInputAction = InputAction.Create(
    "Move",
    (long)InputEventIds.MOUSE_MOVEMENT,
    InputControls.Create(
        new ArrayList<Integer>(),
        new[] { new Integer((int)PlayMouseAction.MouseLeftClick) }.ToJavaList()
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

Mouse InputAction отображается в наложении.

Комбинации клавиш задаются путем передачи нескольких кодов клавиш в ваш InputAction . В этом примере космос + сдвиг сопоставлено с действием Турбо , которое работает, даже если Космос сопоставлен с Диском .

Котлин

companion object {
  private val turboInputAction = InputAction.create(
    "Turbo",
    InputActionsIds.TURBO.ordinal.toLong(),
    InputControls.create(
      listOf(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SPACE),
      emptyList()),
    InputEnums.REMAP_OPTION_ENABLED)
}

Ява

private static final InputAction turboInputAction = InputAction.create(
    "Turbo",
    InputActionsIds.TURBO.ordinal(),
    InputControls.create(
            Arrays.asList(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SPACE),
            Collections.emptyList()
    ),
    InputEnums.REMAP_OPTION_ENABLED
);

С#

private static readonly InputAction turboInputAction = InputAction.Create(
    "Turbo",
    (long)InputEventIds.TURBO,
    InputControls.Create(
        new[]
        {
            new Integer(AndroidKeyCode.KEYCODE_SHIFT_LEFT),
            new Integer(AndroidKeyCode.KEYCODE_SPACE)
        }.ToJavaList(),
        new ArrayList<Integer>()),
    InputEnums.REMAP_OPTION_ENABLED
);

Многоклавишное действие InputAction отображается в наложении.

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

Котлин

companion object {
  private val addWaypointInputAction = InputAction.create(
    "Add waypoint",
    InputActionsIds.ADD_WAYPOINT.ordinal.toLong(),
    InputControls.create(
      listOf(KeyEvent.KeyEvent.KEYCODE_TAB),
      listOf(InputControls.MOUSE_RIGHT_CLICK)),
    InputEnums.REMAP_OPTION_DISABLED)
}

Ява

private static final InputAction addWaypointInputAction = InputAction.create(
    "Add waypoint",
    InputActionsIds.ADD_WAYPOINT.ordinal(),
    InputControls.create(
            Collections.singletonList(KeyEvent.KEYCODE_TAB),
            Collections.singletonList(InputControls.MOUSE_RIGHT_CLICK)
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

С#

private static readonly InputAction addWaypointInputAction = InputAction.Create(
    "Add waypoint",
    (long)InputEventIds.ADD_WAYPOINT,
    InputControls.Create(
        new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE) }.ToJavaList(),
        new[] { new Integer((int)PlayMouseAction.MouseRightClick) }.ToJavaList()
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

Комбинация клавиши + действия мыши InputAction отображается в наложении.

InputAction имеет следующие поля:

  • ActionLabel : строка, отображаемая в пользовательском интерфейсе для представления этого действия. Локализация не выполняется автоматически, поэтому выполняйте любую локализацию заранее.
  • InputControls : определяет элементы управления вводом, которые использует это действие. Элементы управления сопоставляются с единообразными глифами в наложении.
  • InputActionId : объект InputIdentifier , в котором хранится идентификатор номера и версия InputAction (дополнительную информацию см. в разделе «Идентификаторы ключей отслеживания» ).
  • InputRemappingOption : один из InputEnums.REMAP_OPTION_ENABLED или InputEnums.REMAP_OPTION_DISABLED . Определяет, разрешено ли для действия переназначение. Если ваша игра не поддерживает переназначение, вы можете пропустить это поле или просто отключить его.
  • RemappedInputControls : объект InputControls только для чтения, используемый для чтения переназначенного ключа, установленного пользователем при событиях переназначения (используется для получения уведомлений о событиях переназначения ).

InputControls представляет входные данные, связанные с действием, и содержит следующие поля:

  • AndroidKeycodes : список целых чисел, представляющих ввод с клавиатуры, связанный с действием. Они определены в классе KeyEvent или классе AndroidKeycode для Unity.
  • MouseActions : список значений MouseAction , представляющих ввод мыши, связанный с этим действием.

Определите свои группы ввода

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

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

Если вы поддерживаете переназначение, вы можете определить, какие InputGroups можно переназначить. Если ваша игра не поддерживает переназначение, вам следует отключить опцию переназначения для всех ваших InputGroups , но Input SDK достаточно умен, чтобы отключить переназначение, если вы не поддерживаете его в своем InputMap .

Котлин

companion object {
  private val menuInputGroup = InputGroup.create(
    "Menu keys",
    listOf(
      navigateUpInputAction,
      navigateLeftInputAction,
      navigateDownInputAction,
      navigateRightInputAction,
      openMenuInputAction,
      returnMenuInputAction),
    InputGroupsIds.MENU_ACTION_KEYS.ordinal.toLong(),
    InputEnums.REMAP_OPTION_ENABLED
  )
}

Ява

private static final InputGroup menuInputGroup = InputGroup.create(
    "Menu keys",
    Arrays.asList(
           navigateUpInputAction,
           navigateLeftInputAction,
           navigateDownInputAction,
           navigateRightInputAction,
           openMenuInputAction,
           returnMenuInputAction),
    InputGroupsIds.MENU_ACTION_KEYS.ordinal(),
    REMAP_OPTION_ENABLED
);

С#

private static readonly InputGroup menuInputGroup = InputGroup.Create(
    "Menu keys",
    new[]
    {
        navigateUpInputAction,
        navigateLeftInputAction,
        navigateDownInputAction,
        navigateRightInputAction,
        openMenuInputAction,
        returnMenuInputAction,
    }.ToJavaList(),
    (long)InputGroupsIds.MENU_ACTION_KEYS,
    InputEnums.REMAP_OPTION_ENABLED
);

В следующем примере показаны группы входов элементов управления «Дорога» и «Меню» в наложении:

Наложение, отображающее InputMap, содержащее элементы управления Road и Меню управляет группами входов.

InputGroup имеет следующие поля:

  • GroupLabel : строка, отображаемая в наложении, которую можно использовать для логической группировки набора действий. Эта строка не локализуется автоматически.
  • InputActions : список объектов InputAction , которые вы определили на предыдущем шаге. Все эти действия визуально отображаются под заголовком группы.
  • InputGroupId : объект InputIdentifier , который хранит идентификатор номера и версию InputGroup . Дополнительную информацию см. в разделе «Идентификаторы ключей отслеживания» .
  • InputRemappingOption : один из InputEnums.REMAP_OPTION_ENABLED или InputEnums.REMAP_OPTION_DISABLED . Если этот параметр отключен, для всех объектов InputAction принадлежащих к этой группе, переназначение будет отключено, даже если для них указана включенная опция переназначения. Если этот параметр включен, все действия, принадлежащие к этой группе, можно переназначать, если не указано иное, что они отключены отдельными действиями.

Определите контексты ввода

InputContexts позволяет вашей игре использовать разные наборы клавиш управления для разных сцен вашей игры. Например:

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

При использовании InputContexts наложение сначала показывает группы используемого контекста. Чтобы включить это поведение, вызовите setInputContext() чтобы установить контекст всякий раз, когда ваша игра входит в другую сцену. Следующее изображение демонстрирует такое поведение: в сцене «Вождение» действия элементов управления дорогой отображаются в верхней части наложения. При открытии меню «магазина» действия «Управление меню» отображаются вверху наложения.

Группы сортировки InputContexts в наложении.

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

  1. Группируйте свои InputActions с логически связанными действиями, используя InputGroups
  2. Назначьте эти InputGroups InputContext для разных частей вашей игры.

InputGroups , принадлежащие одному и тому же InputContext не могут иметь конфликтующих InputActions , в которых используется один и тот же ключ. Хорошей практикой является назначение каждой InputGroup одному InputContext .

Следующий пример кода демонстрирует логику InputContext :

Котлин

companion object {
  val menuSceneInputContext = InputContext.create(
    "Menu",
    InputIdentifier.create(
      INPUTMAP_VERSION,
      InputContextIds.MENU_SCENE.ordinal.toLong()),
    listOf(basicMenuNavigationInputGroup, menuActionsInputGroup))

  val gameSceneInputContext = InputContext.create(
    "Game",
    InputIdentifier.create(
      INPUTMAP_VERSION,
      InputContextIds.GAME_SCENE.ordinal.toLong()),
    listOf(
      movementInputGroup,
      mouseActionsInputGroup,
      emojisInputGroup,
      gameActionsInputGroup))
}

Ява

public static final InputContext menuSceneInputContext = InputContext.create(
        "Menu",
        InputIdentifier.create(
                INPUTMAP_VERSION,
                InputContextIds.MENU_SCENE.ordinal()),
        Arrays.asList(
                basicMenuNavigationInputGroup,
                menuActionsInputGroup
        )
);

public static final InputContext gameSceneInputContext = InputContext.create(
        "Game",
        InputIdentifier.create(
                INPUTMAP_VERSION,
                InputContextIds.GAME_SCENE.ordinal()),
        Arrays.asList(
                movementInputGroup,
                mouseActionsInputGroup,
                emojisInputGroup,
                gameActionsInputGroup
        )
);

С#

public static readonly InputContext menuSceneInputContext = InputContext.Create(
    "Menu",
    InputIdentifier.Create(
        INPUT_MAP_VERSION,
        (long)InputContextsIds.MENU_SCENE),
    new[]
    {
        basicMenuNavigationInputGroup,
        menuActionsInputGroup
    }.ToJavaList()
);

public static readonly InputContext gameSceneInputContext = InputContext.Create(
    "Game",
    InputIdentifier.Create(
        INPUT_MAP_VERSION,
        (long)InputContextsIds.GAME_SCENE),
    new[]
    {
        movementInputGroup,
        mouseActionsInputGroup,
        emojisInputGroup,
        gameActionsInputGroup
    }.ToJavaList()
);

InputContext имеет следующие поля:

  • LocalizedContextLabel : строка, описывающая группы, принадлежащие контексту.
  • InputContextId : объект InputIdentifier , в котором хранится идентификатор номера и версия InputContext (дополнительную информацию см. в разделе «Идентификаторы ключей отслеживания» ).
  • ActiveGroups : список InputGroups который будет использоваться и отображаться в верхней части наложения, когда этот контекст активен.

Создайте входную карту

InputMap — это коллекция всех объектов InputGroup , доступных в игре, и, следовательно, всех объектов InputAction которые игрок может ожидать выполнить.

Сообщая о своих привязках клавиш, вы создаете InputMap со всеми InputGroups используемыми в вашей игре.

Если ваша игра не поддерживает переназначение, отключите опцию переназначения и оставьте зарезервированные ключи пустыми.

В следующем примере создается InputMap используемый для отчета о коллекции InputGroups .

Котлин

companion object {
  val gameInputMap = InputMap.create(
    listOf(
      basicMenuNavigationInputGroup,
      menuActionKeysInputGroup,
      movementInputGroup,
      mouseMovementInputGroup,
      pauseMenuInputGroup),
    MouseSettings.create(true, false),
    InputIdentifier.create(INPUTMAP_VERSION, INPUT_MAP_ID.toLong()),
    InputEnums.REMAP_OPTION_ENABLED,
    // Use ESCAPE as reserved remapping key
    listof(InputControls.create(listOf(KeyEvent.KEYCODE_ESCAPE), emptyList()))
  )
}

Ява

public static final InputMap gameInputMap = InputMap.create(
        Arrays.asList(
                basicMenuNavigationInputGroup,
                menuActionKeysInputGroup,
                movementInputGroup,
                mouseMovementInputGroup,
                pauseMenuInputGroup),
        MouseSettings.create(true, false),
        InputIdentifier.create(INPUTMAP_VERSION, INPUT_MAP_ID),
        REMAP_OPTION_ENABLED,
        // Use ESCAPE as reserved remapping key
        Arrays.asList(
                InputControls.create(
                        Collections.singletonList(KeyEvent.KEYCODE_ESCAPE),
                        Collections.emptyList()
                )
        )
);

С#

public static readonly InputMap gameInputMap = InputMap.Create(
    new[]
    {
        basicMenuNavigationInputGroup,
        menuActionKeysInputGroup,
        movementInputGroup,
        mouseMovementInputGroup,
        pauseMenuInputGroup,
    }.ToJavaList(),
    MouseSettings.Create(true, false),
    InputIdentifier.Create(INPUT_MAP_VERSION, INPUT_MAP_ID),
    InputEnums.REMAP_OPTION_ENABLED,
    // Use ESCAPE as reserved remapping key
    new[]
    {
        InputControls.Create(
            New[] {
            new Integer(AndroidKeyCode.KEYCODE_ESCAPE)
        }.ToJavaList(),
        new ArrayList<Integer>())
    }.ToJavaList()
);

InputMap имеет следующие поля:

  • InputGroups : входные группы, о которых сообщает ваша игра. Группы отображаются в наложении по порядку, если не указаны текущие используемые группы при вызове setInputContext() .
  • MouseSettings : объект MouseSettings указывает, что чувствительность мыши можно регулировать и что мышь инвертирована по оси Y.
  • InputMapId : объект InputIdentifier , в котором хранится идентификатор номера и версия InputMap (дополнительную информацию см. в разделе «Идентификаторы ключей отслеживания» ).
  • InputRemappingOption : один из InputEnums.REMAP_OPTION_ENABLED или InputEnums.REMAP_OPTION_DISABLED . Определяет, включена ли функция переназначения.
  • ReservedControls : список InputControls , на которые пользователям не будет разрешено переназначать.

Отслеживать идентификаторы ключей

Объекты InputAction , InputGroup , InputContext и InputMap содержат объект InputIdentifier , который хранит уникальный числовой идентификатор и идентификатор версии строки. Отслеживание строковой версии ваших объектов не является обязательным, но рекомендуется отслеживать версии вашего InputMap . Если версия строки не указана, строка пуста. Для объектов InputMap требуется строковая версия.

В следующем примере строковая версия присваивается InputActions или InputGroups :

Котлин

class InputSDKProviderKotlin : InputMappingProvider {
  companion object {
    const val INPUTMAP_VERSION = "1.0.0"
    private val enterMenuInputAction = InputAction.create(
      "Enter menu",
      InputControls.create(listOf(KeyEvent.KEYCODE_ENTER), emptyList()),
      InputIdentifier.create(
        INPUTMAP_VERSION, InputActionsIds.ENTER_MENU.ordinal.toLong()),
      InputEnums.REMAP_OPTION_ENABLED
    )

    private val movementInputGroup  = InputGroup.create(
      "Basic movement",
      listOf(
        moveUpInputAction,
        moveLeftInputAction,
        moveDownInputAction,
        mouseGameInputAction),
      InputIdentifier.create(
        INPUTMAP_VERSION, InputGroupsIds.BASIC_MOVEMENT.ordinal.toLong()),
      InputEnums.REMAP_OPTION_ENABLED)
  }
}

Ява

public class InputSDKProvider implements InputMappingProvider {
    public static final String INPUTMAP_VERSION = "1.0.0";

    private static final InputAction enterMenuInputAction = InputAction.create(
            "Enter menu",
            InputControls.create(
                    Collections.singletonList(KeyEvent.KEYCODE_ENTER),
                    Collections.emptyList()),
            InputIdentifier.create(
                    INPUTMAP_VERSION, InputActionsIds.ENTER_MENU.ordinal()),
            InputEnums.REMAP_OPTION_ENABLED
    );

    private static final InputGroup movementInputGroup = InputGroup.create(
            "Basic movement",
            Arrays.asList(
                    moveUpInputAction,
                    moveLeftInputAction,
                    moveDownInputAction,
                    moveRightInputAction,
                    mouseGameInputAction
            ),
            InputIdentifier.create(
                    INPUTMAP_VERSION, InputGroupsIds.BASIC_MOVEMENT.ordinal()),
            InputEnums.REMAP_OPTION_ENABLED
    );
}

С#

#if PLAY_GAMES_PC

using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;

public class InputSDKMappingProvider : InputMappingProviderCallbackHelper
{
    public static readonly string INPUT_MAP_VERSION = "1.0.0";

    private static readonly InputAction enterMenuInputAction =
        InputAction.Create(
            "Enter menu",
            InputControls.Create(
                new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE)}.ToJavaList(),
                new ArrayList<Integer>()),
            InputIdentifier.Create(
                INPUT_MAP_VERSION,
                (long)InputEventIds.ENTER_MENU),
            InputEnums.REMAP_OPTION_ENABLED
        );

    private static readonly InputGroup movementInputGroup = InputGroup.Create(
        "Basic movement",
        new[]
        {
            moveUpInputAction,
            moveLeftInputAction,
            moveDownInputAction,
            moveRightInputAction,
            mouseGameInputAction
        }.ToJavaList(),
        InputIdentifier.Create(
            INPUT_MAP_VERSION,
            (long)InputGroupsIds.BASIC_MOVEMENT),
        InputEnums.REMAP_OPTION_ENABLED
    );
}
#endif

Идентификаторы номеров объектов InputAction должны быть уникальными для всех InputActions в вашем InputMap . Аналогично, идентификаторы объектов InputGroup должны быть уникальными для всех InputGroups в InputMap . В следующем примере показано, как использовать enum для отслеживания уникальных идентификаторов объекта:

Котлин

enum class InputActionsIds {
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

enum class InputGroupsIds {
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

enum class InputContextIds {
    MENU_SCENE, // Basic menu navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

const val INPUT_MAP_ID = 0

Ява

public enum InputActionsIds {
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

public enum InputGroupsIds {
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

public enum InputContextIds {
    MENU_SCENE, // Basic navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

public static final long INPUT_MAP_ID = 0;

С#

public enum InputActionsIds
{
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

public enum InputGroupsIds
{
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

public enum InputContextIds
{
    MENU_SCENE, // Basic navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

public static readonly long INPUT_MAP_ID = 0;

InputIdentifier имеет следующие поля:

  • UniqueId : уникальный номер идентификатора, установленный для однозначной идентификации данного набора входных данных.
  • VersionString : удобочитаемая строка версии, установленная для идентификации версии входных данных между двумя версиями изменений входных данных.

Получать уведомления о событиях переназначения (необязательно)

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

На следующем изображении показан пример такого поведения, когда после переназначения клавиш Г , П и С к Дж , Х и Т соответственно, элементы пользовательского интерфейса игры обновляются для отображения клавиш, установленных пользователем.

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

Эта функциональность достигается путем регистрации обратного вызова InputRemappingListener . Чтобы реализовать эту функцию, начните с регистрации экземпляра InputRemappingListener :

Котлин

class InputSDKRemappingListener : InputRemappingListener {
  override fun onInputMapChanged(inputMap: InputMap) {
    Log.i(TAG, "Received update on input map changed.")
    if (inputMap.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) {
      return
    }
    for (inputGroup in inputMap.inputGroups()) {
      if (inputGroup.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) {
        continue
      }
      for (inputAction in inputGroup.inputActions()) {
        if (inputAction.inputRemappingOption() != InputEnums.REMAP_OPTION_DISABLED) {
          // Found InputAction remapped by user
          processRemappedAction(inputAction)
        }
      }
    }
  }

  private fun processRemappedAction(remappedInputAction: InputAction) {
    // Get remapped action info
    val remappedControls = remappedInputAction.remappedInputControls()
    val remappedKeyCodes = remappedControls.keycodes()
    val mouseActions = remappedControls.mouseActions()
    val version = remappedInputAction.inputActionId().versionString()
    val remappedActionId = remappedInputAction.inputActionId().uniqueId()
    val currentInputAction: Optional<InputAction>
    currentInputAction = if (version == null || version.isEmpty()
      || version == InputSDKProvider.INPUTMAP_VERSION
    ) {
      getCurrentVersionInputAction(remappedActionId)
    } else {
      Log.i(TAG,
            "Detected version of user-saved input action defers from current version")
      getCurrentVersionInputActionFromPreviousVersion(
        remappedActionId, version)
    }
    if (!currentInputAction.isPresent) {
      Log.e(TAG, String.format(
        "can't find remapped input action with id %d and version %s",
        remappedActionId, if (version == null || version.isEmpty()) "UNKNOWN" else version))
      return
    }
    val originalControls = currentInputAction.get().inputControls()
    val originalKeyCodes = originalControls.keycodes()
    Log.i(TAG, String.format(
      "Found input action with id %d remapped from key %s to key %s",
      remappedActionId,
      keyCodesToString(originalKeyCodes),
      keyCodesToString(remappedKeyCodes)))

    // TODO: make display changes to match controls used by the user
  }

  private fun getCurrentVersionInputAction(inputActionId: Long): Optional<InputAction> {
    for (inputGroup in InputSDKProvider.gameInputMap.inputGroups()) {
      for (inputAction in inputGroup.inputActions()) {
        if (inputAction.inputActionId().uniqueId() == inputActionId) {
          return Optional.of(inputAction)
        }
      }
    }
    return Optional.empty()
  }

  private fun getCurrentVersionInputActionFromPreviousVersion(
    inputActionId: Long, previousVersion: String
  ): Optional<InputAction7gt; {
    // TODO: add logic to this method considering the diff between the current and previous
    //  InputMap.
    return Optional.empty()
  }

  private fun keyCodesToString(keyCodes: List<Int>): String {
    val builder = StringBuilder()
    for (keyCode in keyCodes) {
      if (!builder.toString().isEmpty()) {
        builder.append(" + ")
      }
      builder.append(keyCode)
    }
    return String.format("(%s)", builder)
  }

  companion object {
    private const val TAG = "InputSDKRemappingListener"
  }
}

Ява

public class InputSDKRemappingListener implements InputRemappingListener {

    private static final String TAG = "InputSDKRemappingListener";

    @Override
    public void onInputMapChanged(InputMap inputMap) {
        Log.i(TAG, "Received update on input map changed.");
        if (inputMap.inputRemappingOption() ==
                InputEnums.REMAP_OPTION_DISABLED) {
            return;
        }
        for (InputGroup inputGroup : inputMap.inputGroups()) {
            if (inputGroup.inputRemappingOption() ==
                    InputEnums.REMAP_OPTION_DISABLED) {
                continue;
            }
            for (InputAction inputAction : inputGroup.inputActions()) {
                if (inputAction.inputRemappingOption() !=
                        InputEnums.REMAP_OPTION_DISABLED) {
                    // Found InputAction remapped by user
                    processRemappedAction(inputAction);
                }
            }
        }
    }

    private void processRemappedAction(InputAction remappedInputAction) {
        // Get remapped action info
        InputControls remappedControls =
            remappedInputAction.remappedInputControls();
        List<Integer> remappedKeyCodes = remappedControls.keycodes();
        List<Integer> mouseActions = remappedControls.mouseActions();
        String version = remappedInputAction.inputActionId().versionString();
        long remappedActionId = remappedInputAction.inputActionId().uniqueId();
        Optional<InputAction> currentInputAction;
        if (version == null || version.isEmpty()
                    || version.equals(InputSDKProvider.INPUTMAP_VERSION)) {
            currentInputAction = getCurrentVersionInputAction(remappedActionId);
        } else {
            Log.i(TAG, "Detected version of user-saved input action defers " +
                    "from current version");
            currentInputAction =
                    getCurrentVersionInputActionFromPreviousVersion(
                            remappedActionId, version);
        }
        if (!currentInputAction.isPresent()) {
            Log.e(TAG, String.format(
                    "input action with id %d and version %s not found",
                    remappedActionId, version == null || version.isEmpty() ?
                            "UNKNOWN" : version));
            return;
        }
        InputControls originalControls =
                currentInputAction.get().inputControls();
        List<Integer> originalKeyCodes = originalControls.keycodes();

        Log.i(TAG, String.format(
                "Found input action with id %d remapped from key %s to key %s",
                remappedActionId,
                keyCodesToString(originalKeyCodes),
                keyCodesToString(remappedKeyCodes)));

        // TODO: make display changes to match controls used by the user
    }

    private Optional<InputAction> getCurrentVersionInputAction(
            long inputActionId) {
        for (InputGroup inputGroup :
                    InputSDKProvider.gameInputMap.inputGroups()) {
            for (InputAction inputAction : inputGroup.inputActions()) {
                if (inputAction.inputActionId().uniqueId() == inputActionId) {
                    return Optional.of(inputAction);
                }
            }
        }
        return Optional.empty();
    }

    private Optional<InputAction>
            getCurrentVersionInputActionFromPreviousVersion(
                    long inputActionId, String previousVersion) {
        // TODO: add logic to this method considering the diff between your
        // current and previous InputMap.
        return Optional.empty();
    }

    private String keyCodesToString(List<Integer> keyCodes) {
        StringBuilder builder = new StringBuilder();
        for (Integer keyCode : keyCodes) {
            if (!builder.toString().isEmpty()) {
                builder.append(" + ");
            }
            builder.append(keyCode);
        }
        return String.format("(%s)", builder);
    }
}

С#

#if PLAY_GAMES_PC

using System.Text;
using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;
using UnityEngine;

public class InputSDKRemappingListener : InputRemappingListenerCallbackHelper
{
    public override void OnInputMapChanged(InputMap inputMap)
    {
        Debug.Log("Received update on remapped controls.");
        if (inputMap.InputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED)
        {
            return;
        }
        List<InputGroup> inputGroups = inputMap.InputGroups();
        for (int i = 0; i < inputGroups.Size(); i ++)
        {
            InputGroup inputGroup = inputGroups.Get(i);
            if (inputGroup.InputRemappingOption()
                    == InputEnums.REMAP_OPTION_DISABLED)
            {
                continue;
            }
            List<InputAction> inputActions = inputGroup.InputActions();
            for (int j = 0; j < inputActions.Size(); j ++)
            {
                InputAction inputAction = inputActions.Get(j);
                if (inputAction.InputRemappingOption()
                        != InputEnums.REMAP_OPTION_DISABLED)
                {
                    // Found action remapped by user
                    ProcessRemappedAction(inputAction);
                }
            }
        }
    }

    private void ProcessRemappedAction(InputAction remappedInputAction)
    {
        InputControls remappedInputControls =
                remappedInputAction.RemappedInputControls();
        List<Integer> remappedKeycodes = remappedInputControls.Keycodes();
        List<Integer> mouseActions = remappedInputControls.MouseActions();
        string version = remappedInputAction.InputActionId().VersionString();
        long remappedActionId = remappedInputAction.InputActionId().UniqueId();
        InputAction currentInputAction;
        if (string.IsNullOrEmpty(version)
                || string.Equals(
                version, InputSDKMappingProvider.INPUT_MAP_VERSION))
        {
            currentInputAction = GetCurrentVersionInputAction(remappedActionId);
        }
        else
        {
            Debug.Log("Detected version of used-saved input action defers" +
                " from current version");
            currentInputAction =
                GetCurrentVersionInputActionFromPreviousVersion(
                    remappedActionId, version);
        }
        if (currentInputAction == null)
        {
            Debug.LogError(string.Format(
                "Input Action with id {0} and version {1} not found",
                remappedActionId,
                string.IsNullOrEmpty(version) ? "UNKNOWN" : version));
            return;
        }
        InputControls originalControls = currentInputAction.InputControls();
        List<Integer> originalKeycodes = originalControls.Keycodes();

        Debug.Log(string.Format(
            "Found Input Action with id {0} remapped from key {1} to key {2}",
            remappedActionId,
            KeyCodesToString(originalKeycodes),
            KeyCodesToString(remappedKeycodes)));
        // TODO: update HUD according to the controls of the user
    }

    private InputAction GetCurrentVersionInputAction(
            long inputActionId)
    {
        List<InputGroup> inputGroups =
            InputSDKMappingProvider.gameInputMap.InputGroups();
        for (int i = 0; i < inputGroups.Size(); i++)
        {
            InputGroup inputGroup = inputGroups.Get(i);
            List<InputAction> inputActions = inputGroup.InputActions();
            for (int j = 0; j < inputActions.Size(); j++)
            {
                InputAction inputAction = inputActions.Get(j);
                if (inputAction.InputActionId().UniqueId() == inputActionId)
                {
                    return inputAction;
                }
            }
        }
        return null;
    }

    private InputAction GetCurrentVersionInputActionFromPreviousVersion(
            long inputActionId, string version)
    {
        // TODO: add logic to this method considering the diff between your
        // current and previous InputMap.
        return null;
    }

    private string KeyCodesToString(List<Integer> keycodes)
    {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < keycodes.Size(); i ++)
        {
            Integer keycode = keycodes.Get(i);
            if (builder.Length > 0)
            {
                builder.Append(" + ");
            }
            builder.Append(keycode.IntValue());
        }
        return string.Format("({0})", builder.ToString());
    }
}
#endif

InputRemappingListener уведомляется во время запуска после загрузки сохраненных пользователем переназначенных элементов управления и после каждого переназначения пользователем своих ключей.

Инициализация

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

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

В следующем примере показано, как инициализировать API:

Котлин

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (isGooglePlayGamesOnPC()) {
        val inputMappingClient = Input.getInputMappingClient(this)
        // Register listener before registering the provider
        inputMappingClient.registerRemappingListener(InputSDKRemappingListener())
        inputMappingClient.setInputMappingProvider(
                InputSDKProvider())
        // Set the context after you have registered the provider.
        inputMappingClient.setInputContext(InputSDKProvider.menuSceneInputContext)
    }
}

Ява

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (isGooglePlayGamesOnPC()) {
        InputMappingClient inputMappingClient =
                Input.getInputMappingClient(this);
        // Register listener before registering the provider
        inputMappingClient.registerRemappingListener(
                new InputSDKRemappingListener());
        inputMappingClient.setInputMappingProvider(
                new InputSDKProvider());
        // Set the context after you have registered the provider
        inputMappingClient.setInputContext(InputSDKProvider.menuSceneInputContext);
    }
}

С#

#if PLAY_GAMES_PC
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.InputMapping.ExternalType.Android.Content;
using Google.LibraryWrapper.Java;
#endif

public class GameManager : MonoBehaviour
{
#if PLAY_GAMES_PC
    private InputSDKMappingProvider _inputMapProvider =
        new InputSDKMappingProvider();
    private InputMappingClient _inputMappingClient;
#endif

    public void Awake()
    {
#if PLAY_GAMES_PC
        Context context = (Context)Utils.GetUnityActivity().GetRawObject();
        _inputMappingClient = Google.Android.Libraries.Play.Games.Inputmapping
            .Input.GetInputMappingClient(context);
        // Register listener before registering the provider.
        _inputMappingClient.RegisterRemappingListener(
            new InputSDKRemappingListener());
        _inputMappingClient.SetInputMappingProvider(_inputMapProvider);
        // Register context after you have registered the provider.
       _inputMappingClient.SetInputContext(
           InputSDKMappingProvider.menuSceneInputContext);
#endif
    }
}

Очистить

Отмените регистрацию экземпляра InputMappingProvider и всех экземпляров InputRemappingListener , когда ваша игра закрыта, хотя Input SDK достаточно умен, чтобы избежать утечки ресурсов, если вы этого не сделаете:

Котлин

override fun onDestroy() {
    if (isGooglePlayGamesOnPC()) {
        val inputMappingClient = Input.getInputMappingClient(this)
        inputMappingClient.clearInputMappingProvider()
        inputMappingClient.clearRemappingListener()
    }

    super.onDestroy()
}

Ява

@Override
protected void onDestroy() {
    if (isGooglePlayGamesOnPC()) {
        InputMappingClient inputMappingClient =
                Input.getInputMappingClient(this);
        inputMappingClient.clearInputMappingProvider();
        inputMappingClient.clearRemappingListener();
    }

    super.onDestroy();
}

С#

public class GameManager : MonoBehaviour
{
    private void OnDestroy()
    {
#if PLAY_GAMES_PC
        _inputMappingClient.ClearInputMappingProvider();
        _inputMappingClient.ClearRemappingListener();
#endif
    }
}

Тест

Вы можете протестировать реализацию Input SDK, вручную открыв оверлей , чтобы просмотреть работу проигрывателя, или через оболочку adb для автоматического тестирования и проверки.

Эмулятор Google Play Games для ПК проверяет правильность вашей входной карты на предмет распространенных ошибок. В таких сценариях, как дублирование уникальных идентификаторов, использование разных входных карт или сбой в правилах переназначения (если переназначение включено), наложение отображает сообщение об ошибке, как показано ниже: Оверлей входного SDK.

Проверьте реализацию входного SDK, используя adb в командной строке. Чтобы получить текущую входную карту, используйте следующую команду adb shell (замените MY.PACKAGE.NAME на название вашей игры):

adb shell dumpsys input_mapping_service --get MY.PACKAGE.NAME

Вы увидите вывод, аналогичный этому, если вы успешно зарегистрировали свой InputMap :

Getting input map for com.example.inputsample...
Successfully received the following inputmap:
# com.google.android.libraries.play.games.InputMap@d73526e1
input_groups {
  group_label: "Basic Movement"
  input_actions {
    action_label: "Jump"
    input_controls {
      keycodes: 51
      keycodes: 19
    }
    unique_id: 0
  }
  input_actions {
    action_label: "Left"
    input_controls {
      keycodes: 29
      keycodes: 21
    }
    unique_id: 1
  }
  input_actions {
    action_label: "Right"
    input_controls {
      keycodes: 32
      keycodes: 22
    }
    unique_id: 2
  }
  input_actions {
    action_label: "Use"
    input_controls {
      keycodes: 33
      keycodes: 66
      mouse_actions: MOUSE_LEFT_CLICK
      mouse_actions_value: 0
    }
    unique_id: 3
  }
}
input_groups {
  group_label: "Special Input"
  input_actions {
    action_label: "Jump"
    input_controls {
      keycodes: 51
      keycodes: 19
      keycodes: 62
      mouse_actions: MOUSE_LEFT_CLICK
      mouse_actions_value: 0
    }
    unique_id: 4
  }
  input_actions {
    action_label: "Duck"
    input_controls {
      keycodes: 47
      keycodes: 20
      keycodes: 113
      mouse_actions: MOUSE_RIGHT_CLICK
      mouse_actions_value: 1
    }
    unique_id: 5
  }
}
mouse_settings {
  allow_mouse_sensitivity_adjustment: true
  invert_mouse_movement: true
}

Локализация

Входной SDK не использует систему локализации Android. В результате вы должны предоставить локализованные строки при отправке InputMap . Вы также можете использовать систему локализации вашего игрового движка.

Прогард

При использовании Proguard для минимизации игры добавьте следующие правила в файл конфигурации proguard, чтобы гарантировать, что SDK не будет удален из вашего окончательного пакета:

-keep class com.google.android.libraries.play.hpe.** { *; }
-keep class com.google.android.libraries.play.games.inputmapping.** { *; }

Что дальше

После того как вы интегрируете Input SDK в свою игру, вы сможете продолжить работу с оставшимися требованиями Google Play Games для ПК. Дополнительную информацию см. в разделе Начало работы с Google Play Games на ПК .

,

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

Прежде чем начать

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

Input SDK предоставляет Google Play Games на ПК информацию о том, какие элементы управления используются в вашей игре, чтобы их можно было отобразить пользователю. Он также может дополнительно разрешить переназначение клавиатуры для пользователей.

Каждый элемент управления представляет собой InputAction (например, «J» для «Jump»), и вы организуете свои InputActions в InputGroups . Группа InputGroup может представлять другой режим в вашей игре, например «Вождение», «Ходьба» или «Главное меню». Вы также можете использовать InputContexts , чтобы указать, какие группы активны в разные моменты игры.

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

Следующая диаграмма последовательности описывает, как работает API входного SDK:

Диаграмма последовательности реализации игры, которая вызывает API входного SDK и его взаимодействие с Android устройство.

Если в вашей игре реализован входной SDK, ваши элементы управления отображаются в оверлее Google Play Games on PC.

Оверлей Google Play Games на ПК

Наложение «Google Play Игры для ПК» («наложение») отображает элементы управления, определенные вашей игрой. Пользователи получают доступ к наложению в любое время, нажав Shift + Tab .

Наложение Google Play Games на ПК.

Лучшие практики по разработке привязок клавиш

При разработке привязок клавиш учитывайте следующие рекомендации:

  • Группируйте свои InputActions в логически связанные InputGroups чтобы улучшить навигацию и удобство обнаружения элементов управления во время игры.
  • Назначьте каждую InputGroup не более чем одному InputContext . Детализированная InputMap обеспечивает более удобную навигацию по элементам управления в наложении.
  • Создайте InputContext для каждого типа сцены вашей игры. Обычно вы можете использовать один InputContext для всех сцен, похожих на меню. Используйте разные InputContexts для любых мини-игр в вашей игре или для альтернативных элементов управления для одной сцены.
  • Если два действия предназначены для использования одной и той же клавиши в одном и том же InputContext , используйте строку метки, например «Взаимодействие/Пожар».
  • Если две клавиши предназначены для привязки к одному и тому же InputAction , используйте два разных InputActions , которые выполняют одно и то же действие в вашей игре. Вы можете использовать одну и ту же строку метки для обоих InputActions , но ее идентификатор должен быть разным.
  • Если к набору клавиш применяется клавиша-модификатор, рассмотрите возможность использования одного InputAction с клавишей-модификатором вместо нескольких InputActions , объединяющих клавишу-модификатор (пример: используйте Shift и W, A, S, D вместо Shift + W, Shift + А, Shift+S, Shift+D ).
  • Перераспределение ввода автоматически отключается, когда пользователь пишет в текстовые поля. Следуйте рекомендациям по реализации текстовых полей Android, чтобы Android мог обнаруживать текстовые поля в вашей игре и не допускать взаимодействия с ними переназначенных клавиш. Если в вашей игре необходимо использовать нестандартные текстовые поля, вы можете использовать setInputContext() с InputContext содержащим пустой список InputGroups , чтобы отключить переназначение вручную.
  • Если ваша игра поддерживает переназначение, рассмотрите возможность обновления привязок клавиш — это конфиденциальная операция, которая может конфликтовать с сохраненными пользователем версиями. По возможности избегайте изменения идентификаторов существующих элементов управления.

Функция переназначения

Google Play Games для ПК поддерживает переназначение элементов управления клавиатурой на основе привязок клавиш, предоставляемых вашей игрой с помощью Input SDK. Это необязательно и может быть полностью отключено. Например, вы можете предоставить собственный интерфейс переназначения клавиатуры. Чтобы отключить переназначение для вашей игры, вам просто нужно указать опцию переназначения, отключенную для вашей InputMap (дополнительную информацию см. в разделе Создание карты ввода ).

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

Попытка переназначить ключ

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

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

Ограничения переназначения

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

  • InputActions с несколькими клавишами, которые не состоят из клавиши-модификатора + клавиши-немодификатора. Например, Shift+A допустимо, а A+B , Ctrl+Alt или Shift+A+Tab — нет.
  • InputMap содержит InputActions , InputGroups или InputContexts с повторяющимися уникальными идентификаторами.

Ограничения переназначения

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

  • Переназначение комбинаций клавиш не поддерживается. Например, пользователи не могут переназначить Shift+A на Ctrl+B или A на Shift+A .
  • Переназначение не поддерживается для InputActions с помощью кнопок мыши. Например, Shift + щелчок правой кнопкой мыши нельзя переназначить.

Тестовое переназначение клавиш в эмуляторе Google Play Games на ПК

Вы можете включить функцию переназначения в эмуляторе Google Play Games на ПК в любое время, введя следующую команду adb:

adb shell dumpsys input_mapping_service --set RemappingFlagValue true

Наложение изменится, как показано на следующем изображении:

Оверлей с включенным переназначением клавиш.

Добавить SDK

Установите Input SDK в соответствии с вашей платформой разработки.

Ява и Котлин

Получите входной SDK для Java или Kotlin, добавив зависимость в файл build.gradle на уровне модуля:

dependencies {
  implementation 'com.google.android.libraries.play.games:inputmapping:1.1.1-beta'
  ...
}

Единство

Входной SDK — это стандартный пакет Unity с несколькими зависимостями.

Требуется установка пакета со всеми зависимостями. Существует несколько способов установки пакетов.

Установите пакет .unitypackage

Загрузите файл unitypackage входного SDK со всеми его зависимостями. Вы можете установить пакет .unitypackage , выбрав «Ресурсы» > «Импортировать пакет» > «Пользовательский пакет» и найдя загруженный файл.

Установить с помощью UPM

Альтернативно вы можете установить пакет с помощью диспетчера пакетов Unity , загрузив .tgz и установив его зависимости:

Установить с помощью OpenUPM

Вы можете установить пакет с помощью OpenUPM .

$ openupm add com.google.android.libraries.play.games.inputmapping

Примеры игр

Примеры интеграции с Input SDK см. в разделах AGDK Tunnel для игр Kotlin или Java и Trivial Kart для игр Unity.

Создайте свои привязки клавиш

Зарегистрируйте привязки клавиш, создав InputMap и вернув его с помощью InputMappingProvider . В следующем примере описывается InputMappingProvider :

Котлин

class InputSDKProvider : InputMappingProvider {
  override fun onProvideInputMap(): InputMap {
    TODO("Not yet implemented")
  }
}

Ява

public class InputSDKProvider implements InputMappingProvider {
    private static final String INPUTMAP_VERSION = "1.0.0";

    @Override
    @NonNull
    public InputMap onProvideInputMap() {
        // TODO: return an InputMap
    }
}

С#

#if PLAY_GAMES_PC
using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;

public class InputSDKProvider : InputMappingProviderCallbackHelper
{
    public static readonly string INPUT_MAP_VERSION = "1.0.0";

    public override InputMap OnProvideInputMap()
    {
        // TODO: return an InputMap
    }
}
#endif

Определите действия ввода

Класс InputAction используется для сопоставления клавиши или комбинации клавиш с игровым действием. InputActions должны иметь уникальные идентификаторы для всех InputActions .

Если вы поддерживаете переназначение, вы можете определить, какие InputActions можно переназначить. Если ваша игра не поддерживает переназначение, вам следует отключить опцию переназначения для всех ваших InputActions , но Input SDK достаточно умен, чтобы отключить переназначение, если вы не поддерживаете его в своем InputMap .

Этот пример отображает космос ключ к действию «Драйв» .

Котлин

companion object {
  private val driveInputAction = InputAction.create(
    "Drive",
    InputActionsIds.DRIVE.ordinal.toLong(),
    InputControls.create(listOf(KeyEvent.KEYCODE_SPACE), emptyList()),
    InputEnums.REMAP_OPTION_ENABLED)
}

Ява

private static final InputAction driveInputAction = InputAction.create(
    "Drive",
    InputEventIds.DRIVE.ordinal(),
    InputControls.create(
            Collections.singletonList(KeyEvent.KEYCODE_SPACE),
            Collections.emptyList()),
    InputEnums.REMAP_OPTION_ENABLED
);

С#

private static readonly InputAction driveInputAction = InputAction.Create(
    "Drive",
    (long)InputEventIds.DRIVE,
    InputControls.Create(
        new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE) }.ToJavaList(),
        new ArrayList<Integer>()),
    InputEnums.REMAP_OPTION_ENABLED
);

Одиночная клавиша InputAction отображается в наложении.

Действия также могут представлять действия мыши. В этом примере щелчок левой кнопкой мыши задается для действия «Переместить» :

Котлин

companion object {
  private val mouseInputAction = InputAction.create(
    "Move",
    InputActionsIds.MOUSE_MOVEMENT.ordinal.toLong(),
    InputControls.create(emptyList(), listOf(InputControls.MOUSE_LEFT_CLICK)),
    InputEnums.REMAP_OPTION_DISABLED)
}

Ява

private static final InputAction mouseInputAction = InputAction.create(
    "Move",
    InputActionsIds.MOUSE_MOVEMENT.ordinal(),
    InputControls.create(
            Collections.emptyList(),
            Collections.singletonList(InputControls.MOUSE_LEFT_CLICK)
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

С#

private static readonly InputAction mouseInputAction = InputAction.Create(
    "Move",
    (long)InputEventIds.MOUSE_MOVEMENT,
    InputControls.Create(
        new ArrayList<Integer>(),
        new[] { new Integer((int)PlayMouseAction.MouseLeftClick) }.ToJavaList()
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

Mouse InputAction отображается в наложении.

Комбинации клавиш задаются путем передачи нескольких кодов клавиш в ваш InputAction . В этом примере космос + сдвиг сопоставлено с действием Турбо , которое работает, даже если Космос сопоставлен с Диском .

Котлин

companion object {
  private val turboInputAction = InputAction.create(
    "Turbo",
    InputActionsIds.TURBO.ordinal.toLong(),
    InputControls.create(
      listOf(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SPACE),
      emptyList()),
    InputEnums.REMAP_OPTION_ENABLED)
}

Ява

private static final InputAction turboInputAction = InputAction.create(
    "Turbo",
    InputActionsIds.TURBO.ordinal(),
    InputControls.create(
            Arrays.asList(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SPACE),
            Collections.emptyList()
    ),
    InputEnums.REMAP_OPTION_ENABLED
);

С#

private static readonly InputAction turboInputAction = InputAction.Create(
    "Turbo",
    (long)InputEventIds.TURBO,
    InputControls.Create(
        new[]
        {
            new Integer(AndroidKeyCode.KEYCODE_SHIFT_LEFT),
            new Integer(AndroidKeyCode.KEYCODE_SPACE)
        }.ToJavaList(),
        new ArrayList<Integer>()),
    InputEnums.REMAP_OPTION_ENABLED
);

Многоклавишное действие InputAction отображается в наложении.

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

Котлин

companion object {
  private val addWaypointInputAction = InputAction.create(
    "Add waypoint",
    InputActionsIds.ADD_WAYPOINT.ordinal.toLong(),
    InputControls.create(
      listOf(KeyEvent.KeyEvent.KEYCODE_TAB),
      listOf(InputControls.MOUSE_RIGHT_CLICK)),
    InputEnums.REMAP_OPTION_DISABLED)
}

Ява

private static final InputAction addWaypointInputAction = InputAction.create(
    "Add waypoint",
    InputActionsIds.ADD_WAYPOINT.ordinal(),
    InputControls.create(
            Collections.singletonList(KeyEvent.KEYCODE_TAB),
            Collections.singletonList(InputControls.MOUSE_RIGHT_CLICK)
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

С#

private static readonly InputAction addWaypointInputAction = InputAction.Create(
    "Add waypoint",
    (long)InputEventIds.ADD_WAYPOINT,
    InputControls.Create(
        new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE) }.ToJavaList(),
        new[] { new Integer((int)PlayMouseAction.MouseRightClick) }.ToJavaList()
    ),
    InputEnums.REMAP_OPTION_DISABLED
);

Комбинация клавиши + мыши InputAction отображается в наложении.

InputAction имеет следующие поля:

  • ActionLabel : строка, отображаемая в пользовательском интерфейсе для представления этого действия. Локализация не выполняется автоматически, поэтому выполняйте любую локализацию заранее.
  • InputControls : определяет элементы управления вводом, которые использует это действие. Элементы управления сопоставляются с согласованными глифами в наложении.
  • InputActionId : объект InputIdentifier , в котором хранится идентификатор номера и версия InputAction (дополнительную информацию см. в разделе «Идентификаторы ключей отслеживания» ).
  • InputRemappingOption : один из InputEnums.REMAP_OPTION_ENABLED или InputEnums.REMAP_OPTION_DISABLED . Определяет, разрешено ли для действия переназначение. Если ваша игра не поддерживает переназначение, вы можете пропустить это поле или просто отключить его.
  • RemappedInputControls : объект InputControls только для чтения, используемый для чтения переназначенного ключа, установленного пользователем при событиях переназначения (используется для получения уведомлений о событиях переназначения ).

InputControls представляет входные данные, связанные с действием, и содержит следующие поля:

  • AndroidKeycodes : список целых чисел, представляющих ввод с клавиатуры, связанный с действием. Они определены в классе KeyEvent или классе AndroidKeycode для Unity.
  • MouseActions : список значений MouseAction , представляющих ввод мыши, связанный с этим действием.

Определите свои группы ввода

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

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

Если вы поддерживаете переназначение, вы можете определить, какие InputGroups можно переназначить. Если ваша игра не поддерживает переназначение, вам следует отключить опцию переназначения для всех ваших InputGroups , но Input SDK достаточно умен, чтобы отключить переназначение, если вы не поддерживаете его в своем InputMap .

Котлин

companion object {
  private val menuInputGroup = InputGroup.create(
    "Menu keys",
    listOf(
      navigateUpInputAction,
      navigateLeftInputAction,
      navigateDownInputAction,
      navigateRightInputAction,
      openMenuInputAction,
      returnMenuInputAction),
    InputGroupsIds.MENU_ACTION_KEYS.ordinal.toLong(),
    InputEnums.REMAP_OPTION_ENABLED
  )
}

Ява

private static final InputGroup menuInputGroup = InputGroup.create(
    "Menu keys",
    Arrays.asList(
           navigateUpInputAction,
           navigateLeftInputAction,
           navigateDownInputAction,
           navigateRightInputAction,
           openMenuInputAction,
           returnMenuInputAction),
    InputGroupsIds.MENU_ACTION_KEYS.ordinal(),
    REMAP_OPTION_ENABLED
);

С#

private static readonly InputGroup menuInputGroup = InputGroup.Create(
    "Menu keys",
    new[]
    {
        navigateUpInputAction,
        navigateLeftInputAction,
        navigateDownInputAction,
        navigateRightInputAction,
        openMenuInputAction,
        returnMenuInputAction,
    }.ToJavaList(),
    (long)InputGroupsIds.MENU_ACTION_KEYS,
    InputEnums.REMAP_OPTION_ENABLED
);

В следующем примере показаны группы входов элементов управления «Дорога» и «Меню» в наложении:

Наложение, отображающее InputMap, содержащее элементы управления Road и Меню управляет группами входов.

InputGroup имеет следующие поля:

  • GroupLabel : строка, отображаемая в наложении, которую можно использовать для логической группировки набора действий. Эта строка не локализуется автоматически.
  • InputActions : список объектов InputAction , которые вы определили на предыдущем шаге. Все эти действия визуально отображаются под заголовком группы.
  • InputGroupId : объект InputIdentifier , который хранит идентификатор номера и версию InputGroup . Дополнительную информацию см. в разделе «Идентификаторы ключей отслеживания» .
  • InputRemappingOption : один из InputEnums.REMAP_OPTION_ENABLED или InputEnums.REMAP_OPTION_DISABLED . Если отключено, все объекты InputAction принадлежащие этой группе, будут иметь переиздание отключено, даже если они указывают его опцию переработки. Если включено, все действия, принадлежащие к этой группе, могут быть переназначены, если не указано отключено отдельными действиями.

Определите свои входные контексты

InputContexts позволяет вашей игре использовать другой набор элементов управления клавиатуры для разных сцен вашей игры. Например:

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

При использовании InputContexts наложение показывает сначала группы используемого контекста. Чтобы включить это поведение, вызовите setInputContext() чтобы установить контекст всякий раз, когда ваша игра входит в другую сцену. Следующее изображение демонстрирует это поведение: в сцене «вождение» действия управления дорожным управлением показаны в верхней части наложения. При открытии меню «Магазин» действия «Управление меню» отображаются в верхней части наложения.

Входные группы сортируют группы в наложении.

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

  1. Группируйте ваши InputActions с логически связанными действиями с использованием InputGroups
  2. Назначьте эти InputGroups в InputContext для различных частей вашей игры

InputGroups , принадлежащие к одному и тому же InputContext не могут иметь конфликтующие InputActions , в которых используется один и тот же ключ. Хорошая практика назначать каждую InputGroup одному InputContext .

Следующий пример кода демонстрирует логику InputContext :

Котлин

companion object {
  val menuSceneInputContext = InputContext.create(
    "Menu",
    InputIdentifier.create(
      INPUTMAP_VERSION,
      InputContextIds.MENU_SCENE.ordinal.toLong()),
    listOf(basicMenuNavigationInputGroup, menuActionsInputGroup))

  val gameSceneInputContext = InputContext.create(
    "Game",
    InputIdentifier.create(
      INPUTMAP_VERSION,
      InputContextIds.GAME_SCENE.ordinal.toLong()),
    listOf(
      movementInputGroup,
      mouseActionsInputGroup,
      emojisInputGroup,
      gameActionsInputGroup))
}

Ява

public static final InputContext menuSceneInputContext = InputContext.create(
        "Menu",
        InputIdentifier.create(
                INPUTMAP_VERSION,
                InputContextIds.MENU_SCENE.ordinal()),
        Arrays.asList(
                basicMenuNavigationInputGroup,
                menuActionsInputGroup
        )
);

public static final InputContext gameSceneInputContext = InputContext.create(
        "Game",
        InputIdentifier.create(
                INPUTMAP_VERSION,
                InputContextIds.GAME_SCENE.ordinal()),
        Arrays.asList(
                movementInputGroup,
                mouseActionsInputGroup,
                emojisInputGroup,
                gameActionsInputGroup
        )
);

С#

public static readonly InputContext menuSceneInputContext = InputContext.Create(
    "Menu",
    InputIdentifier.Create(
        INPUT_MAP_VERSION,
        (long)InputContextsIds.MENU_SCENE),
    new[]
    {
        basicMenuNavigationInputGroup,
        menuActionsInputGroup
    }.ToJavaList()
);

public static readonly InputContext gameSceneInputContext = InputContext.Create(
    "Game",
    InputIdentifier.Create(
        INPUT_MAP_VERSION,
        (long)InputContextsIds.GAME_SCENE),
    new[]
    {
        movementInputGroup,
        mouseActionsInputGroup,
        emojisInputGroup,
        gameActionsInputGroup
    }.ToJavaList()
);

InputContext имеет следующие поля:

  • LocalizedContextLabel : строка, описывающая группы, которые принадлежат контексту.
  • InputContextId : объект InputIdentifier , который хранит идентификатор номера и версию InputContext (для получения дополнительной информации см. Идентификаторы клавиш отслеживания ).
  • ActiveGroups : список InputGroups которые будут использоваться и отображаться в верхней части наложения, когда этот контекст активен.

Создайте входную карту

InputMap - это коллекция всех объектов InputGroup , доступных в игре, и, следовательно, все объекты InputAction которые игрок может ожидать.

При сообщении о ваших ключевых привязках вы создаете InputMap со всеми InputGroups используемых в вашей игре.

Если ваша игра не поддерживает переиздание, установите опцию переработки отключенных и зарезервированных клавиш пусты.

Следующий пример создает InputMap используемую для сообщений о сборе InputGroups .

Котлин

companion object {
  val gameInputMap = InputMap.create(
    listOf(
      basicMenuNavigationInputGroup,
      menuActionKeysInputGroup,
      movementInputGroup,
      mouseMovementInputGroup,
      pauseMenuInputGroup),
    MouseSettings.create(true, false),
    InputIdentifier.create(INPUTMAP_VERSION, INPUT_MAP_ID.toLong()),
    InputEnums.REMAP_OPTION_ENABLED,
    // Use ESCAPE as reserved remapping key
    listof(InputControls.create(listOf(KeyEvent.KEYCODE_ESCAPE), emptyList()))
  )
}

Ява

public static final InputMap gameInputMap = InputMap.create(
        Arrays.asList(
                basicMenuNavigationInputGroup,
                menuActionKeysInputGroup,
                movementInputGroup,
                mouseMovementInputGroup,
                pauseMenuInputGroup),
        MouseSettings.create(true, false),
        InputIdentifier.create(INPUTMAP_VERSION, INPUT_MAP_ID),
        REMAP_OPTION_ENABLED,
        // Use ESCAPE as reserved remapping key
        Arrays.asList(
                InputControls.create(
                        Collections.singletonList(KeyEvent.KEYCODE_ESCAPE),
                        Collections.emptyList()
                )
        )
);

С#

public static readonly InputMap gameInputMap = InputMap.Create(
    new[]
    {
        basicMenuNavigationInputGroup,
        menuActionKeysInputGroup,
        movementInputGroup,
        mouseMovementInputGroup,
        pauseMenuInputGroup,
    }.ToJavaList(),
    MouseSettings.Create(true, false),
    InputIdentifier.Create(INPUT_MAP_VERSION, INPUT_MAP_ID),
    InputEnums.REMAP_OPTION_ENABLED,
    // Use ESCAPE as reserved remapping key
    new[]
    {
        InputControls.Create(
            New[] {
            new Integer(AndroidKeyCode.KEYCODE_ESCAPE)
        }.ToJavaList(),
        new ArrayList<Integer>())
    }.ToJavaList()
);

InputMap имеет следующие поля:

  • InputGroups : группы ввода, сообщенные вашей игрой. Группы отображаются в порядке в наложении, если не указано в текущих группах, используемых Calling setInputContext() .
  • MouseSettings : объект MouseSettings указывает на то, что чувствительность мыши может быть отрегулирована и что мышь инвертируется на оси Y.
  • InputMapId : объект InputIdentifier , который хранит идентификатор номера и версию InputMap (для получения дополнительной информации см. Идентификаторы клавиш отслеживания ).
  • InputRemappingOption : один из InputEnums.REMAP_OPTION_ENABLED или InputEnums.REMAP_OPTION_DISABLED . Определяет, включена ли функция перенастроения.
  • ReservedControls : список InputControls , в которые пользователям не будет разрешено переназначить.

Отслеживайте идентификаторы ключей

InputAction , InputGroup , InputContext и InputMap объекты содержат объект InputIdentifier , который хранит уникальный идентификатор номера и идентификатор версии строки. Отслеживание строковой версии ваших объектов является необязательным, но рекомендуется отслеживать версии вашей InputMap . Если версия строки не предоставлена, то строка пуста. Строковая версия требуется для объектов InputMap .

В следующем примере присваивает строковую версию для InputActions или InputGroups :

Котлин

class InputSDKProviderKotlin : InputMappingProvider {
  companion object {
    const val INPUTMAP_VERSION = "1.0.0"
    private val enterMenuInputAction = InputAction.create(
      "Enter menu",
      InputControls.create(listOf(KeyEvent.KEYCODE_ENTER), emptyList()),
      InputIdentifier.create(
        INPUTMAP_VERSION, InputActionsIds.ENTER_MENU.ordinal.toLong()),
      InputEnums.REMAP_OPTION_ENABLED
    )

    private val movementInputGroup  = InputGroup.create(
      "Basic movement",
      listOf(
        moveUpInputAction,
        moveLeftInputAction,
        moveDownInputAction,
        mouseGameInputAction),
      InputIdentifier.create(
        INPUTMAP_VERSION, InputGroupsIds.BASIC_MOVEMENT.ordinal.toLong()),
      InputEnums.REMAP_OPTION_ENABLED)
  }
}

Ява

public class InputSDKProvider implements InputMappingProvider {
    public static final String INPUTMAP_VERSION = "1.0.0";

    private static final InputAction enterMenuInputAction = InputAction.create(
            "Enter menu",
            InputControls.create(
                    Collections.singletonList(KeyEvent.KEYCODE_ENTER),
                    Collections.emptyList()),
            InputIdentifier.create(
                    INPUTMAP_VERSION, InputActionsIds.ENTER_MENU.ordinal()),
            InputEnums.REMAP_OPTION_ENABLED
    );

    private static final InputGroup movementInputGroup = InputGroup.create(
            "Basic movement",
            Arrays.asList(
                    moveUpInputAction,
                    moveLeftInputAction,
                    moveDownInputAction,
                    moveRightInputAction,
                    mouseGameInputAction
            ),
            InputIdentifier.create(
                    INPUTMAP_VERSION, InputGroupsIds.BASIC_MOVEMENT.ordinal()),
            InputEnums.REMAP_OPTION_ENABLED
    );
}

С#

#if PLAY_GAMES_PC

using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;

public class InputSDKMappingProvider : InputMappingProviderCallbackHelper
{
    public static readonly string INPUT_MAP_VERSION = "1.0.0";

    private static readonly InputAction enterMenuInputAction =
        InputAction.Create(
            "Enter menu",
            InputControls.Create(
                new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE)}.ToJavaList(),
                new ArrayList<Integer>()),
            InputIdentifier.Create(
                INPUT_MAP_VERSION,
                (long)InputEventIds.ENTER_MENU),
            InputEnums.REMAP_OPTION_ENABLED
        );

    private static readonly InputGroup movementInputGroup = InputGroup.Create(
        "Basic movement",
        new[]
        {
            moveUpInputAction,
            moveLeftInputAction,
            moveDownInputAction,
            moveRightInputAction,
            mouseGameInputAction
        }.ToJavaList(),
        InputIdentifier.Create(
            INPUT_MAP_VERSION,
            (long)InputGroupsIds.BASIC_MOVEMENT),
        InputEnums.REMAP_OPTION_ENABLED
    );
}
#endif

Идентификаторы номеров объектов InputAction должны быть уникальными для всех InputActions в вашей InputMap . Аналогичным образом, идентификаторы объекта InputGroup должны быть уникальными для всех InputGroups в InputMap . Следующий образец демонстрирует, как использовать enum для отслеживания уникальных идентификаторов вашего объекта:

Котлин

enum class InputActionsIds {
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

enum class InputGroupsIds {
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

enum class InputContextIds {
    MENU_SCENE, // Basic menu navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

const val INPUT_MAP_ID = 0

Ява

public enum InputActionsIds {
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

public enum InputGroupsIds {
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

public enum InputContextIds {
    MENU_SCENE, // Basic navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

public static final long INPUT_MAP_ID = 0;

С#

public enum InputActionsIds
{
    NAVIGATE_UP,
    NAVIGATE_DOWN,
    ENTER_MENU,
    EXIT_MENU,
    // ...
    JUMP,
    RUN,
    EMOJI_1,
    EMOJI_2,
    // ...
}

public enum InputGroupsIds
{
    // Main menu scene
    BASIC_NAVIGATION, // WASD, Enter, Backspace
    MENU_ACTIONS, // C: chat, Space: quick game, S: store
    // Gameplay scene
    BASIC_MOVEMENT, // WASD, space: jump, Shift: run
    MOUSE_ACTIONS, // Left click: shoot, Right click: aim
    EMOJIS, // Emojis with keys 1,2,3,4 and 5
    GAME_ACTIONS, // M: map, P: pause, R: reload
}

public enum InputContextIds
{
    MENU_SCENE, // Basic navigation, menu actions
    GAME_SCENE, // Basic movement, mouse actions, emojis, game actions
}

public static readonly long INPUT_MAP_ID = 0;

InputIdentifier имеет следующие поля:

  • UniqueId : уникальный идентификатор номера, установленная для четкого определения данного набора входных данных в однозначности.
  • VersionString : Человеческая читаемая версия, установленная для определения версии входных данных между 2 версиями изменений входных данных.

Получите уведомление о событиях переиздания (необязательно)

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

Следующее изображение показывает пример такого поведения, где после переигрывания ключей Г , П и С к Дж , Х и Т Соответственно, элементы пользовательского интерфейса игры обновляются, чтобы отобразить ключи, установленные пользователем.

Пользовательский интерфейс реагирует на повторные события, используя обратный вызов inputRemappingListener.

Эта функциональность достигается путем регистрации обратного вызовов InputRemappingListener . Чтобы реализовать это Feautre, запустите с регистрации экземпляра InputRemappingListener :

Котлин

class InputSDKRemappingListener : InputRemappingListener {
  override fun onInputMapChanged(inputMap: InputMap) {
    Log.i(TAG, "Received update on input map changed.")
    if (inputMap.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) {
      return
    }
    for (inputGroup in inputMap.inputGroups()) {
      if (inputGroup.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) {
        continue
      }
      for (inputAction in inputGroup.inputActions()) {
        if (inputAction.inputRemappingOption() != InputEnums.REMAP_OPTION_DISABLED) {
          // Found InputAction remapped by user
          processRemappedAction(inputAction)
        }
      }
    }
  }

  private fun processRemappedAction(remappedInputAction: InputAction) {
    // Get remapped action info
    val remappedControls = remappedInputAction.remappedInputControls()
    val remappedKeyCodes = remappedControls.keycodes()
    val mouseActions = remappedControls.mouseActions()
    val version = remappedInputAction.inputActionId().versionString()
    val remappedActionId = remappedInputAction.inputActionId().uniqueId()
    val currentInputAction: Optional<InputAction>
    currentInputAction = if (version == null || version.isEmpty()
      || version == InputSDKProvider.INPUTMAP_VERSION
    ) {
      getCurrentVersionInputAction(remappedActionId)
    } else {
      Log.i(TAG,
            "Detected version of user-saved input action defers from current version")
      getCurrentVersionInputActionFromPreviousVersion(
        remappedActionId, version)
    }
    if (!currentInputAction.isPresent) {
      Log.e(TAG, String.format(
        "can't find remapped input action with id %d and version %s",
        remappedActionId, if (version == null || version.isEmpty()) "UNKNOWN" else version))
      return
    }
    val originalControls = currentInputAction.get().inputControls()
    val originalKeyCodes = originalControls.keycodes()
    Log.i(TAG, String.format(
      "Found input action with id %d remapped from key %s to key %s",
      remappedActionId,
      keyCodesToString(originalKeyCodes),
      keyCodesToString(remappedKeyCodes)))

    // TODO: make display changes to match controls used by the user
  }

  private fun getCurrentVersionInputAction(inputActionId: Long): Optional<InputAction> {
    for (inputGroup in InputSDKProvider.gameInputMap.inputGroups()) {
      for (inputAction in inputGroup.inputActions()) {
        if (inputAction.inputActionId().uniqueId() == inputActionId) {
          return Optional.of(inputAction)
        }
      }
    }
    return Optional.empty()
  }

  private fun getCurrentVersionInputActionFromPreviousVersion(
    inputActionId: Long, previousVersion: String
  ): Optional<InputAction7gt; {
    // TODO: add logic to this method considering the diff between the current and previous
    //  InputMap.
    return Optional.empty()
  }

  private fun keyCodesToString(keyCodes: List<Int>): String {
    val builder = StringBuilder()
    for (keyCode in keyCodes) {
      if (!builder.toString().isEmpty()) {
        builder.append(" + ")
      }
      builder.append(keyCode)
    }
    return String.format("(%s)", builder)
  }

  companion object {
    private const val TAG = "InputSDKRemappingListener"
  }
}

Ява

public class InputSDKRemappingListener implements InputRemappingListener {

    private static final String TAG = "InputSDKRemappingListener";

    @Override
    public void onInputMapChanged(InputMap inputMap) {
        Log.i(TAG, "Received update on input map changed.");
        if (inputMap.inputRemappingOption() ==
                InputEnums.REMAP_OPTION_DISABLED) {
            return;
        }
        for (InputGroup inputGroup : inputMap.inputGroups()) {
            if (inputGroup.inputRemappingOption() ==
                    InputEnums.REMAP_OPTION_DISABLED) {
                continue;
            }
            for (InputAction inputAction : inputGroup.inputActions()) {
                if (inputAction.inputRemappingOption() !=
                        InputEnums.REMAP_OPTION_DISABLED) {
                    // Found InputAction remapped by user
                    processRemappedAction(inputAction);
                }
            }
        }
    }

    private void processRemappedAction(InputAction remappedInputAction) {
        // Get remapped action info
        InputControls remappedControls =
            remappedInputAction.remappedInputControls();
        List<Integer> remappedKeyCodes = remappedControls.keycodes();
        List<Integer> mouseActions = remappedControls.mouseActions();
        String version = remappedInputAction.inputActionId().versionString();
        long remappedActionId = remappedInputAction.inputActionId().uniqueId();
        Optional<InputAction> currentInputAction;
        if (version == null || version.isEmpty()
                    || version.equals(InputSDKProvider.INPUTMAP_VERSION)) {
            currentInputAction = getCurrentVersionInputAction(remappedActionId);
        } else {
            Log.i(TAG, "Detected version of user-saved input action defers " +
                    "from current version");
            currentInputAction =
                    getCurrentVersionInputActionFromPreviousVersion(
                            remappedActionId, version);
        }
        if (!currentInputAction.isPresent()) {
            Log.e(TAG, String.format(
                    "input action with id %d and version %s not found",
                    remappedActionId, version == null || version.isEmpty() ?
                            "UNKNOWN" : version));
            return;
        }
        InputControls originalControls =
                currentInputAction.get().inputControls();
        List<Integer> originalKeyCodes = originalControls.keycodes();

        Log.i(TAG, String.format(
                "Found input action with id %d remapped from key %s to key %s",
                remappedActionId,
                keyCodesToString(originalKeyCodes),
                keyCodesToString(remappedKeyCodes)));

        // TODO: make display changes to match controls used by the user
    }

    private Optional<InputAction> getCurrentVersionInputAction(
            long inputActionId) {
        for (InputGroup inputGroup :
                    InputSDKProvider.gameInputMap.inputGroups()) {
            for (InputAction inputAction : inputGroup.inputActions()) {
                if (inputAction.inputActionId().uniqueId() == inputActionId) {
                    return Optional.of(inputAction);
                }
            }
        }
        return Optional.empty();
    }

    private Optional<InputAction>
            getCurrentVersionInputActionFromPreviousVersion(
                    long inputActionId, String previousVersion) {
        // TODO: add logic to this method considering the diff between your
        // current and previous InputMap.
        return Optional.empty();
    }

    private String keyCodesToString(List<Integer> keyCodes) {
        StringBuilder builder = new StringBuilder();
        for (Integer keyCode : keyCodes) {
            if (!builder.toString().isEmpty()) {
                builder.append(" + ");
            }
            builder.append(keyCode);
        }
        return String.format("(%s)", builder);
    }
}

С#

#if PLAY_GAMES_PC

using System.Text;
using Java.Lang;
using Java.Util;
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel;
using UnityEngine;

public class InputSDKRemappingListener : InputRemappingListenerCallbackHelper
{
    public override void OnInputMapChanged(InputMap inputMap)
    {
        Debug.Log("Received update on remapped controls.");
        if (inputMap.InputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED)
        {
            return;
        }
        List<InputGroup> inputGroups = inputMap.InputGroups();
        for (int i = 0; i < inputGroups.Size(); i ++)
        {
            InputGroup inputGroup = inputGroups.Get(i);
            if (inputGroup.InputRemappingOption()
                    == InputEnums.REMAP_OPTION_DISABLED)
            {
                continue;
            }
            List<InputAction> inputActions = inputGroup.InputActions();
            for (int j = 0; j < inputActions.Size(); j ++)
            {
                InputAction inputAction = inputActions.Get(j);
                if (inputAction.InputRemappingOption()
                        != InputEnums.REMAP_OPTION_DISABLED)
                {
                    // Found action remapped by user
                    ProcessRemappedAction(inputAction);
                }
            }
        }
    }

    private void ProcessRemappedAction(InputAction remappedInputAction)
    {
        InputControls remappedInputControls =
                remappedInputAction.RemappedInputControls();
        List<Integer> remappedKeycodes = remappedInputControls.Keycodes();
        List<Integer> mouseActions = remappedInputControls.MouseActions();
        string version = remappedInputAction.InputActionId().VersionString();
        long remappedActionId = remappedInputAction.InputActionId().UniqueId();
        InputAction currentInputAction;
        if (string.IsNullOrEmpty(version)
                || string.Equals(
                version, InputSDKMappingProvider.INPUT_MAP_VERSION))
        {
            currentInputAction = GetCurrentVersionInputAction(remappedActionId);
        }
        else
        {
            Debug.Log("Detected version of used-saved input action defers" +
                " from current version");
            currentInputAction =
                GetCurrentVersionInputActionFromPreviousVersion(
                    remappedActionId, version);
        }
        if (currentInputAction == null)
        {
            Debug.LogError(string.Format(
                "Input Action with id {0} and version {1} not found",
                remappedActionId,
                string.IsNullOrEmpty(version) ? "UNKNOWN" : version));
            return;
        }
        InputControls originalControls = currentInputAction.InputControls();
        List<Integer> originalKeycodes = originalControls.Keycodes();

        Debug.Log(string.Format(
            "Found Input Action with id {0} remapped from key {1} to key {2}",
            remappedActionId,
            KeyCodesToString(originalKeycodes),
            KeyCodesToString(remappedKeycodes)));
        // TODO: update HUD according to the controls of the user
    }

    private InputAction GetCurrentVersionInputAction(
            long inputActionId)
    {
        List<InputGroup> inputGroups =
            InputSDKMappingProvider.gameInputMap.InputGroups();
        for (int i = 0; i < inputGroups.Size(); i++)
        {
            InputGroup inputGroup = inputGroups.Get(i);
            List<InputAction> inputActions = inputGroup.InputActions();
            for (int j = 0; j < inputActions.Size(); j++)
            {
                InputAction inputAction = inputActions.Get(j);
                if (inputAction.InputActionId().UniqueId() == inputActionId)
                {
                    return inputAction;
                }
            }
        }
        return null;
    }

    private InputAction GetCurrentVersionInputActionFromPreviousVersion(
            long inputActionId, string version)
    {
        // TODO: add logic to this method considering the diff between your
        // current and previous InputMap.
        return null;
    }

    private string KeyCodesToString(List<Integer> keycodes)
    {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < keycodes.Size(); i ++)
        {
            Integer keycode = keycodes.Get(i);
            if (builder.Length > 0)
            {
                builder.Append(" + ");
            }
            builder.Append(keycode.IntValue());
        }
        return string.Format("({0})", builder.ToString());
    }
}
#endif

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

Инициализация

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

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

Следующий образец демонстрирует, как инициализировать API:

Котлин

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (isGooglePlayGamesOnPC()) {
        val inputMappingClient = Input.getInputMappingClient(this)
        // Register listener before registering the provider
        inputMappingClient.registerRemappingListener(InputSDKRemappingListener())
        inputMappingClient.setInputMappingProvider(
                InputSDKProvider())
        // Set the context after you have registered the provider.
        inputMappingClient.setInputContext(InputSDKProvider.menuSceneInputContext)
    }
}

Ява

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (isGooglePlayGamesOnPC()) {
        InputMappingClient inputMappingClient =
                Input.getInputMappingClient(this);
        // Register listener before registering the provider
        inputMappingClient.registerRemappingListener(
                new InputSDKRemappingListener());
        inputMappingClient.setInputMappingProvider(
                new InputSDKProvider());
        // Set the context after you have registered the provider
        inputMappingClient.setInputContext(InputSDKProvider.menuSceneInputContext);
    }
}

С#

#if PLAY_GAMES_PC
using Google.Android.Libraries.Play.Games.Inputmapping;
using Google.Android.Libraries.Play.Games.InputMapping.ExternalType.Android.Content;
using Google.LibraryWrapper.Java;
#endif

public class GameManager : MonoBehaviour
{
#if PLAY_GAMES_PC
    private InputSDKMappingProvider _inputMapProvider =
        new InputSDKMappingProvider();
    private InputMappingClient _inputMappingClient;
#endif

    public void Awake()
    {
#if PLAY_GAMES_PC
        Context context = (Context)Utils.GetUnityActivity().GetRawObject();
        _inputMappingClient = Google.Android.Libraries.Play.Games.Inputmapping
            .Input.GetInputMappingClient(context);
        // Register listener before registering the provider.
        _inputMappingClient.RegisterRemappingListener(
            new InputSDKRemappingListener());
        _inputMappingClient.SetInputMappingProvider(_inputMapProvider);
        // Register context after you have registered the provider.
       _inputMappingClient.SetInputContext(
           InputSDKMappingProvider.menuSceneInputContext);
#endif
    }
}

Очистить

Не регистрируйте ваш экземпляр InputMappingProvider и любые экземпляры InputRemappingListener , когда ваша игра закрыта, хотя входной SDK достаточно умный, чтобы избежать утечки ресурсов, если вы этого не сделаете:

Котлин

override fun onDestroy() {
    if (isGooglePlayGamesOnPC()) {
        val inputMappingClient = Input.getInputMappingClient(this)
        inputMappingClient.clearInputMappingProvider()
        inputMappingClient.clearRemappingListener()
    }

    super.onDestroy()
}

Ява

@Override
protected void onDestroy() {
    if (isGooglePlayGamesOnPC()) {
        InputMappingClient inputMappingClient =
                Input.getInputMappingClient(this);
        inputMappingClient.clearInputMappingProvider();
        inputMappingClient.clearRemappingListener();
    }

    super.onDestroy();
}

С#

public class GameManager : MonoBehaviour
{
    private void OnDestroy()
    {
#if PLAY_GAMES_PC
        _inputMappingClient.ClearInputMappingProvider();
        _inputMappingClient.ClearRemappingListener();
#endif
    }
}

Тест

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

Google Play Games на эмуляторе ПК проверяет правильность вашей входной карты против общих ошибок. Для сценариев, таких как дубликаты уникальных идентификаторов, с использованием различных карт ввода или сбоя в правилах переигрывания (если включено переиздание), наложение показывает сообщение об ошибке, как ниже: Входное наложение SDK.

Проверьте свою входную реализацию SDK, используя adb в командной строке. Чтобы получить текущую карту ввода, используйте следующую команду adb shell (замените MY.PACKAGE.NAME с именем вашей игры):

adb shell dumpsys input_mapping_service --get MY.PACKAGE.NAME

Вы увидите вывод, похожий на это, если вы успешно зарегистрировали свою InputMap :

Getting input map for com.example.inputsample...
Successfully received the following inputmap:
# com.google.android.libraries.play.games.InputMap@d73526e1
input_groups {
  group_label: "Basic Movement"
  input_actions {
    action_label: "Jump"
    input_controls {
      keycodes: 51
      keycodes: 19
    }
    unique_id: 0
  }
  input_actions {
    action_label: "Left"
    input_controls {
      keycodes: 29
      keycodes: 21
    }
    unique_id: 1
  }
  input_actions {
    action_label: "Right"
    input_controls {
      keycodes: 32
      keycodes: 22
    }
    unique_id: 2
  }
  input_actions {
    action_label: "Use"
    input_controls {
      keycodes: 33
      keycodes: 66
      mouse_actions: MOUSE_LEFT_CLICK
      mouse_actions_value: 0
    }
    unique_id: 3
  }
}
input_groups {
  group_label: "Special Input"
  input_actions {
    action_label: "Jump"
    input_controls {
      keycodes: 51
      keycodes: 19
      keycodes: 62
      mouse_actions: MOUSE_LEFT_CLICK
      mouse_actions_value: 0
    }
    unique_id: 4
  }
  input_actions {
    action_label: "Duck"
    input_controls {
      keycodes: 47
      keycodes: 20
      keycodes: 113
      mouse_actions: MOUSE_RIGHT_CLICK
      mouse_actions_value: 1
    }
    unique_id: 5
  }
}
mouse_settings {
  allow_mouse_sensitivity_adjustment: true
  invert_mouse_movement: true
}

Локализация

Входной SDK не использует систему локализации Android. В результате вы должны предоставить локализованные строки при отправке InputMap . Вы также можете использовать систему локализации вашего игрового двигателя.

Прогард

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

-keep class com.google.android.libraries.play.hpe.** { *; }
-keep class com.google.android.libraries.play.games.inputmapping.** { *; }

Что дальше

После того, как вы интегрируете вход SDK в свою игру, вы можете продолжить с любыми оставшимися играми Google Play по требованиям ПК. Для получения дополнительной информации см. Начните работу с Google Play Games на ПК .