В этом документе описывается, как настроить и отобразить Input SDK в играх, поддерживающих Google Play Games на ПК. Задачи включают добавление SDK в вашу игру и генерацию карты ввода, которая содержит назначения действий игры пользовательскому вводу.
Прежде чем начать
Прежде чем добавлять Input SDK в свою игру, необходимо обеспечить поддержку ввода с клавиатуры и мыши с использованием системы ввода вашего игрового движка .
Input SDK предоставляет Google Play Games на ПК информацию о том, какие элементы управления используются в вашей игре, чтобы они могли отображаться пользователю. Он также может опционально разрешать переназначение клавиш для пользователей.
Каждый элемент управления представляет собой InputAction (например, "J" для "Прыжка"), и вы организуете свои InputActions в InputGroups . InputGroup может представлять собой различный режим в вашей игре, например, "Вождение", "Ходьба" или "Главное меню". Вы также можете использовать InputContexts , чтобы указать, какие группы активны в разные моменты игры.
Вы можете включить автоматическую перенастройку клавиш, но если вы предпочитаете использовать собственный интерфейс переназначения элементов управления, вы можете отключить переназначение клавиш в Input SDK.
Следующая диаграмма последовательности описывает принцип работы API Input SDK:

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

Рекомендации по разработке сочетаний клавиш
При разработке сочетаний клавиш учитывайте следующие рекомендации:
- Сгруппируйте ваши
InputActionsв логически связанныеInputGroups, чтобы улучшить навигацию и удобство использования элементов управления во время игры. - Назначьте каждому
InputGroupне более одногоInputContext. Более детальная настройкаInputMapобеспечит более удобную навигацию по элементам управления в оверлее. - Создайте
InputContextдля каждого типа сцен в вашей игре. Как правило, для всех сцен, напоминающих меню, можно использовать одинInputContext. Используйте разныеInputContextsдля мини-игр в вашей игре или для альтернативных элементов управления в одной сцене. - Если два действия предназначены для использования одной и той же клавиши в рамках одного и того же
InputContext, используйте строковую метку, например, "Взаимодействовать / Выстрелить". - Если две клавиши предназначены для привязки к одному и тому же
InputActionиспользуйте в игре два разныхInputActions, выполняющих одно и то же действие. Вы можете использовать одну и ту же строку метки для обоихInputActions, но их ID должен быть разным. - Если клавиша-модификатор применяется к набору клавиш, рекомендуется использовать один
InputActionс этой клавишей-модификатором вместо несколькихInputActions, объединяющих эти клавиши (например, использовать Shift и W, A, S, D вместо Shift + W, Shift + A, Shift + S, Shift + D ). - Переназначение клавиш автоматически отключается, когда пользователь вводит текст в текстовые поля. Следуйте рекомендациям по реализации текстовых полей Android, чтобы гарантировать, что Android сможет распознавать текстовые поля в вашей игре и предотвращать конфликт с переназначенными клавишами. Если в вашей игре используются нестандартные текстовые поля, вы можете использовать
setInputContext()сInputContextсодержащим пустой списокInputGroups, чтобы вручную отключить переназначение клавиш. - Если ваша игра поддерживает переназначение клавиш, обновите их настройки — это деликатная операция, которая может конфликтовать с сохраненными пользователем версиями. По возможности избегайте изменения идентификаторов существующих элементов управления.
Функция переназначения
Google Play Games на ПК поддерживает переназначение клавиш управления на основе настроек клавиш, предоставляемых вашей игрой с помощью Input SDK. Эта функция является необязательной и может быть полностью отключена. Например, вы можете захотеть предоставить собственный интерфейс переназначения клавиш. Чтобы отключить переназначение для вашей игры, вам просто нужно указать параметр переназначения disabled для вашего 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 на эмуляторе ПК.
Включить функцию переназначения клавиш в Google Play Games на ПК-эмуляторе можно в любое время, выполнив следующую команду adb:
adb shell dumpsys input_mapping_service --set RemappingFlagValue true
Изменение наложения показано на следующем изображении:

