В этом документе описывается, как настроить и отобразить Input SDK в играх, поддерживающих Google Play Games, на ПК. В задачи входит добавление SDK в вашу игру и создание карты ввода, которая содержит назначения действий игры и ввода пользователя.
Прежде чем начать
Прежде чем добавлять в игру SDK ввода, вы должны поддерживать ввод с клавиатуры и мыши с помощью системы ввода вашего игрового движка .
Input SDK предоставляет Google Play Games на ПК информацию о том, какие элементы управления используются в вашей игре, чтобы их можно было отобразить пользователю. Он также может дополнительно разрешить переназначение клавиатуры для пользователей.
Каждый элемент управления представляет собой InputAction
(например, «J» для «Jump»), и вы организуете свои InputActions
в InputGroups
. Группа InputGroup
может представлять другой режим в вашей игре, например «Вождение», «Ходьба» или «Главное меню». Вы также можете использовать InputContexts
, чтобы указать, какие группы активны в разные моменты игры.
Вы можете включить автоматическую обработку переназначения клавиатуры, но если вы предпочитаете предоставить собственный интерфейс переназначения элементов управления, вы можете отключить переназначение входного SDK.
Следующая диаграмма последовательности описывает, как работает API входного SDK:
Если в вашей игре реализован входной SDK, ваши элементы управления отображаются в оверлее Google Play Games on PC.
Оверлей 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 + А, 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
и установив его зависимости:
- com.google.external-dependent-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
Установить с помощью 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 );
Действия также могут представлять действия мыши. В этом примере щелчок левой кнопкой мыши задается для действия «Переместить» :
Котлин
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 );
Комбинации клавиш задаются путем передачи нескольких кодов клавиш в ваш 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 );
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 имеет следующие поля:
-
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 );
В следующем примере показаны группы входов элементов управления «Дорога» и «Меню» в наложении:
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)) }
Ява
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
:
Котлин
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, используя 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 на ПК .