Ce document décrit comment configurer et afficher le SDK Input dans les jeux compatibles avec Google Play Jeux sur PC. Les tâches, telles que l'ajout du SDK à votre jeu et la génération d'un mappage des entrées, qui contient les différentes actions du jeu correspondant aux entrées utilisateur définies.
Avant de commencer
Avant d'ajouter le SDK Input à votre jeu, vous devez prendre en charge l'entrée au clavier et à la souris à l'aide du système d'entrée de votre moteur de jeu.
Le SDK Input fournit des informations à Google Play Jeux sur PC concernant les commandes que votre jeu utilise, afin qu'elles soient affichées à l'utilisateur. Il peut aussi, si vous le souhaitez, autoriser les utilisateurs à remapper leur clavier.
Chaque commande est une InputAction
(par exemple "S" pour "Sauter") et vous organisez vos InputActions
en InputGroups
. Un InputGroup
peut représenter un mode différent dans votre jeu, par exemple "Conduite", "Marche" ou "Menu principal". Vous pouvez aussi utiliser des InputContexts
pour indiquer quels groupes sont actifs à différents stades du jeu.
Vous pouvez activer la gestion automatique du remappage du clavier, mais si vous préférez fournir votre propre interface de remappage des commandes, vous pouvez désactiver le remappage du SDK Input.
Le diagramme séquentiel suivant décrit le fonctionnement de l'API du SDK Input.
Lorsque votre jeu implémente le SDK Input, vos commandes s'affichent dans la superposition Google Play Jeux sur PC.
La superposition Google Play Jeux sur PC
La superposition Google Play Jeux sur PC (la "superposition") affiche les commandes définies par votre jeu. Les utilisateurs peuvent accéder à la superposition à tout moment en appuyant sur Maj + Tab.
Bonnes pratiques pour la conception d'associations de touches
Lors de la conception d'associations de touches, tenez compte des bonnes pratiques suivantes :
- Regroupez vos
InputActions
enInputGroups
reliés de façon logique afin d'améliorer la navigation et la visibilité des commandes lors du jeu. - Attribuez chaque
InputGroup
à un seulInputContext
au maximum. UneInputMap
affinée offre une meilleure expérience pour naviguer dans les commandes dans la superposition. - Créez un
InputContext
pour chaque type de scène différent de votre jeu. Généralement, vous pouvez utiliser un seulInputContext
pour toutes vos scènes de menu. Utilisez desInputContexts
différents pour tout mini-jeu présent dans votre jeu ou pour des commandes alternatives au sein d'une même scène. - Si deux actions sont conçues pour utiliser la même touche avec le même
InputContext
, servez-vous de la chaîne de libellé pour le préciser, par exemple "Interagir/Tirer". - Si deux touches sont conçues pour être associées à la même
InputAction
, utilisez deuxInputActions
différentes qui effectuent la même action dans votre jeu. Vous pouvez utiliser la même chaîne de libellé pour les deuxInputActions
, mais leur identifiant doit être différent. - Si une touche de modification est appliquée à un ensemble de touches, envisagez de disposer d'une seule
InputAction
avec la touche de modification au lieu de plusieursInputActions
associées à une même touche de modification (par exemple : utilisez Maj et W, A, S, D au lieu de Maj + W, Maj + A, Maj + S, MAj + D). - Le remappage des entrées est automatiquement désactivé lorsque l'utilisateur écrit dans des champs de texte. Suivez les bonnes pratiques d'implémentation des champs de texte Android pour vous assurer qu'Android est capable de détecter les champs de texte dans votre jeu et éviter que les touches remappées interfèrent avec ces champs. Si votre jeu utilise des champs de texte non conventionnels, vous pouvez désactiver manuellement le remappage en utilisant
setInputContext()
avec unInputContext
contenant une liste d'InputGroups
vide. - Si votre jeu prend en charge le remappage, tenez compte du fait que la modification de vos associations de touches est une opération délicate qui peut entrer en conflit avec les associations de touches enregistrées par l'utilisateur. Dans la mesure du possible, évitez de modifier l'identifiant de commandes existantes.
La fonctionnalité de remappage
Google Play Jeux sur PC permet de remapper les commandes du clavier en fonction des associations de touches fournies par votre jeu à l'aide du SDK Input. Cette fonctionnalité est optionnelle et peut être totalement désactivée. Par exemple, vous pouvez fournir votre propre interface de remappage de clavier. Pour désactiver le remappage pour votre jeu, il vous suffit de désactiver l'option de remappage pour votre InputMap
(consultez Concevoir une InputMap pour en savoir plus).
Pour accéder à cette fonctionnalité, les utilisateurs doivent ouvrir la superposition, puis cliquer sur l'action qu'ils souhaitent remapper. Après chaque événement de remappage, Google Play Jeux sur PC mappe chaque commande remappée par l'utilisateur aux commandes par défaut que votre jeu s'attend à recevoir, sans signalement à votre jeu du remappage effectué par le joueur. Si vous le souhaitez, vous pouvez modifier les éléments permettant d'afficher les commandes du clavier dans votre jeu en ajoutant un rappel pour les événements de remappage.
Google Play Jeux sur PC stocke localement les commandes remappées pour chaque utilisateur, ce qui permet de les conserver d'une session de jeu à une autre. Ces informations sont stockées sur le disque uniquement sur PC et n'ont aucun effet sur l'expérience mobile. Les données de commande sont supprimées lorsque l'utilisateur désinstalle ou réinstalle Google Play Jeux sur PC. Ces données ne sont pas conservées d'un PC à l'autre.
Pour prendre en charge la fonctionnalité de remappage dans votre jeu, évitez les restrictions suivantes :
Restrictions de remappage
Les fonctionnalités de remappage peuvent être désactivées dans votre jeu si l'un des cas suivants s'applique pour les associations de touches :
- Des
InputActions
qui utilisent plusieurs touches ne sont pas composées d'une touche de modification et d'une touche de non-modification. Par exemple, Maj + A est valide, mais A + B, Ctrl + Alt ou Maj + A + Tab ne l'est pas. - L'
InputMap
contient desInputActions
, desInputGroups
ou desInputContexts
avec des identifiants uniques répétés.
Limitations du remappage
Lors de la conception d'associations de touches pour le remappage, tenez compte des limitations suivantes :
- Le remappage à des combinaisons de touches n'est pas pris en charge. Par exemple, les utilisateurs ne peuvent pas remapper Maj + A à Ctrl + B ni A à Maj + A.
- Le remappage n'est pas pris en charge pour les
InputActions
avec les boutons de la souris. Par exemple, la combinaison Maj + Clic droit ne peut pas être remappée.
Tester le remappage des touches sur l'émulateur Google Play Jeux sur PC
Vous pouvez activer la fonctionnalité de remappage à tout moment dans l'émulateur Google Play Jeux sur PC en émettant la commande adb suivante :
adb shell dumpsys input_mapping_service --set RemappingFlagValue true
La superposition est modifiée comme dans l'exemple suivant :
Ajouter le SDK
Installez le SDK Input conformément à votre plate-forme de développement.
Java et Kotlin
Obtenez le SDK Input pour Java ou Kotlin en ajoutant une dépendance à votre fichier build.gradle
au niveau du module :
dependencies {
implementation 'com.google.android.libraries.play.games:inputmapping:1.1.1-beta'
...
}
Unity
Le SDK Input est un package Unity standard avec plusieurs dépendances.
L'installation du package avec toutes les dépendances est requise. Il existe plusieurs façons d'installer les packages.
Installer le fichier .unitypackage
Téléchargez le fichier unitypackage du SDK Input
avec toutes ses dépendances. Vous pouvez installer le fichier .unitypackage
en sélectionnant Assets > Import package > Custom Package (Éléments > Importer un package > Package personnalisé) et en trouvant le fichier que vous avez téléchargé.
Installer à l'aide d'UPM
Vous pouvez aussi installer le package à l'aide du gestionnaire de paquets Unity en téléchargeant le fichier .tgz
et en installant ses dépendances:
- 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 (ou en sélectionnant le fichier tgz de cette archive)
Installer à l'aide d'OpenUPM
Vous pouvez installer le package à l'aide d'OpenUPM.
$ openupm add com.google.android.libraries.play.games.inputmapping
Exemples de jeux
Pour obtenir des exemples d'intégration avec le SDK Input, consultez la page Tunnel AGDK pour les jeux Kotlin ou Java et Trivial Kart pour les jeux Unity.
Générer vos associations de clés
Enregistrez vos associations de touches en créant une InputMap
et en la retournant avec un InputMappingProvider
. L'exemple suivant illustre 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
Définir vos actions d'entrée
La classe InputAction
permet de mapper une touche ou une combinaison de touches pour l'associer à une action de jeu. Les InputActions
doivent avoir un identifiant unique entre les différents InputActions
.
Si vous prenez en charge le remappage, vous pouvez définir quelles InputActions
peuvent être remappées. Si votre jeu ne prend pas en charge le remappage, nous vous invitons à désactiver cette option pour toutes vos InputActions
, mais le SDK Input est suffisamment intelligent pour désactiver le remappage si vous ne le prenez pas en charge dans votre InputMap
.
L'exemple suivant mappe la touche
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 );
Les actions peuvent également être déclenchées par des entrées de la souris. L'exemple suivant associe le clic gauche à l'action Move (Déplacement) :
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 );
Des combinaisons de touches peuvent être spécifiées par l'envoi de plusieurs codes de touche à l'élément InputAction
. Dans cet exemple, la combinaison
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 );
Le SDK Input vous permet de combiner les boutons de la souris et les touches pour une même action. Dans l'exemple ci-dessous, les touches
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 );
L'élément InputAction possède les champs suivants :
ActionLabel
: la chaîne affichée dans l'interface utilisateur pour représenter cette action. La localisation n'est pas effectuée de façon automatique, donc effectuez toute localisation à l'avance.InputControls
: définit les commandes d'entrée utilisées par cette action. Des symboles correspondants s'affichent dans la superposition.InputActionId
: objetInputIdentifier
qui stocke le numéro d'identifiant et la version de l'élémentInputAction
(consultez la section Suivi des identifiants de touches pour en savoir plus).InputRemappingOption
:InputEnums.REMAP_OPTION_ENABLED
ouInputEnums.REMAP_OPTION_DISABLED
. Définit si l'action peut être remappée. Si votre jeu ne prend pas en charge le remappage, vous pouvez ignorer ce champ ou le définir simplement sur désactivé.RemappedInputControls
: objetInputControls
en lecture seule permettant de lire la touche remappée définie par l'utilisateur lors des événements de remappage (utilisé pour être averti lors des événements de remappage).
InputControls
représente les entrées associées à une action et contient les champs suivants :
AndroidKeycodes
: liste d'entiers représentant les entrées au clavier associées à une action. Ces éléments sont définis dans la classe KeyEvent ou dans la classe AndroidKeycode pour Unity.MouseActions
: liste de valeursMouseAction
représentant les saisies à la souris associées à cette action.
Définir vos groupes d'entrées
Les éléments InputActions
dont les actions sont liées de façon logique sont regroupés à l'aide d'InputGroups
afin d'améliorer la navigation et la visibilité des commandes dans la superposition. Chaque ID d'InputGroup
doit être unique parmi tous les InputGroups
de votre jeu.
En organisant vos actions d'entrées en groupes, vous aidez le joueur à identifier plus facilement l'association de touche qui correspond à son contexte actuel.
Si vous prenez en charge le remappage, vous pouvez définir quels InputGroups
peuvent être remappés. Si votre jeu ne prend pas en charge le remappage, nous vous invitons à désactiver cette option pour toutes vos InputGroups
, mais le SDK Input est suffisamment intelligent pour désactiver le remappage si vous ne le prenez pas en charge dans votre 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 );
L'exemple suivant affiche les groupes d'entrée Road controls (Commandes de conduite) et Menu controls (Commandes de menu) dans la superposition :
InputGroup
comporte les champs suivants :
GroupLabel
: chaîne à afficher dans la superposition et qui permet de regrouper un ensemble d'actions de façon logique. Cette chaîne n'est pas localisée automatiquement.InputActions
: liste des objetsInputAction
que vous avez définis dans l'étape précédente. Toutes ces actions s'affichent de façon visuelle sous l'en-tête de groupe.InputGroupId
: objetInputIdentifier
qui stocke le numéro d'identifiant et la version de l'InputGroup
. Pour en savoir plus, consultez la section Suivi des identifiants de touches.InputRemappingOption
:InputEnums.REMAP_OPTION_ENABLED
ouInputEnums.REMAP_OPTION_DISABLED
. Si cette option est désactivée, tous les objetsInputAction
appartenant à ce groupe verront leur remappage désactivé, même si leur option individuelle de remappage est activée. Si cette option est activée, toutes les actions appartenant à ce groupe peuvent être remappées, sauf si leur option individuelle de remappage est désactivée.
Définir vos contextes d'entrée
InputContexts
permet à votre jeu d'utiliser un ensemble de commandes au clavier distinct pour différentes scènes de votre jeu. Par exemple :
- Vous pouvez spécifier des ensembles d'entrées distincts pour naviguer dans le menu et pour vous déplacer dans le jeu.
- Vous pouvez spécifier différents ensembles d'entrées en fonction du mode de locomotion dans votre jeu (conduite ou marche, par exemple).
- Vous pouvez spécifier des ensembles d'entrées distincts en fonction de l'état actuel de votre jeu à un moment donné, par exemple lorsque vous naviguez sur une carte ou lorsque vous jouez à un niveau en particulier.
Lors de l'utilisation d'InputContexts
, la superposition affiche le groupe correspondant au contexte en cours en premier. Pour activer ce comportement, appelez setInputContext()
pour définir le contexte chaque fois que votre jeu entre dans une scène différente. L'image suivante illustre ce comportement dans la scène de conduite. Le groupe Road controls (Commandes de conduite) s'affiche en haut de la superposition. Lorsque vous ouvrez le menu de boutique, les actions "Menu controls" (Commandes de menu) s'affichent en haut de la superposition.
Ces modifications de la superposition sont obtenues en définissant un InputContext
à différents endroits de votre jeu. Pour ce faire, procédez comme suit :
- Regroupez vos
InputActions
dont les actions sont liées de façon logique à l'aide d'InputGroups
. - Affectez ces
InputGroups
à unInputContext
correspondant aux différentes parties de votre jeu.
Les InputGroups
qui font partie du même InputContext
ne peuvent pas avoir d'InputActions
en conflit pour lesquelles la même touche est utilisée. Il est recommandé d'attribuer chaque InputGroup
à un InputContext
unique.
L'exemple de code suivant illustre la logique de l'élément 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
comporte les champs suivants :
LocalizedContextLabel
: chaîne décrivant les groupes qui font partie du contexte.InputContextId
: objetInputIdentifier
qui stocke le numéro d'identifiant et la version de l'élémentInputContext
(consultez la section Suivi des identifiants de touches pour en savoir plus).ActiveGroups
: liste desInputGroups
à utiliser et qui s'affichent en haut de la superposition lorsque ce contexte est actif.
Créer un mappage d'entrées
Un élément InputMap
est une collection de tous les objets InputGroup
disponibles dans un jeu, c'est-à-dire de tous les objets InputAction
qu'un joueur peut s'attendre à exécuter.
Lorsque vous signalez vos associations de touches, vous créez une InputMap
contenant tous les InputGroups
utilisés dans votre jeu.
Si votre jeu ne prend pas en charge le remappage, désactivez l'option de remappage et définissez les touches réservées comme vides.
L'exemple suivant crée une InputMap
permettant de signaler un ensemble d'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
comporte les champs suivants :
InputGroups
: les InputGroups signalés par votre jeu. Les groupes s'affichent dans l'ordre dans la superposition, sauf si vous spécifiez les groupes actuellement utilisés en appelantsetInputContext()
.MouseSettings
: l'objetMouseSettings
indique que la sensibilité de la souris peut être ajustée et que les mouvements de souris sont inversés sur l'axe y.InputMapId
: objetInputIdentifier
qui stocke le numéro d'identifiant et la version de l'élémentInputMap
(consultez la section Suivi des identifiants de touches pour en savoir plus).InputRemappingOption
:InputEnums.REMAP_OPTION_ENABLED
ouInputEnums.REMAP_OPTION_DISABLED
. Définit si la fonctionnalité de remappage est activée.ReservedControls
: une liste d'InputControls
que les utilisateurs n'auront pas le droit de remapper.
Suivi des identifiants de touches
Les objets InputAction
, InputGroup
, InputContext
et InputMap
contiennent un objet InputIdentifier
qui stocke un numéro d'identifiant unique et un identifiant de version de chaîne.
Le suivi de la version de chaîne de vos objets est facultative, mais recommandée pour suivre les versions de votre InputMap
. Si la version de chaîne n'est pas fournie, la chaîne est vide. Une version de chaîne est requise pour les objets InputMap
.
L'exemple suivant attribue une version de chaîne à des InputActions
ou à des 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
Les numéros d'identifiant des objets InputAction
doivent être uniques parmi tous les InputActions
de votre InputMap
. De même, les identifiants des objets InputGroup
doivent être uniques parmi tous les InputGroups
de votre InputMap
. L'exemple suivant illustre comment suivre les identifiants uniques de vos objets à l'aide d'un 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
comporte les champs suivants :
UniqueId
: un numéro d'identifiant unique est défini pour identifier clairement et sans équivoque un ensemble de données d'entrée.VersionString
: une chaîne de version lisible par l'humain et définie pour identifier une version de données d'entrée parmi deux versions de modifications de données d'entrée.
Recevoir une notification lors d'événements de remappage (facultatif)
Recevez des notifications lors d'événements de remappage afin d'être informé des touches en cours d'utilisation dans votre jeu. Cela permet à votre jeu de mettre à jour les éléments présents à l'écran du jeu et qui permettent d'afficher les commandes des actions.
L'image suivante illustre un exemple de ce comportement où, après le remappage des touches
Cette fonctionnalité est obtenue en enregistrant un rappel InputRemappingListener
. Pour implémenter cette fonctionnalité, commencez par enregistrer une instance 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
Le InputRemappingListener
est averti lors du lancement après le chargement des commandes remappées enregistrées par l'utilisateur, et chaque fois que l'utilisateur remappe ses touches.
Initialisation
Si vous utilisez des InputContexts
, définissez le contexte lors de chaque transition vers une nouvelle scène, y compris le premier contexte utilisé pour votre scène initiale. Vous devez définir l'InputContext
après avoir enregistré votre InputMap
.
Si vous utilisez des InputRemappingListeners
pour être averti lors d'événements de remappage, enregistrez votre InputRemappingListener
avant d'enregistrer votre InputMappingProvider
. Dans le cas contraire, votre jeu peut manquer des événements importants au moment du lancement.
L'exemple suivant montre comment initialiser l'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 } }
Nettoyage
Désinscrivez votre instance InputMappingProvider
et toute instance InputRemappingListener
lorsque votre jeu est fermé, même si le SDK Input est assez intelligent pour éviter les fuites de ressources si vous ne le faites pas :
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 } }
Test
Vous pouvez tester votre implémentation du SDK Input en ouvrant manuellement la superposition afin d'afficher l'expérience utilisateur. Vous pouvez aussi vous servir du shell adb pour un test et une vérification automatisés.
L'émulateur Google Play Jeux sur PC vérifie l'exactitude de votre mappe d'entrée en vérifiant les erreurs courantes. Pour certains scénarios, par exemple des identifiants uniques en double, une utilisation de mappages d'entrée différents ou l'échec des règles de remappage (si le remappage est activé), la superposition affiche un message d'erreur comme celui ci-dessous :
Vérifiez votre implémentation du SDK Input à l'aide d'adb
dans la ligne de commande.
Pour obtenir le mappage actuel des entrées, utilisez la commande adb shell
suivante (remplacez MY.PACKAGE.NAME
par le nom de votre jeu) :
adb shell dumpsys input_mapping_service --get MY.PACKAGE.NAME
Un résultat semblable aux lignes suivantes devrait s'afficher si vous avez correctement enregistré votre 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
}
Localisation
Le SDK Input n'utilise pas le système de localisation d'Android. Par conséquent, vous devez fournir des chaînes localisées lorsque vous envoyez une propriété InputMap
. Vous pouvez aussi utiliser le système de localisation de votre moteur de jeu.
Proguard
Lorsque vous utilisez Proguard pour minimiser votre jeu, ajoutez les règles suivantes au fichier de configuration de Proguard pour vous assurer que le SDK n'est pas supprimé de votre package final :
-keep class com.google.android.libraries.play.hpe.** { *; }
-keep class com.google.android.libraries.play.games.inputmapping.** { *; }
Étapes suivantes
Après avoir intégré le SDK Input dans votre jeu, vous pouvez passer aux autres conditions à remplir pour Google Play Jeux sur PC. Pour en savoir plus, consultez Premiers pas avec Google Play Jeux sur PC.