Добавьте SDK
Установите Input SDK в соответствии с вашей платформой разработки.
Java и Kotlin
Чтобы получить Input SDK для Java или Kotlin, добавьте зависимость в файл build.gradle на уровне модуля:
dependencies {
implementation 'com.google.android.libraries.play.games:inputmapping:1.1.1-beta'
...
}
Единство
Input SDK — это стандартный пакет Unity с несколькими зависимостями.
Для установки пакета необходимо установить все его зависимости. Существует несколько способов установки пакетов.
Установите файл .unitypackage
Загрузите файл Unitypackage для Input SDK со всеми его зависимостями. Вы можете установить .unitypackage , выбрав Assets > Import package > Custom Package и найдя загруженный файл.
Установка с помощью UPM
В качестве альтернативы вы можете установить пакет с помощью менеджера пакетов Unity , загрузив файл .tgz и установив его зависимости:
- com.google.external-dependency-manager-1.2.172
- com.google.librarywrapper.java-0.2.0
- com.google.librarywrapper.openjdk8-0.2.0
- com.google.android.libraries.play.games.inputmapping-1.1.1-beta (или выберите 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") } }
Java
public class InputSDKProvider implements InputMappingProvider { private static final String INPUTMAP_VERSION = "1.0.0"; @Override @NonNull public InputMap onProvideInputMap() { // TODO: return an InputMap } }
C#
#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) }
Java
private static final InputAction driveInputAction = InputAction.create( "Drive", InputEventIds.DRIVE.ordinal(), InputControls.create( Collections.singletonList(KeyEvent.KEYCODE_SPACE), Collections.emptyList()), InputEnums.REMAP_OPTION_ENABLED );
C#
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 );

Действия также могут представлять собой ввод с мыши. В этом примере щелчок левой кнопкой мыши назначает действие «Перемещение» :
Котлин
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) }
Java
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 );
C#
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 );

Комбинации клавиш задаются путем передачи нескольких кодов клавиш в метод 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) }
Java
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 );
C#
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 );

Встроенный 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) }
Java
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 );
C#
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 имеет следующие поля:
-
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 ) }
Java
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 );
C#
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 );
В следующем примере на наложенном изображении отображаются группы элементов управления «Дорога» и « Меню» :

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

Обновление наложения достигается путем установки различных значений InputContext в разных точках игры. Для этого:
- Сгруппируйте
InputActionsпо логически связанным действиям, используяInputGroups - Назначьте эти
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)) }
Java
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 ) );
C#
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())) ) }
Java
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() ) ) );
C#
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) } }
Java
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 ); }
C#
#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
Java
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;
C#
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 :
Котлин
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" } }
Java
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); } }
C#
#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) } }
Java
@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); } }
C#
#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() }
Java
@Override protected void onDestroy() { if (isGooglePlayGamesOnPC()) { InputMappingClient inputMappingClient = Input.getInputMappingClient(this); inputMappingClient.clearInputMappingProvider(); inputMappingClient.clearRemappingListener(); } super.onDestroy(); }
C#
public class GameManager : MonoBehaviour { private void OnDestroy() { #if PLAY_GAMES_PC _inputMappingClient.ClearInputMappingProvider(); _inputMappingClient.ClearRemappingListener(); #endif } }
Тест
Вы можете протестировать свою реализацию Input SDK, вручную открыв оверлей для просмотра интерфейса проигрывателя, или используя оболочку adb для автоматического тестирования и проверки.
Эмулятор Google Play Games на ПК проверяет правильность вашей карты ввода на наличие распространенных ошибок. В таких случаях, как дублирование уникальных идентификаторов, использование разных карт ввода или несоответствие правилам переназначения (если переназначение включено), наложение отображает сообщение об ошибке, как показано ниже: 
Проверьте реализацию вашего Input 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
}
Локализация
В Input 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 на ПК» .