En este documento, se describe cómo configurar y mostrar el SDK de entrada en juegos compatibles con Google Play Juegos para PC. Las tareas incluyen agregar el SDK a tu juego y generar un mapa de entrada, que contiene las asignaciones de acciones del juego a entradas del usuario.
Antes de comenzar
Antes de incorporar el SDK de entrada al juego, debes agregar compatibilidad con las entradas de teclado y mouse usando el sistema de entrada de tu motor de juego.
El SDK de entrada brinda información a Google Play Juegos para PC sobre los controles que usa tu juego, de modo que el usuario pueda verlos. También puede, de manera opcional, permitir que los usuarios reasignen teclas del teclado.
Cada control es una InputAction
(p. ej., "C" para "Correr"), y tú organizas tus InputActions
en InputGroups
. Un InputGroup
podría representar un modo diferente en el juego, como "Conducir" o "Caminar", o "Menú principal". También puedes usar InputContexts
para indicar qué grupos están activos en distintos puntos del juego.
Es posible configurar que la reasignación de teclas se administre automáticamente, pero, si prefieres emplear tu propia interfaz de reasignación de controles, puedes inhabilitar la reasignación del SDK de entrada.
En el siguiente diagrama de secuencias, se describe cómo funciona la API del SDK de entrada:
Cuando tu juego implemente el SDK de entrada, los controles se mostrarán en la superposición de Google Play Juegos para PC.
La superposición de Google Play Juegos para PC
La superposición de Google Play Juegos para PC ("la superposición") muestra los controles que definió tu juego. Los usuarios pueden acceder a ella en cualquier momento con Mayúsculas + Tab.
Prácticas recomendadas para diseñar vinculaciones de teclas
Cuando diseñes vinculaciones de teclas, ten en cuenta las siguientes prácticas recomendadas:
- Agrupa tus
InputActions
enInputGroups
relacionados de forma lógica para mejorar la navegación y la capacidad de detectar los controles durante la partida del juego. - Asigna cada
InputGroup
a, al menos, unInputContext
. UnInputMap
correcto y detallado genera una mejor experiencia para navegar por tus controles en la superposición. - Crea un
InputContext
para cada tipo de escena distinto de tu juego. Por lo general, puedes usar un soloInputContext
para todas las escenas que son similares al menú. UsaInputContexts
diferentes para los minijuegos que haya o para los controles alternativos de una escena única. - Si hay dos acciones diseñadas para usar la misma tecla bajo el mismo
InputContext
, usa la cadena de etiqueta; por ejemplo, "Interactuar/disparar". - Si hay dos teclas diseñadas para vincularse a la misma
InputAction
, usa 2InputActions
diferentes que realicen la misma acción en el juego. Puedes utilizar la misma cadena de etiqueta para ambasInputActions
, pero su ID debe ser distinto. - Si se aplica una tecla modificadora a un conjunto de teclas, considera usar únicamente una sola
InputAction
con la tecla modificadora, en lugar de variasInputActions
que combinen esa tecla (por ejemplo, usa Mayúsculas y W, A, S y D en lugar de Mayúsculas + W, Mayúsculas + A, Mayúsculas + S y Mayúsculas + D). - La reasignación de entradas se inhabilita automáticamente cuando el usuario escribe en campos de texto. Sigue las prácticas recomendadas para implementar campos de texto de Android para asegurarte de que Android pueda detectarlos en tu juego y evitar que las teclas reasignadas interfieran con ellos. Si tu juego debe usar campos de texto no convencionales, puedes utilizar
setInputContext()
con unInputContext
que contenga una lista vacía deInputGroups
para inhabilitar manualmente la reasignación. - Si tu juego admite la reasignación, ten en cuenta que actualizar las vinculaciones de teclas puede ser una operación delicada que podría generar conflictos con las versiones que guardó el usuario. Cuando sea posible, evita cambiar los IDs de los controles existentes.
La función de reasignación
Google Play Juegos para PC admite la reasignación de controles del teclado en función de las vinculaciones de teclas que proporciona tu juego usando el SDK de entrada. Esta función es opcional y se puede inhabilitar por completo. Por ejemplo, tal vez quieras emplear tu propia interfaz de reasignación de teclas. Para inhabilitar la reasignación en tu juego, solo debes especificar que esa opción está inhabilitada para tu InputMap
(consulta Cómo crear un mapa de entradas para obtener más información).
Para acceder a esta función, los usuarios deben abrir la superposición y hacer clic en la acción que desean reasignar. Después de cada evento de reasignación, Google Play Juegos para PC asigna cada control modificado por el usuario a los controles predeterminados que espera recibir tu juego, de modo que este no necesite estar al tanto de la reasignación que configuró el usuario. De manera opcional, puedes actualizar los recursos que se usan para mostrar los controles del teclado en el juego agregando una devolución de llamada para los eventos de reasignación.
Google Play Juegos para PC almacena los controles reasignados de manera local para cada usuario, lo que permite la persistencia de controles en todas las sesiones de juego. Esa información se guarda en el disco solo en el caso de la plataforma de PC y no influye en la experiencia en dispositivos móviles. Los datos de controles se borran si el usuario desinstala o reinstala Google Play Juegos para PC. Esos datos no persisten en diferentes dispositivos de PC.
Para admitir la función de reasignación en tu juego, evita las siguientes restricciones:
Restricciones de la reasignación
Las funciones de reasignación se pueden inhabilitar en tu juego si las vinculaciones de teclas entran en alguno de estos casos:
InputActions
de varias teclas que no constan de una tecla modificadora + una tecla no modificadora. Por ejemplo, Mayúsculas + A es válida, pero A + B, Ctrl + Alt o Mayúsculas + A + Tab no lo son.InputMap
contieneInputActions
,InputGroups
oInputContexts
con IDs únicos repetidos.
Limitaciones de la reasignación
Cuando diseñes las vinculaciones de teclas para la reasignación, ten en cuenta las siguientes limitaciones:
- No se admite la reasignación a combinaciones de teclas. Por ejemplo, los usuarios no pueden reasignar Mayúsculas + A a Ctrl + B o A a Mayúsculas + A.
- No se admite la reasignación para
InputActions
con botones del mouse. Por ejemplo, no se puede reasignar Mayúsculas + clic derecho.
Cómo probar la reasignación de teclas en el emulador de Google Play Juegos para PC
Puedes habilitar la función de reasignación en el emulador de Google Play Juegos para PC en cualquier momento emitiendo el siguiente comando de adb:
adb shell dumpsys input_mapping_service --set RemappingFlagValue true
La superposición cambia como en la siguiente imagen:
Cómo agregar el SDK
Instala el SDK de entrada según tu plataforma de desarrollo.
Java y Kotlin
Para obtener el SDK de entrada para Java o Kotlin, agrega una dependencia al archivo build.gradle
del nivel de módulo:
dependencies {
implementation 'com.google.android.libraries.play.games:inputmapping:1.1.1-beta'
...
}
Unity
El SDK de entrada es un paquete estándar de Unity con varias dependencias.
Es obligatorio instalar el paquete con todas las dependencias. Hay distintas formas de instalar los paquetes.
Cómo instalar el .unitypackage
Descarga el archivo unitypackage del SDK de entrada con todas sus dependencias. Para instalar el .unitypackage
, selecciona Assets > Import package > Custom Package y busca el archivo que descargaste.
Cómo instalar el paquete con UPM
Como alternativa, puedes instalar el paquete con el administrador de paquetes de Unity. Para ello, descarga el .tgz
e instala sus dependencias:
- 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 (o selecciona el archivo tgz de este archivo)
Cómo instalar el paquete con OpenUPM
Puedes usar OpenUPM para instalar el paquete.
$ openupm add com.google.android.libraries.play.games.inputmapping
Juegos de muestra
Si quieres ver ejemplos de cómo hacer la integración con el SDK de entrada, consulta AGDK Tunnel para juegos de Kotlin o Java, y Trivial Kart para juegos de Unity.
Cómo generar tus vinculaciones de teclas
Registra tus vinculaciones de teclas creando un InputMap
y mostrándolo con un InputMappingProvider
. En el siguiente ejemplo, se muestra a grandes rasgos un 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
Cómo definir tus acciones de entrada
La clase InputAction
se usa para asignar una tecla o una combinación de teclas a una acción del juego. InputActions
debe tener IDs únicos en todas las InputActions
.
Si admites la reasignación, puedes definir qué InputActions
se pueden reasignar. Si tu juego no admite la reasignación, deberías configurarla como inhabilitada para todas las InputActions
, aunque el SDK de entrada es tan inteligente que la desactiva si no la admites en tu InputMap
.
En este ejemplo, se asigna la tecla
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 );
Las acciones también pueden representar entradas del mouse. En este ejemplo, se configura el clic izquierdo para la acción Mover.
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 );
Las combinaciones de teclas se especifican pasando varios códigos de tecla a InputAction
. En este ejemplo, se asigna
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 );
El SDK de entrada te permite combinar los botones del mouse y las teclas para una sola acción. En este ejemplo, se indica que
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 tiene los siguientes campos:
ActionLabel
: Es la cadena que se muestra en la IU para representar esta acción. Dado que la localización no se realiza automáticamente, debes hacerla por adelantado.InputControls
: Define los controles de entrada que usa esta acción. Los controles se asignan a glifos coherentes en la superposición.InputActionId
: Es el objetoInputIdentifier
que almacena el ID de número y la versión de laInputAction
(consulta Cómo hacer un seguimiento de IDs de teclas para obtener más información).InputRemappingOption
: EsInputEnums.REMAP_OPTION_ENABLED
oInputEnums.REMAP_OPTION_DISABLED
. Define si es posible reasignar la acción. Si tu juego no admite la reasignación, puedes omitir este campo o configurar la opción como inhabilitada.RemappedInputControls
: Es el objetoInputControls
de solo lectura que se usa para leer la tecla reasignada que configuró el usuario en eventos de reasignación (se usa para recibir notificaciones sobre eventos de reasignación).
InputControls
representa las entradas asociadas con una acción y contiene los siguientes campos:
AndroidKeycodes
: Es una lista de números enteros que representan las entradas del teclado que se asocian con una acción. Se definen en la clase KeyEvent o en la clase AndroidKeycode en Unity.MouseActions
es una lista de valoresMouseAction
que representan entradas del mouse que se asocian con esta acción.
Cómo definir tus grupos de entradas
Las InputActions
se agrupan con acciones relacionadas de forma lógica con InputGroups
para mejorar la navegación y la capacidad de detectar los controles en la superposición. Cada ID de InputGroup
debe ser único en todos los InputGroups
en tu juego.
Si organizas tus acciones de entrada en grupos, permites que un jugador encuentre la vinculación correcta de teclas para su contexto actual con mayor facilidad.
Si admites la reasignación, puedes definir qué InputGroups
se pueden reasignar. Si tu juego no admite la reasignación, deberías configurarla como inhabilitada para todos los InputGroups
, aunque el SDK de entrada es tan inteligente que la desactiva si no la admites en tu 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 );
En el siguiente ejemplo, se muestran los grupos de entradas de los controles de carretera y los controles de menú en la superposición.
InputGroup
tiene los siguientes campos:
GroupLabel
: Es una cadena que se muestra en la superposición y que se puede usar para agrupar un conjunto de acciones de forma lógica. Esta cadena no se localiza automáticamente.InputActions
: Es una lista de objetosInputAction
que defines en el paso anterior. Todas estas acciones se muestran visualmente bajo el encabezado del grupo.InputGroupId
: Es un objetoInputIdentifier
que almacena el ID de número y la versión delInputGroup
. Consulta Cómo hacer un seguimiento de IDs de teclas para obtener más información.InputRemappingOption
: EsInputEnums.REMAP_OPTION_ENABLED
oInputEnums.REMAP_OPTION_DISABLED
. Si se desactiva, se inhabilitará la reasignación en todos los objetosInputAction
que pertenezcan a este grupo, incluso si especifican que tienen habilitada esa opción. Si se habilita, todas las acciones que pertenezcan a este grupo se podrán reasignar, a menos que la acción individual especifique que se inhabilitó.
Cómo definir tus contextos de entrada
InputContexts
permite que tu juego use un conjunto distinto de controles de teclado para diferentes escenas. Por ejemplo:
- Puedes especificar conjuntos de entradas para navegar por menús distintos de los que se usan para el movimiento en el juego.
- Puedes especificar diferentes conjuntos de entradas según el modo de locomoción en el juego (por ejemplo, conducir frente a caminar).
- Puedes especificar diferentes conjuntos de entradas según el estado actual del juego (por ejemplo, explorar un inframundo frente a recorrer un nivel particular).
Cuando se utilizan InputContexts
, la superposición muestra primero los grupos del contexto en uso. Si quieres habilitar este comportamiento, llama a setInputContext()
para configurar el contexto cada vez que el juego entre a una escena distinta. En la siguiente imagen, se demuestra este comportamiento: en la escena de conducción, las acciones de los Road controls (Controles de carretera) se muestran en la parte superior de la superposición. Cuando se abre el menú "Store" (Tienda), las acciones de los "Menu controls" (Controles de menú) se muestran en la parte superior de la superposición.
Para lograr estas actualizaciones de la superposición, se configura un InputContext
diferente en distintos puntos del juego. Sigue estos pasos:
- Agrupa tus
InputActions
con acciones relacionadas de forma lógica usandoInputGroups
. - Asigna estos
InputGroups
a unInputContext
para diferentes partes del juego.
Los InputGroups
que pertenecen al mismo InputContext
no pueden tener InputActions
en conflicto cuando se usa la misma tecla. Una práctica recomendada es asignar cada InputGroup
a un solo InputContext
.
En el siguiente código de muestra, se demuestra la lógica de 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
tiene los siguientes campos:
LocalizedContextLabel
: Es una cadena que describe los grupos que pertenecen al contexto.InputContextId
: Es el objetoInputIdentifier
que almacena el ID de número y la versión delInputContext
(consulta Cómo hacer un seguimiento de IDs de teclas para obtener más información).ActiveGroups
: Es una lista deInputGroups
que se usarán y se mostrarán en la parte superior de la superposición cuando este contexto esté activo.
Cómo crear un mapa de entradas
Un InputMap
es una colección de todos los objetos InputGroup
disponibles en un juego y, por lo tanto, todos los objetos InputAction
que un jugador puede esperar realizar.
Cuando informas tus vinculaciones de teclas, creas un InputMap
con todos los InputGroups
que se usan en el juego.
Si el juego no admite la reasignación, configúrala como inhabilitada y deja las teclas reservadas vacías.
En el siguiente ejemplo, se crea un InputMap
que se usa para informar una colección de 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
tiene los siguientes campos:
InputGroups
: Son los InputGroups que informa tu juego. Los grupos se muestran en orden en la superposición, a menos que se especifique en los grupos actuales en uso llamando asetInputContext()
.MouseSettings
: El objetoMouseSettings
indica que la sensibilidad del mouse se puede ajustar y que el mouse se invierte en el eje y.InputMapId
: Es el objetoInputIdentifier
que almacena el ID de número y la versión delInputMap
(consulta Cómo hacer un seguimiento de IDs de teclas para obtener más información).InputRemappingOption
: EsInputEnums.REMAP_OPTION_ENABLED
oInputEnums.REMAP_OPTION_DISABLED
. Define si la función de reasignación está habilitada.ReservedControls
: Es una lista deInputControls
que los usuarios no podrán reasignar.
Cómo hacer un seguimiento de IDs de teclas
Los objetos InputAction
, InputGroup
, InputContext
y InputMap
contienen un objeto InputIdentifier
que almacena un ID de número único y un ID de versión de cadena.
Hacer un seguimiento de la versión de cadena de tu objeto es opcional, pero se recomienda hacer un seguimiento de las versiones de tu InputMap
. Si no se proporciona una versión de cadena, esta estará vacía. Se requiere una versión de cadena para los objetos InputMap
.
En el siguiente ejemplo, se asigna una versión de cadena a InputActions
o 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
Los IDs de número de los objetos InputAction
deben ser únicos en todas las InputActions
de tu InputMap
. De manera similar, los IDs de objeto de InputGroup
deben ser únicos en todos los InputGroups
de un InputMap
. En el siguiente ejemplo, se demuestra cómo usar una enum
para hacer un seguimiento de los IDs únicos de tu objeto:
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
tiene los siguientes campos:
UniqueId
: Es un ID de número único establecido para identificar claramente un conjunto determinado de datos de entrada de manera individual.VersionString
: Es una cadena de versión legible por humanos establecida para identificar una versión de datos de entrada entre 2 versiones de cambios en los datos.
Cómo recibir notificaciones sobre eventos de reasignación (opcional)
Recibe notificaciones sobre eventos de reasignación para estar al tanto de las teclas que se usan en tu juego. Esto permite que tu juego actualice los recursos que se muestran en la pantalla del juego y se usan para mostrar los controles de acciones.
En la siguiente imagen, se muestra un ejemplo de este comportamiento: después de que se reasignan las teclas
Para lograr esta funcionalidad, se registra una devolución de llamada InputRemappingListener
. Para implementar esta función, comienza por registrar una instancia de 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
recibe una notificación al momento de inicio después de cargar los controles reasignados que guardó el usuario y después de cada vez que este reasigna las teclas.
Inicialización
Si usas InputContexts
, establece el contexto en cada transición en una nueva escena, incluido el primer contexto que se utilizó para la escena inicial. Debes establecer el InputContext
después de haber registrado tu InputMap
.
Si usas InputRemappingListeners
para recibir notificaciones sobre eventos de reasignación, registra tu InputRemappingListener
antes de registrar el InputMappingProvider
. De lo contrario, es posible que el juego se pierda eventos importantes durante el tiempo de inicio.
En el siguiente ejemplo, se demuestra cómo inicializar la 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 } }
Limpieza
Anula el registro de la instancia de InputMappingProvider
y las instancias de InputRemappingListener
cuando se cierre el juego, aunque el SDK de entrada es tan inteligente que evitará filtrar recursos si no lo haces.
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 } }
Cómo realizar pruebas
Puedes probar la implementación del SDK de entrada abriendo manualmente la superposición para ver la experiencia del jugador o con el shell adb para pruebas y verificación automatizadas.
El emulador de Google Play Juegos para PC verifica si hay errores comunes en tu mapa de entradas para determinar si es correcto. En casos como IDs únicos duplicados, uso de mapas de entradas diferentes o incumplimiento de las reglas de reasignación (si está habilitada), la superposición muestra un mensaje de error como el siguiente:
Usa adb
en la línea de comandos para verificar la implementación del SDK de entrada.
Para obtener el mapa de entradas actual, usa el siguiente comando de adb shell
(reemplaza MY.PACKAGE.NAME
por el nombre de tu juego):
adb shell dumpsys input_mapping_service --get MY.PACKAGE.NAME
Si registraste correctamente tu InputMap
, verás un resultado como el siguiente:
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
}
Localización
El SDK de entrada no usa el sistema de localización de Android. Como resultado, debes proporcionar cadenas localizadas cuando envíes un InputMap
. También puedes usar el sistema de localización de tu motor de juego.
ProGuard
Cuando uses ProGuard para reducir tu juego, agrega las siguientes reglas al archivo de configuración de ProGuard para asegurarte de que no se quite el SDK del paquete final:
-keep class com.google.android.libraries.play.hpe.** { *; }
-keep class com.google.android.libraries.play.games.inputmapping.** { *; }
Próximos pasos
Después de integrar el SDK de entrada en el juego, puedes continuar con los requisitos restantes de Google Play Juegos para PC. Para obtener más información, consulta Cómo comenzar a usar Google Play Juegos para PC.