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

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

Рекомендации по назначению клавиш
При назначении клавиш учитывайте следующие рекомендации:
- Группируйте
InputActionsв логически связанныеInputGroups, чтобы во время игры было проще ориентироваться и находить нужные элементы управления. - Каждому значению
InputGroupможно назначить не более одного значенияInputContext. Чем меньше шаг, тем удобнее пользоваться элементами управления в оверлее.InputMap - Создайте элемент
InputContextдля каждого типа сцены в игре. Обычно для всех сцен, похожих на меню, можно использовать один и тот же тегInputContext. Используйте разныеInputContextsдля мини-игр или альтернативных способов управления в одной сцене. - Если два действия должны использовать один и тот же ключ в рамках одного и того же
InputContext, используйте строку ярлыка, например "Взаимодействовать / Стрелять". - Если два ключа должны быть привязаны к одному и тому же
InputAction, используйте два разныхInputActions, которые выполняют одно и то же действие в игре. Вы можете использовать одну и ту же строку ярлыка для обоих свойствInputActions, но идентификаторы должны быть разными. - Если клавиша-модификатор применяется к набору клавиш, рекомендуем использовать одну клавишу
InputActionс клавишей-модификатором вместо нескольких клавишInputActions, которые сочетают клавишу-модификатор (например, используйте Shift и W, A, S, D вместо Shift + W, Shift + A, Shift + S, Shift + D). - Переназначение клавиш автоматически отключается, когда пользователь вводит текст в текстовые поля. Чтобы Android мог распознавать текстовые поля в вашей игре и предотвращать их блокировку переназначенными клавишами, следуйте рекомендациям по реализации текстовых полей Android. Если в вашей игре используются нестандартные текстовые поля, вы можете отключить переназначение вручную, используя
setInputContext()сInputContext, содержащим пустой списокInputGroups. - Если в вашей игре поддерживается переназначение клавиш, не меняйте их настройки, так как это может привести к конфликту с сохраненными пользователем версиями. По возможности не меняйте идентификаторы существующих элементов управления.
Функция переназначения
Google Play Игры на компьютере поддерживают переназначение клавиш на основе привязок, которые вы задали в игре с помощью Input SDK. Это необязательная функция, которую можно отключить. Например, вы можете создать собственный интерфейс для переназначения клавиш. Чтобы отключить переназначение клавиш в игре, нужно указать, что параметр переназначения отключен для InputMap (подробнее о том, как создать InputMap…).
Чтобы использовать эту функцию, пользователям нужно открыть оверлей и нажать на действие, которое они хотят переназначить. После каждого переназначения клавиш Google Play Games на компьютере сопоставляет переназначенные пользователем клавиши с клавишами по умолчанию, которые ожидает получить ваша игра. Таким образом, игра не должна знать о переназначении клавиш. При необходимости вы можете обновить объекты, используемые для показа элементов управления клавиатурой в игре, добавив обратный вызов для переназначения событий.

Google Play Игры на компьютере сохраняют переназначенные элементы управления локально для каждого пользователя, поэтому настройки сохраняются между игровыми сессиями. Эта информация хранится на диске только на платформе ПК и не влияет на работу мобильных устройств. Данные управления удаляются, когда пользователь удаляет или переустанавливает Google Play Игры на компьютере. Эти данные не сохраняются на нескольких устройствах.
Чтобы в игре можно было переназначать кнопки, соблюдайте следующие ограничения:
Ограничения переназначения
Переназначение клавиш может быть отключено в игре, если в настройках управления есть следующие случаи:
- Сочетания клавиш
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'
...
}
Unity
Input SDK – это стандартный пакет Unity с несколькими зависимостями.
Необходимо установить пакет со всеми зависимостями. Установить пакеты можно несколькими способами.
Как установить .unitypackage
Скачайте файл Input SDK unitypackage со всеми зависимостями. Установить .unitypackage можно, выбрав Assets > Import package > Custom Package и указав скачанный файл.
Установка с помощью UPM
Вы также можете установить пакет с помощью Unity Package Manager, скачав файл .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:
Kotlin
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.
В этом примере клавиша
Kotlin
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 );

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

Input SDK позволяет объединять кнопки мыши и клавиши для выполнения одного действия. В этом примере показано, что одновременное нажатие клавиши
Kotlin
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.
Kotlin
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 - Назначьте эти
InputGroupsInputContextдля разных частей игры.
InputGroups, относящиеся к одномуInputContext, не могут иметь конфликтующиеInputActions, если используется один и тот же ключ. Рекомендуется назначать каждому InputGroup только один InputContext.
В приведенном ниже образце кода показана логика параметра InputContext:
Kotlin
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.
Kotlin
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:
Kotlin
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 для отслеживания уникальных идентификаторов объектов.
Kotlin
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:
Kotlin
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:
Kotlin
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 достаточно умный, чтобы избежать утечки ресурсов, даже если вы этого не сделаете:
Kotlin
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 для минимизации игры, добавьте в файл конфигурации Proguard следующие правила, чтобы SDK не был удален из конечного пакета:
-keep class com.google.android.libraries.play.hpe.** { *; }
-keep class com.google.android.libraries.play.games.inputmapping.** { *; }
Что дальше
После того как вы интегрируете Input SDK в игру, вы можете выполнить оставшиеся требования для Google Play Игр на компьютере. Подробнее о том, как начать работу с Google Play Играми на компьютере…