Questo documento descrive come configurare e visualizzare l'SDK di immissione nei giochi che supportano Google Play Giochi su PC. Le attività includono l'aggiunta dell'SDK al gioco e la generazione di una mappa di input contenente le assegnazioni delle azioni di gioco all'input dell'utente.
Prima di iniziare
Prima di aggiungere l'SDK di Input al tuo gioco, devi supportare l'input da tastiera e mouse utilizzando il sistema di input del tuo motore di gioco.
Input SDK fornisce a Google Play Giochi su PC informazioni sui controlli usati dal tuo gioco, in modo che possano essere mostrati all'utente. Può anche consentire la rimappatura della tastiera per gli utenti.
Ogni controllo è un InputAction
(ad esempio "J" per "Passa") e organizzi InputActions
in InputGroups
. Un InputGroup
potrebbe rappresentare una modalità diversa nel gioco, ad esempio "Auto", "A piedi" o "Menu principale". Puoi anche utilizzare InputContexts
per indicare quali gruppi sono attivi in diversi punti della partita.
Puoi abilitare la gestione automatica della rimappatura della tastiera, ma se preferisci fornire una tua interfaccia di rimappatura della tastiera, puoi disabilitare la ricostruzione della mappatura dell'SDK di input.
Il seguente diagramma di sequenza descrive il funzionamento dell'API di Input SDK:
Quando il tuo gioco implementa l'SDK di input, i controlli vengono visualizzati nell'overlay di Google Play Giochi su PC.
Overlay di Google Play Giochi su PC
L'overlay di Google Play Giochi su PC ("l'overlay") mostra i controlli definiti dal tuo gioco. Gli utenti possono accedere all'overlay in qualsiasi momento premendo Maiusc + Tab.
Best practice per la progettazione di associazioni di chiavi
Quando progetti le associazioni di chiavi, tieni presente le seguenti best practice:
- Raggruppa
InputActions
inInputGroups
logicamente correlate per migliorare la navigazione e la rilevabilità dei controlli durante il gameplay. - Assegna ogni
InputGroup
a un massimo di unInputContext
. La granularitàInputMap
offre un'esperienza migliore di navigazione dei controlli nell'overlay. - Crea un
InputContext
per ogni tipo di scena del gioco. In genere, puoi utilizzare un singoloInputContext
per tutte le scene "come un menù". UsaInputContexts
diversi per ogni minigiochi nel gioco o per i controlli alternativi per una singola scena. - Se due azioni sono progettate per utilizzare la stessa chiave nello stesso
InputContext
, utilizza la stringa di etichetta, ad esempio "Interazione / Attiva". - Se due chiavi sono progettate per associarsi allo stesso
InputAction
, utilizza 2InputActions
diversi che eseguono la stessa azione nel gioco. Puoi utilizzare la stessa stringa di etichetta per entrambiInputActions
, ma il suo ID deve essere diverso. - Se un tasto di modifica viene applicato a un insieme di tasti, valuta la possibilità di utilizzare un solo
InputAction
con il tasto di modifica anziché piùInputActions
che combinano il tasto di modifica (ad esempio, usa Maiusc e W, A, S, D al posto di Maiusc + W, Maiusc + A, Maiusc + S, Maiusc + D). - La rimappatura degli input viene disabilitata automaticamente quando l'utente scrive nei campi di testo. Segui le best practice per l'implementazione dei campi di testo di Android per assicurarti che Android possa rilevare i campi di testo nel tuo gioco ed evitare che le chiavi rimappate interferiscano con loro. Se il gioco deve utilizzare campi di testo non convenzionali, puoi usare
setInputContext()
con unInputContext
contenente un elenco vuoto diInputGroups
per disattivare la rimappatura manuale. - Se il tuo gioco supporta la rimappatura, valuta la possibilità di aggiornare le associazioni di chiavi come operazione sensibile che può essere in conflitto con le versioni salvate dall'utente. Se possibile, evita di modificare gli ID dei controlli esistenti.
La funzionalità di rimappatura
Google Play Giochi su PC supporta la rimappatura dei controlli da tastiera in base alle associazioni di tasti fornite dal tuo gioco usando l'SDK di immissione. Questa opzione è facoltativa e
può essere completamente disattivata. Ad esempio, potresti voler fornire la tua
interfaccia di rimappatura della tastiera. Per disattivare la rimappatura del tuo gioco, devi solo specificare
l'opzione di rimappatura disabilitata per il tuo InputMap
(per ulteriori informazioni, consulta la pagina
Creare un InputMap).
Per accedere a questa funzionalità, gli utenti devono aprire l'overlay e fare clic sull'azione da rimappare. Dopo ogni evento di rimappatura, Google Play Giochi su PC mappa ogni controllo rimappato dall'utente ai controlli predefiniti che il gioco si aspetta di ricevere, quindi non è necessario che il gioco sia a conoscenza della rimappatura del giocatore. Se vuoi, puoi aggiornare gli asset utilizzati per visualizzare i controlli da tastiera nel gioco aggiungendo un callback per la rimappatura degli eventi.
Google Play Giochi su PC archivia i controlli rimappati localmente per ogni utente, consentendo la persistenza del controllo tra le sessioni di gioco. Queste informazioni vengono memorizzate su disco solo per la piattaforma PC e non influiscono sull'esperienza mobile. I dati di controllo vengono eliminati quando l'utente disinstalla o reinstalla Google Play Giochi su PC. Questi dati non vengono permanenti su più PC.
Per supportare la funzionalità di rimappatura nel tuo gioco, evita le seguenti limitazioni:
Limitazioni della rimappatura
Le funzionalità di rimappatura possono essere disattivate nel gioco se le associazioni di chiavi contengono uno dei seguenti casi:
InputActions
a più tasti non composti da un tasto di modifica + un tasto non di modifica. Ad esempio, Maiusc + A è valido, mentre A + B, Ctrl + Alt o Maiusc + A + Tab non lo sono.InputMap
contieneInputActions
,InputGroups
oInputContexts
con ID univoci ripetuti.
Limitazioni della rimappatura
Quando progetti le associazioni di chiavi per la rimappatura, considera le seguenti limitazioni:
- La rimappatura alle combinazioni di tasti non è supportata. Ad esempio, gli utenti non possono rimappare Maiusc + A con Ctrl + B o A con Maiusc + A.
- La rimappatura non è supportata per
InputActions
con i pulsanti del mouse. Ad esempio, non è possibile rimappare Maiusc + clic destro.
Testa la rimappatura dei tasti nell'emulatore di Google Play Giochi su PC
Puoi attivare la funzionalità di rimappatura nell'emulatore di Google Play Giochi su PC in qualsiasi momento emettendo il seguente comando adb:
adb shell dumpsys input_mapping_service --set RemappingFlagValue true
L'overlay cambia come nell'immagine seguente:
Aggiungi l'SDK
Installa l'SDK di input in base alla tua piattaforma di sviluppo.
Java e Kotlin
Scarica l'SDK di input per Java o Kotlin aggiungendo una dipendenza al
file build.gradle
a livello di modulo:
dependencies {
implementation 'com.google.android.libraries.play.games:inputmapping:1.1.0-beta'
...
}
Unity
Input SDK è un pacchetto Unity standard con diverse dipendenze.
È necessario installare il pacchetto con tutte le dipendenze. Esistono diversi modi per installare i pacchetti.
Installa .unitypackage
Scarica il file unitypackage di Input SDK con tutte le sue dipendenze. Puoi installare .unitypackage
selezionando
Asset > Importa pacchetto > Pacchetto personalizzato e individua il file che hai scaricato.
Installazione tramite UPM
In alternativa, puoi installare il pacchetto utilizzando il Gestione pacchetti di Unity, scaricando il .tgz
e installando le sue dipendenze:
- 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.0-beta
Installa con OpenUPM
Puoi installare il pacchetto utilizzando OpenUPM.
$ openupm add com.google.android.libraries.play.games.inputmapping
Giochi di esempio
Per esempi sull'integrazione con l'SDK di input, consulta le pagine Tunnel AGDK per i giochi Kotlin o Java e Trivial Kart per i giochi Unity.
Generare le associazioni di chiavi
Registra le associazioni di chiavi creando un InputMap
e restituendolo con un InputMappingProvider
. L'esempio seguente illustra 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
Definisci le azioni di input
La classe InputAction
viene utilizzata per mappare una chiave o una combinazione di tasti a un'azione
di gioco. InputActions
deve avere ID univoci in tutti i InputActions
.
Se supporti la rimappatura, puoi definire quali elementi di InputActions
possono essere rimappati. Se il tuo gioco non supporta la rimappatura, devi impostarla
disattivata per tutti i tuoi InputActions
, ma l'SDK di input
è abbastanza intelligente da disattivare la rimappatura se non la supporta nel tuo
InputMap
.
In questo esempio la chiave
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 );
Le azioni possono anche rappresentare gli input del mouse. Questo esempio imposta Clic con il tasto sinistro sull'azione Sposta:
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 );
Le combinazioni di chiavi vengono specificate passando più codici di chiave a InputAction
. In questo esempio,
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 );
L'SDK di input ti consente di combinare i pulsanti del mouse e dei tasti per una singola azione. Questo esempio indica che
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 ha i seguenti campi:
ActionLabel
: la stringa visualizzata nell'interfaccia utente per rappresentare questa azione. La localizzazione non avviene automaticamente, quindi esegui subito la localizzazione.InputControls
: definisce i controlli di input utilizzati da questa azione. I controlli vengono mappati a glifi coerenti nell'overlay.InputActionId
: oggettoInputIdentifier
che memorizza l'ID numero e la versione delInputAction
(consulta ID chiave di monitoraggio per ulteriori informazioni).InputRemappingOption
: uno traInputEnums.REMAP_OPTION_ENABLED
oInputEnums.REMAP_OPTION_DISABLED
. Definisce se è abilitata la rimappatura dell'azione. Se il tuo gioco non supporta la rimappatura, puoi saltare questo campo o impostarlo semplicemente come disattivato.RemappedInputControls
: oggettoInputControls
di sola lettura utilizzato per leggere la chiave rimappata impostata dall'utente al momento della rimappatura degli eventi (utilizzato per ricevere notifiche sugli eventi di rimappatura).
InputControls
rappresenta gli input associati a un'azione e contiene i seguenti campi:
AndroidKeycodes
: è un elenco di numeri interi che rappresentano gli input da tastiera associati a un'azione. Questi sono definiti nella classe KeyEvent o nella classe AndroidKeycode per Unity.MouseActions
: è un elenco di valoriMouseAction
che rappresentano gli input del mouse associati a questa azione.
Definisci i gruppi di input
InputActions
vengono raggruppate con azioni correlate logicamente utilizzando InputGroups
per
migliorare la navigazione e la rilevabilità dei controlli nell'overlay. Ogni
ID InputGroup
deve essere univoco in tutti gli elementi InputGroups
nel gioco.
Organizzando le azioni di input in gruppi, consenti a un giocatore di trovare più facilmente l'associazione di tasti corretta per il suo contesto attuale.
Se supporti la rimappatura, puoi definire quali elementi di InputGroups
possono essere rimappati. Se il tuo gioco non supporta la rimappatura, devi impostarla
disattivata per tutti i tuoi InputGroups
, ma l'SDK di input
è abbastanza intelligente da disattivare la rimappatura se non la supporta nel tuo
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'esempio seguente mostra i gruppi di input Controlli della strada e Controlli del menu nell'overlay:
InputGroup
include i seguenti campi:
GroupLabel
: una stringa da visualizzare nell'overlay che può essere utilizzata per raggruppare logicamente un insieme di azioni. Questa stringa non viene localizzata automaticamente.InputActions
: un elenco diInputAction
oggetti che hai definito nel passaggio precedente. Tutte queste azioni vengono mostrate sotto l'intestazione del gruppo.InputGroupId
: oggettoInputIdentifier
che memorizza l'ID numero e la versione diInputGroup
. Per ulteriori informazioni, consulta la sezione ID chiave di monitoraggio.InputRemappingOption
: uno traInputEnums.REMAP_OPTION_ENABLED
oInputEnums.REMAP_OPTION_DISABLED
. Se disabilitata, la rimappatura di tutti gli oggettiInputAction
appartenenti a questo gruppo sarà disabilitata anche se specificano la relativa opzione di rimappatura abilitata. Se questa opzione è abilitata, tutte le azioni appartenenti a questo gruppo possono essere rimappabili, a meno che non siano specificate disattivate dalle singole azioni.
Definisci i contesti di input
InputContexts
consente al tuo gioco di usare un set diverso di controlli da tastiera per le diverse scene del gioco. Ecco alcuni esempi:
- Puoi specificare diversi insiemi di input per la navigazione nei menu rispetto allo spostamento nel gioco.
- Puoi specificare diversi set di input a seconda della modalità di locomozione nel gioco, ad esempio guidare anziché camminare.
- Puoi specificare diversi set di input in base allo stato attuale del gioco, ad esempio affrontare un overworld piuttosto che giocare a un livello individuale.
Quando utilizzi InputContexts
, l'overlay mostra prima i gruppi del contesto
in uso. Per attivare questo comportamento, chiama setInputContext()
per impostare il
contesto ogni volta che il gioco entra in una scena diversa. La seguente immagine
mostra questo comportamento: nella scena "guida", le azioni Controlli della strada
vengono mostrate nella parte superiore dell'overlay. Quando apri il menu "Negozio", nella parte superiore dell'overlay vengono
visualizzate le azioni "Controlli del menu".
Questi aggiornamenti in overlay sono disponibili impostando un InputContext
diverso in diversi punti del gioco. Per farlo:
- Raggruppa
InputActions
con azioni correlate logicamente utilizzandoInputGroups
- Assegna questi
InputGroups
a unInputContext
per le diverse parti del gioco
InputGroups
che appartengono alla stessa chiaveInputContext
non possono avere contenuti in conflittoInputActions
quando viene utilizzata la stessa chiave. È buona norma assegnare ogni InputGroup
a un singolo InputContext
.
Il seguente codice di esempio dimostra la logica di 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
include i seguenti campi:
LocalizedContextLabel
: una stringa che descrive i gruppi che appartengono al contesto.InputContextId
: oggettoInputIdentifier
che memorizza l'ID numero e la versione delInputContext
(consulta ID chiave di monitoraggio per ulteriori informazioni).ActiveGroups
: un elenco diInputGroups
da utilizzare e da visualizzare nella parte superiore dell'overlay quando questo contesto è attivo.
Crea una mappa di input
Un InputMap
è una raccolta di tutti gli oggetti InputGroup
disponibili in un
gioco, e pertanto tutti gli oggetti InputAction
che un giocatore può aspettarsi
di eseguire.
Quando segnali le associazioni di chiavi, crei un InputMap
con tutti gli InputGroups
utilizzati nel gioco.
Se il tuo gioco non supporta la rimappatura, imposta l'opzione di rimappatura disattivata e le chiavi riservate vuote.
L'esempio seguente crea un InputMap
utilizzato per segnalare una raccolta di
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
include i seguenti campi:
InputGroups
: gli InputGroups segnalati dal tuo gioco. I gruppi vengono visualizzati in ordine nell'overlay, a meno che non vengano specificati i gruppi attuali in uso durante la chiamata asetInputContext()
.MouseSettings
: l'oggettoMouseSettings
indica che la sensibilità del mouse può essere regolata e che il mouse è invertito sull'asse y.InputMapId
: oggettoInputIdentifier
che memorizza l'ID numero e la versione delInputMap
(consulta ID chiave di monitoraggio per ulteriori informazioni).InputRemappingOption
: uno traInputEnums.REMAP_OPTION_ENABLED
oInputEnums.REMAP_OPTION_DISABLED
. Definisce se la funzionalità di rimappatura è abilitata.ReservedControls
: un elenco diInputControls
a cui gli utenti non possono rimappare.
ID chiave di monitoraggio
Gli oggetti InputAction
, InputGroup
, InputContext
e InputMap
contengono un
oggetto InputIdentifier
che archivia un ID numero univoco e un ID versione della stringa.
Il monitoraggio della versione stringa degli oggetti è facoltativo, ma è consigliato monitorare le versioni di InputMap
. Se non viene fornita la versione stringa,
la stringa è vuota. Una versione stringa è obbligatoria per InputMap
oggetti.
L'esempio seguente assegna una versione stringa 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
Gli ID numero di oggetti InputAction
devono essere univoci in tutti i InputActions
in
InputMap
. Allo stesso modo, gli ID oggetto InputGroup
devono essere univoci in tutti gli elementi InputGroups
in un InputMap
. Il seguente esempio mostra come utilizzare enum
per monitorare gli ID univoci dell'oggetto:
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
include i seguenti campi:
UniqueId
: un ID numero univoco impostato per identificare chiaramente un determinato insieme di dati di input in modo univoco.VersionString
: una stringa di versione leggibile impostata per identificare una versione dei dati di input tra due versioni delle modifiche ai dati di input.
(Facoltativo) Ricevi notifiche sulla rimappatura degli eventi
Ricevi notifiche sugli eventi di rimappa per essere informati dei tasti utilizzati nel gioco. In questo modo il gioco può aggiornare le risorse usate nella schermata del gioco per mostrare i controlli di azione.
L'immagine seguente mostra un esempio di questo comportamento in cui, dopo la rimappatura delle chiavi
Questa funzionalità si ottiene registrando un callback InputRemappingListener
. Per implementare questa funzione, inizia registrando un'istanza 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
riceve una notifica al momento dell'avvio dopo aver caricato i controlli rimappati salvati dall'utente e dopo ogni volta che l'utente rimappa i tasti.
Inizializzazione
Se utilizzi InputContexts
, imposta il contesto di ogni transizione a una nuova scena, incluso il primo contesto utilizzato per la scena iniziale. Devi impostare InputContext
dopo aver registrato il tuo InputMap
.
Se utilizzi InputRemappingListeners
per ricevere notifiche sulla nuova mappatura degli eventi,
registra il tuo InputRemappingListener
prima di registrare il tuo
InputMappingProvider
, altrimenti il gioco potrebbe perderti eventi importanti durante
il momento del lancio.
Il seguente esempio mostra come inizializzare 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 } }
Pulizia
Annulla la registrazione dell'istanza InputMappingProvider
e di eventuali istanze InputRemappingListener
alla chiusura del gioco, anche se l'SDK di input è sufficientemente intelligente da evitare fughe di risorse in caso contrario:
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
Puoi testare l'implementazione dell'SDK di input aprendo manualmente l'overlay per visualizzare l'esperienza del player o tramite la shell adb per test e verifica automatici.
L'emulatore di Google Play Giochi su PC verifica la correttezza della mappa di input da errori comuni. Per scenari come ID univoci duplicati, utilizzo di mappe di input diverse o mancato funzionamento delle regole di rimappatura (se la rimappatura è abilitata), l'overlay mostra un messaggio di errore come riportato di seguito:
Verifica l'implementazione dell'SDK di input utilizzando adb
nella riga di comando.
Per visualizzare la mappa di input corrente, utilizza il seguente comando adb shell
(sostituisci MY.PACKAGE.NAME
con il nome del tuo gioco):
adb shell dumpsys input_mapping_service --get MY.PACKAGE.NAME
Vedrai un output simile a questo se hai registrato correttamente 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
}
Localizzazione
Input SDK non utilizza il sistema di localizzazione di Android. Di conseguenza, devi fornire stringhe localizzate quando invii un elemento InputMap
. Puoi anche utilizzare il sistema di localizzazione del tuo motore di gioco.
ProGuard
Quando utilizzi Proguard per minimizzare il gioco, aggiungi le seguenti regole al file di configurazione di Proguard per assicurarti che l'SDK non venga rimosso dal pacchetto finale:
-keep class com.google.android.libraries.play.hpe.** { *; }
-keep class com.google.android.libraries.play.games.inputmapping.** { *; }
Passaggio successivo
Dopo aver integrato l'SDK di Input nel tuo gioco, puoi continuare con gli eventuali requisiti rimanenti di Google Play Giochi su PC. Per ulteriori informazioni, consulta la guida introduttiva all'utilizzo di Google Play Giochi su PC.