このドキュメントでは、Input SDK を PC 版 Google Play Games に対応しているゲーム。タスクには、SDK の追加、 入力マップを生成します。入力マップには、 ゲーム アクションとユーザー入力の割り当てを
開始する前に
Input SDK をゲームに追加する前に、お使いのゲームエンジンの入力システムを使用して、キーボードとマウスの入力をサポートする必要があります。
Input SDK からは PC 版 Google Play Games に対して、ゲームでどのコントロールが使用されるかに関する情報が提供されるため、それらのコントロールをユーザーに表示できます。また、ユーザーによるキーボードの割り当て変更も許可できます。
各コントールが InputAction
(「Jump」の「J」など)となり、InputActions
をまとめたものが InputGroups
となります。InputGroup
はゲーム内のモード(「運転中」「歩行中」「メインメニュー」など)に相当します。また、InputContexts
を使用して、ゲーム内のポイントごとにアクティブなグループを示すこともできます。
キーボードの割り当て変更が自動で処理されるようにすることも可能ですが、コントロールの割り当て変更のための独自のインターフェースを提供する場合は、Input SDK の割り当て変更を無効にできます。
次のシーケンス図は、Input SDK の API の動作を説明するものです。
Input SDK をゲームに実装すると、PC 版 Google Play Games のオーバーレイにコントロールが表示されます。
PC 版 Google Play Games のオーバーレイ
PC 版 Google Play Games のオーバーレイ(以降、「オーバーレイ」)には、ゲームで定義したコントロールが表示されます。このオーバーレイは、Shift+Tab キーを押すことにより、いつでも表示できます。
キー バインディングを設計するにあたってのベスト プラクティス
キー バインディングを設計するにあたっては、次のベスト プラクティスを考慮してください。
- ゲームプレイ中のナビゲーションを向上させ、コントロールを見つけやすくするために、
InputActions
を論理的に関連するInputGroups
にまとめます。 - 各
InputGroup
を少なくとも 1 つのInputContext
に割り当てます。InputMap
を細かくすることで、オーバーレイのコントロールのナビゲーションが使いやすくなります。 - ゲームのシーンの種類ごとに
InputContext
を作成します。通常は、すべての「メニュー的な」シーンに 1 つのInputContext
を使用できます。各ミニゲームや、1 つのシーンの代替コントロールには、別々のInputContexts
を使用します。 - 2 つのアクションに同じ
InputContext
で同じキーを使用する場合は「操作 / 発射」などのラベル文字列を利用します。 - 2 つのキーを同じ
InputAction
にバインドする場合、ゲーム内で同じアクションを実行する 2 つの異なるInputActions
を使用します。両方のInputActions
に同じラベル文字列を利用しても構いませんが、ID は違っている必要があります。 - キーのセットに修飾キーを適用する場合、修飾キー付きの複数の
InputActions
ではなく、修飾キー付きの単独のInputAction
とすることを検討してください(例: Shift+W、Shift+A、Shift+S、Shift+D ではなく、Shift+W、A、S、D)。 - テキスト フィールドに入力する際は、入力の割り当て変更が自動的に無効になります。Android のテキスト フィールドの実装に関するベスト プラクティスのとおり、ゲームのテキスト フィールドが検出されるようにし、割り当て変更されるキーによる干渉が発生しないようにしてください。標準的でないテキスト フィールドを使用する必要がある場合は、
InputGroups
の空のリストを収めたInputContext
を指定してsetInputContext()
を使用すると、割り当て変更を手動で無効にできます。 - 割り当て変更をサポートしているゲームの場合、キー バインディングの変更は、ユーザーが保存したバージョンと競合する可能性がある繊細な操作だと考えてください。可能であれば、既存のコントロールの ID は変更しないでください。
割り当て変更機能
PC 版 Google Play Games では、Input SDK の使用によりゲームから提供されるキー バインディングに基づいて、キーボード コントロールの割り当てを変更できます。この機能はオプションであり、完全に無効にできます。たとえば、独自のキーボード割り当て変更インターフェースを提供できます。InputMap
の割り当て変更オプションを無効に指定するだけで、割り当て変更を無効にできます(詳しくは、InputMap の作成をご覧ください)。
ユーザーがこの機能を利用するには、オーバーレイを開いてから、割り当て変更したいアクションをクリックする必要があります。割り当て変更イベントが発生するごとに、PC 版 Google Play Games が、ユーザーが割り当て変更を行ったコントロールのそれぞれを、ゲームが受け取るデフォルトのコントロールに割り当てるため、プレーヤーによる割り当て変更をゲーム側で認識する必要はありません。また、割り当て変更イベントに対応するコールバックを追加することにより、キーボード コントロールの表示に使用されるアセットを更新できます。
PC 版 Google Play Games では、割り当てを変更されたコントロールをユーザーごとにローカルで保存し、ゲーム セッション間でコントロールが変わらないようにしています。この情報は、PC プラットフォームの場合にのみディスクに保存され、モバイルでの使用には影響ありません。PC 版 Google Play Games のアンインストールまたは再インストールを行うと、コントロール データが削除されます。このデータが複数の PC デバイスにまたがって存続することはありません。
ゲームの割り当て変更機能をサポートする場合、次の制限事項を避ける必要があります。
割り当て変更の制限事項
キー バインディングに以下のケースが含まれている場合、ゲームでは割り当て変更機能が無効になる可能性があります。
- 修飾キーと非修飾キーの組み合わせではない、複数キーの
InputActions
である場合。たとえば、Shift+A は有効ですが、A+B、Ctrl+Alt、Shift+A+Tab は無効です。 InputMap
に、一意の ID を重複使用しているInputActions
、InputGroups
、またはInputContexts
が含まれている場合。
割り当て変更の限界
割り当て変更のキー バインディングを設計するにあたっては、次のような限界があることを考慮してください。
- 割り当ての変更先をキーの組み合わせにすることはできません。たとえば、Shift+A から Ctrl+B や、A から Shift+A への割り当て変更はできません。
- マウスボタンを伴う
InputActions
の割り当て変更はサポートされていません。たとえば、Shift+右クリックの割り当ては変更できません。
PC 版 Google Play Games エミュレータでキーの割り当て変更をテストする
PC 版 Google Play Games エミュレータでは、次の adb コマンドを発行することで、いつでも割り当て変更機能を有効にできます。
adb shell dumpsys input_mapping_service --set RemappingFlagValue true
オーバーレイは、次の画像のように変わります。
SDK を追加する
開発プラットフォームに応じて Input SDK をインストールします。
Java と Kotlin
Java または Kotlin 用の Input SDK を取得するには、依存関係をモジュール レベルの build.gradle
ファイルに追加します。
dependencies {
implementation 'com.google.android.libraries.play.games:inputmapping:1.1.0-beta'
...
}
Unity
Input SDK は、標準の Unity パッケージであり、いくつかの依存関係があります。
パッケージと必要な依存関係をインストールします。インストール方法は、いくつかあります。
.unitypackage
をインストールする
すべての依存関係と Input SDK の .unitypackage ファイルをダウンロードします。[Assets] > [Import package] > [Custom Package] を選択し、ダウンロードしたファイルを見つけて、.unitypackage
をインストールします。
UPM を使用してインストールする
別の方法として、.tgz
をダウンロードし、以下の依存関係をインストールすることで、Unity のパッケージ管理システムを使用してパッケージをインストールすることもできます。
- 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
OpenUPM を使用してインストールする
OpenUPM を使用してパッケージをインストールすることもできます。
$ openupm add com.google.android.libraries.play.games.inputmapping
サンプルゲーム
Input SDK と統合する方法の例については、Kotlin または Java ゲーム向けの AGDK Tunnel と Unity ゲーム向けの Trivial Kart をご覧ください。
キー バインディングを生成する
InputMap
を作成し、それを InputMappingProvider
で返すことにより、キー バインディングを登録します。以下は、InputMappingProvider
を説明するサンプルです。
Kotlin
class InputSDKProvider : InputMappingProvider { override fun onProvideInputMap(): InputMap { TODO("Not yet implemented") } }
Java
public class InputSDKProvider implements InputMappingProvider { private static final String INPUTMAP_VERSION = "1.0.0"; @Override @NonNull public InputMap onProvideInputMap() { // TODO: return an InputMap } }
C#
#if PLAY_GAMES_PC using Java.Lang; using Java.Util; using Google.Android.Libraries.Play.Games.Inputmapping; using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel; public class InputSDKProvider : InputMappingProviderCallbackHelper { public static readonly string INPUT_MAP_VERSION = "1.0.0"; public override InputMap OnProvideInputMap() { // TODO: return an InputMap } } #endif
入力アクションを定義する
InputAction
クラスは、キーまたはキーの組み合わせをゲーム アクションに割り当てるために使用します。InputActions
の ID は、すべての InputActions
で一意である必要があります。
割り当て変更をサポートしている場合、どの InputActions
の割り当てを変更できるかを定義できます。割り当て変更をサポートしないゲームでは、すべての InputActions
で割り当て変更オプションを無効に設定する必要がありますが、InputMap
でサポートしていない場合には Input SDK が自動的に無効にします。
この例では、
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 );
アクションはマウス入力で表すこともできます。この例では、左クリックを Move アクションに設定しています。
Kotlin
companion object { private val mouseInputAction = InputAction.create( "Move", InputActionsIds.MOUSE_MOVEMENT.ordinal.toLong(), InputControls.create(emptyList(), listOf(InputControls.MOUSE_LEFT_CLICK)), InputEnums.REMAP_OPTION_DISABLED) }
Java
private static final InputAction mouseInputAction = InputAction.create( "Move", InputActionsIds.MOUSE_MOVEMENT.ordinal(), InputControls.create( Collections.emptyList(), Collections.singletonList(InputControls.MOUSE_LEFT_CLICK) ), InputEnums.REMAP_OPTION_DISABLED );
C#
private static readonly InputAction mouseInputAction = InputAction.Create( "Move", (long)InputEventIds.MOUSE_MOVEMENT, InputControls.Create( new ArrayList<Integer>(), new[] { new Integer((int)PlayMouseAction.MouseLeftClick) }.ToJavaList() ), InputEnums.REMAP_OPTION_DISABLED );
キーの組み合わせは、複数のキーコードを InputAction
に渡すと指定できます。この例では、
Kotlin
companion object { private val turboInputAction = InputAction.create( "Turbo", InputActionsIds.TURBO.ordinal.toLong(), InputControls.create( listOf(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SPACE), emptyList()), InputEnums.REMAP_OPTION_ENABLED) }
Java
private static final InputAction turboInputAction = InputAction.create( "Turbo", InputActionsIds.TURBO.ordinal(), InputControls.create( Arrays.asList(KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SPACE), Collections.emptyList() ), InputEnums.REMAP_OPTION_ENABLED );
C#
private static readonly InputAction turboInputAction = InputAction.Create( "Turbo", (long)InputEventIds.TURBO, InputControls.Create( new[] { new Integer(AndroidKeyCode.KEYCODE_SHIFT_LEFT), new Integer(AndroidKeyCode.KEYCODE_SPACE) }.ToJavaList(), new ArrayList<Integer>()), InputEnums.REMAP_OPTION_ENABLED );
Input SDK では、1 つのアクションを実行するためにマウスボタンとキーボタンを混在させることができます。この例は、
Kotlin
companion object { private val addWaypointInputAction = InputAction.create( "Add waypoint", InputActionsIds.ADD_WAYPOINT.ordinal.toLong(), InputControls.create( listOf(KeyEvent.KeyEvent.KEYCODE_TAB), listOf(InputControls.MOUSE_RIGHT_CLICK)), InputEnums.REMAP_OPTION_DISABLED) }
Java
private static final InputAction addWaypointInputAction = InputAction.create( "Add waypoint", InputActionsIds.ADD_WAYPOINT.ordinal(), InputControls.create( Collections.singletonList(KeyEvent.KEYCODE_TAB), Collections.singletonList(InputControls.MOUSE_RIGHT_CLICK) ), InputEnums.REMAP_OPTION_DISABLED );
C#
private static readonly InputAction addWaypointInputAction = InputAction.Create( "Add waypoint", (long)InputEventIds.ADD_WAYPOINT, InputControls.Create( new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE) }.ToJavaList(), new[] { new Integer((int)PlayMouseAction.MouseRightClick) }.ToJavaList() ), InputEnums.REMAP_OPTION_DISABLED );
InputAction には以下のフィールドがあります。
ActionLabel
: このアクションを表し、UI に表示される文字列です。自動ではローカライズされないため、前もってローカライズしてください。InputControls
: このアクションで使用される入力コントロールを定義します。オーバーレイでは、これらのコントロールが一貫したグリフに割り当てられます。InputActionId
:InputAction
の数値 ID とバージョンを保存するInputIdentifier
オブジェクトです(詳しくは、キー ID のトラッキングをご覧ください)。InputRemappingOption
:InputEnums.REMAP_OPTION_ENABLED
かInputEnums.REMAP_OPTION_DISABLED
のいずれかです。アクションの割り当て変更が有効になっているかどうかを定義します。割り当て変更をサポートしない場合は、このフィールドをスキップするか、無効に設定します。RemappedInputControls
: 割り当て変更イベントでユーザーが設定した割り当て変更対象のキーの読み取りに使用される、読み取り専用のInputControls
オブジェクトです(割り当て変更イベントの通知を受け取るために使用されます)。
InputControls
は、アクションに関連付けられた入力を表します。以下のフィールドを含みます。
AndroidKeycodes
: アクションに関連付けられたキーボード入力を表す整数のリストです。これらの整数は、KeyEvent クラス、または Unity の AndroidKeycode クラスで定義されています。MouseActions
: このアクションに関連付けられたマウス入力を表すMouseAction
値のリストです。
入力グループを定義する
InputActions
は、オーバーレイでのナビゲーションを向上させ、コントロールを発見しやすくするために、InputGroups
を使用して、論理的に関連するアクション別にグループ化されます。各 InputGroup
ID は、ゲーム内のすべての InputGroups
で一意である必要があります。
入力アクションをグループにまとめることで、現在の状況に適したキー バインディングをプレーヤーが容易に見つけられるようになります。
割り当て変更をサポートしている場合、どの InputGroups
の割り当てを変更できるかを定義できます。割り当て変更をサポートしないゲームでは、すべての InputGroups
で割り当て変更オプションを無効に設定する必要がありますが、InputMap
でサポートしていない場合には Input SDK が自動的に無効にします。
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 );
以下の例では、オーバーレイに Road controls 入力グループと Menu controls 入力グループが表示されています。
InputGroup
には次のフィールドがあります。
GroupLabel
: オーバーレイに表示される文字列で、一連のアクションを論理的にグループ化するために使用できます。この文字列は自動ではローカライズされません。InputActions
: 前のステップで定義したInputAction
オブジェクトのリストです。これらのアクションは、すべてグループ見出しの下に表示されます。InputGroupId
:InputGroup
の数値 ID とバージョンを保存するInputIdentifier
オブジェクトです。詳しくは、キー ID をトラッキングするをご覧ください。InputRemappingOption
:InputEnums.REMAP_OPTION_ENABLED
かInputEnums.REMAP_OPTION_DISABLED
のいずれかです。無効にすると、割り当て変更オプションを有効に指定していても、このグループに属するすべてのInputAction
オブジェクトの割り当て変更は無効になります。有効にすると、このグループに属するアクションは、個々のアクションで無効と指定しない限り、割り当て変更が可能になります。
入力コンテキストを定義する
InputContexts
により、ゲームのシーンごとに異なるキーボード コントロールのセットを使用することが可能になります。次に例を示します。
- メニューの操作とゲーム内の移動に対して、それぞれ異なる入力セットを指定できます。
- 運転や歩行など、ゲーム内の移動方法に応じて異なる入力セットを指定できます。
- ゲーム全体での移動か、個々のレベルでのプレイかなど、ゲームの状況に応じて異なる入力セットを指定できます。
InputContexts
を使用している場合、オーバーレイには、まず使用中のコンテキストのグループが表示されます。この動作を有効にするには、ゲームのシーンが変わるときに setInputContext()
を呼び出してコンテキストを設定します。次の画像は、この動作(「driving」シーンで、オーバーレイの上部に Road controls のアクションが表示される)を示しています。「store」メニューを開くと、オーバーレイの上部に「Menu controls」のアクションが表示されます。
ゲームのポイントごとに異なる InputContext
を設定することで、このようなオーバーレイの更新を実現できます。そのためには、次のような処理を行います。
InputGroups
を使用して、InputActions
を論理的に関連するアクション別にグループ化します。- これらの
InputGroups
をゲームの異なる部分ごとのInputContext
に割り当てます。
同じ InputContext
に属する InputGroups
で、同じキーが使用され、競合している InputActions
を持つことはできません。各 InputGroup
を 1 つの InputContext
に割り当てることをおすすめします。
以下は、InputContext
のロジックを示すサンプルコードです。
Kotlin
companion object { val menuSceneInputContext = InputContext.create( "Menu", InputIdentifier.create( INPUTMAP_VERSION, InputContextIds.MENU_SCENE.ordinal.toLong()), listOf(basicMenuNavigationInputGroup, menuActionsInputGroup)) val gameSceneInputContext = InputContext.create( "Game", InputIdentifier.create( INPUTMAP_VERSION, InputContextIds.GAME_SCENE.ordinal.toLong()), listOf( movementInputGroup, mouseActionsInputGroup, emojisInputGroup, gameActionsInputGroup)) }
Java
public static final InputContext menuSceneInputContext = InputContext.create( "Menu", InputIdentifier.create( INPUTMAP_VERSION, InputContextIds.MENU_SCENE.ordinal()), Arrays.asList( basicMenuNavigationInputGroup, menuActionsInputGroup ) ); public static final InputContext gameSceneInputContext = InputContext.create( "Game", InputIdentifier.create( INPUTMAP_VERSION, InputContextIds.GAME_SCENE.ordinal()), Arrays.asList( movementInputGroup, mouseActionsInputGroup, emojisInputGroup, gameActionsInputGroup ) );
C#
public static readonly InputContext menuSceneInputContext = InputContext.Create( "Menu", InputIdentifier.Create( INPUT_MAP_VERSION, (long)InputContextsIds.MENU_SCENE), new[] { basicMenuNavigationInputGroup, menuActionsInputGroup }.ToJavaList() ); public static readonly InputContext gameSceneInputContext = InputContext.Create( "Game", InputIdentifier.Create( INPUT_MAP_VERSION, (long)InputContextsIds.GAME_SCENE), new[] { movementInputGroup, mouseActionsInputGroup, emojisInputGroup, gameActionsInputGroup }.ToJavaList() );
InputContext
には次のフィールドがあります。
LocalizedContextLabel
: コンテキストに属するグループを説明する文字列です。InputContextId
:InputContext
の数値 ID とバージョンを保存するInputIdentifier
オブジェクトです(詳しくは、キー ID のトラッキングをご覧ください)。ActiveGroups
: このコンテキストがアクティブになったときに使用され、オーバーレイの上部に表示されるInputGroups
のリストです。
入力マップを作成する
InputMap
は、ゲームで利用できるすべての InputGroup
オブジェクト(つまり、実行されることをユーザーが期待しているすべての InputAction
オブジェクト)のコレクションです。
キー バインディングをレポートする場合には、ゲーム内で使用されるすべての InputGroups
が入った InputMap
を作成します。
割り当て変更をサポートしていない場合、割り当て変更オプションを無効に設定し、予約されているキーを空に設定します。
次の例では、InputGroups
のコレクションをレポートするために使用される InputMap
を作成しています。
Kotlin
companion object { val gameInputMap = InputMap.create( listOf( basicMenuNavigationInputGroup, menuActionKeysInputGroup, movementInputGroup, mouseMovementInputGroup, pauseMenuInputGroup), MouseSettings.create(true, false), InputIdentifier.create(INPUTMAP_VERSION, INPUT_MAP_ID.toLong()), InputEnums.REMAP_OPTION_ENABLED, // Use ESCAPE as reserved remapping key listof(InputControls.create(listOf(KeyEvent.KEYCODE_ESCAPE), emptyList())) ) }
Java
public static final InputMap gameInputMap = InputMap.create( Arrays.asList( basicMenuNavigationInputGroup, menuActionKeysInputGroup, movementInputGroup, mouseMovementInputGroup, pauseMenuInputGroup), MouseSettings.create(true, false), InputIdentifier.create(INPUTMAP_VERSION, INPUT_MAP_ID), REMAP_OPTION_ENABLED, // Use ESCAPE as reserved remapping key Arrays.asList( InputControls.create( Collections.singletonList(KeyEvent.KEYCODE_ESCAPE), Collections.emptyList() ) ) );
C#
public static readonly InputMap gameInputMap = InputMap.Create( new[] { basicMenuNavigationInputGroup, menuActionKeysInputGroup, movementInputGroup, mouseMovementInputGroup, pauseMenuInputGroup, }.ToJavaList(), MouseSettings.Create(true, false), InputIdentifier.Create(INPUT_MAP_VERSION, INPUT_MAP_ID), InputEnums.REMAP_OPTION_ENABLED, // Use ESCAPE as reserved remapping key new[] { InputControls.Create( New[] { new Integer(AndroidKeyCode.KEYCODE_ESCAPE) }.ToJavaList(), new ArrayList<Integer>()) }.ToJavaList() );
InputMap
には次のフィールドがあります。
InputGroups
: ゲームからレポートされる InputGroups です。グループは、setInputContext()
を呼び出して現在のグループを指定しない限り、オーバーレイに順番に表示されます。MouseSettings
:MouseSettings
オブジェクトは、マウスの感度を調整できることと、マウスを Y 軸上で反転することを示しています。InputMapId
:InputMap
の数値 ID とバージョンを保存するInputIdentifier
オブジェクトです(詳しくは、キー ID のトラッキングをご覧ください)。InputRemappingOption
:InputEnums.REMAP_OPTION_ENABLED
かInputEnums.REMAP_OPTION_DISABLED
のいずれかです。割り当て変更機能が有効かどうかを定義します。ReservedControls
: ユーザーが割り当て変更先にすることを禁止するInputControls
のリストです。
キー ID をトラッキングする
InputAction
、InputGroup
、InputContext
、InputMap
の各オブジェクトには、一意の数値 ID と文字列バージョン ID を収めた InputIdentifier
オブジェクトが含まれます。オブジェクトの文字列バージョンをトラッキングすることは任意ですが、InputMap
のバージョンのトラッキングを行う場合はおすすめします。文字列バージョンが指定されていない場合、この文字列は空になります。InputMap
オブジェクトには文字列バージョンが必要です。
次の例では、InputActions
または InputGroups
に文字列バージョンを割り当てています。
Kotlin
class InputSDKProviderKotlin : InputMappingProvider { companion object { const val INPUTMAP_VERSION = "1.0.0" private val enterMenuInputAction = InputAction.create( "Enter menu", InputControls.create(listOf(KeyEvent.KEYCODE_ENTER), emptyList()), InputIdentifier.create( INPUTMAP_VERSION, InputActionsIds.ENTER_MENU.ordinal.toLong()), InputEnums.REMAP_OPTION_ENABLED ) private val movementInputGroup = InputGroup.create( "Basic movement", listOf( moveUpInputAction, moveLeftInputAction, moveDownInputAction, mouseGameInputAction), InputIdentifier.create( INPUTMAP_VERSION, InputGroupsIds.BASIC_MOVEMENT.ordinal.toLong()), InputEnums.REMAP_OPTION_ENABLED) } }
Java
public class InputSDKProvider implements InputMappingProvider { public static final String INPUTMAP_VERSION = "1.0.0"; private static final InputAction enterMenuInputAction = InputAction.create( "Enter menu", InputControls.create( Collections.singletonList(KeyEvent.KEYCODE_ENTER), Collections.emptyList()), InputIdentifier.create( INPUTMAP_VERSION, InputActionsIds.ENTER_MENU.ordinal()), InputEnums.REMAP_OPTION_ENABLED ); private static final InputGroup movementInputGroup = InputGroup.create( "Basic movement", Arrays.asList( moveUpInputAction, moveLeftInputAction, moveDownInputAction, moveRightInputAction, mouseGameInputAction ), InputIdentifier.create( INPUTMAP_VERSION, InputGroupsIds.BASIC_MOVEMENT.ordinal()), InputEnums.REMAP_OPTION_ENABLED ); }
C#
#if PLAY_GAMES_PC using Java.Lang; using Java.Util; using Google.Android.Libraries.Play.Games.Inputmapping; using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel; public class InputSDKMappingProvider : InputMappingProviderCallbackHelper { public static readonly string INPUT_MAP_VERSION = "1.0.0"; private static readonly InputAction enterMenuInputAction = InputAction.Create( "Enter menu", InputControls.Create( new[] { new Integer(AndroidKeyCode.KEYCODE_SPACE)}.ToJavaList(), new ArrayList<Integer>()), InputIdentifier.Create( INPUT_MAP_VERSION, (long)InputEventIds.ENTER_MENU), InputEnums.REMAP_OPTION_ENABLED ); private static readonly InputGroup movementInputGroup = InputGroup.Create( "Basic movement", new[] { moveUpInputAction, moveLeftInputAction, moveDownInputAction, moveRightInputAction, mouseGameInputAction }.ToJavaList(), InputIdentifier.Create( INPUT_MAP_VERSION, (long)InputGroupsIds.BASIC_MOVEMENT), InputEnums.REMAP_OPTION_ENABLED ); } #endif
InputAction
オブジェクトの数値 ID は、InputMap
内のすべての InputActions
で一意である必要があります。同様に、InputGroup
オブジェクトの ID は、InputMap
内のすべての InputGroups
で一意である必要があります。次のサンプルでは、enum
を使用してオブジェクトの一意の ID のトラッキングを行う方法を示しています。
Kotlin
enum class InputActionsIds { NAVIGATE_UP, NAVIGATE_DOWN, ENTER_MENU, EXIT_MENU, // ... JUMP, RUN, EMOJI_1, EMOJI_2, // ... } enum class InputGroupsIds { // Main menu scene BASIC_NAVIGATION, // WASD, Enter, Backspace MENU_ACTIONS, // C: chat, Space: quick game, S: store // Gameplay scene BASIC_MOVEMENT, // WASD, space: jump, Shift: run MOUSE_ACTIONS, // Left click: shoot, Right click: aim EMOJIS, // Emojis with keys 1,2,3,4 and 5 GAME_ACTIONS, // M: map, P: pause, R: reload } enum class InputContextIds { MENU_SCENE, // Basic menu navigation, menu actions GAME_SCENE, // Basic movement, mouse actions, emojis, game actions } const val INPUT_MAP_ID = 0
Java
public enum InputActionsIds { NAVIGATE_UP, NAVIGATE_DOWN, ENTER_MENU, EXIT_MENU, // ... JUMP, RUN, EMOJI_1, EMOJI_2, // ... } public enum InputGroupsIds { // Main menu scene BASIC_NAVIGATION, // WASD, Enter, Backspace MENU_ACTIONS, // C: chat, Space: quick game, S: store // Gameplay scene BASIC_MOVEMENT, // WASD, space: jump, Shift: run MOUSE_ACTIONS, // Left click: shoot, Right click: aim EMOJIS, // Emojis with keys 1,2,3,4 and 5 GAME_ACTIONS, // M: map, P: pause, R: reload } public enum InputContextIds { MENU_SCENE, // Basic navigation, menu actions GAME_SCENE, // Basic movement, mouse actions, emojis, game actions } public static final long INPUT_MAP_ID = 0;
C#
public enum InputActionsIds { NAVIGATE_UP, NAVIGATE_DOWN, ENTER_MENU, EXIT_MENU, // ... JUMP, RUN, EMOJI_1, EMOJI_2, // ... } public enum InputGroupsIds { // Main menu scene BASIC_NAVIGATION, // WASD, Enter, Backspace MENU_ACTIONS, // C: chat, Space: quick game, S: store // Gameplay scene BASIC_MOVEMENT, // WASD, space: jump, Shift: run MOUSE_ACTIONS, // Left click: shoot, Right click: aim EMOJIS, // Emojis with keys 1,2,3,4 and 5 GAME_ACTIONS, // M: map, P: pause, R: reload } public enum InputContextIds { MENU_SCENE, // Basic navigation, menu actions GAME_SCENE, // Basic movement, mouse actions, emojis, game actions } public static readonly long INPUT_MAP_ID = 0;
InputIdentifier
には次のフィールドがあります。
UniqueId
: 入力データの特定のセットを一意かつ明確に識別するために設定された一意の数値 ID です。VersionString
: 2 つのバージョンの入力データの変化の間で入力データのバージョンを識別するために設定された、人間が読める形のバージョン文字列です。
割り当て変更イベントの通知を受ける(任意)
ゲーム内でキーが使用されていることを知るための割り当て変更イベントの通知を受け取ります。これにより、アクション コントロールの表示に使用され、ゲーム画面に表示されるアセットを更新することが可能になります。
次の画像は、この動作の例です。
この機能は、InputRemappingListener
コールバックを登録することで実現します。この機能を実装するために、まず InputRemappingListener
インスタンスを登録します。
Kotlin
class InputSDKRemappingListener : InputRemappingListener { override fun onInputMapChanged(inputMap: InputMap) { Log.i(TAG, "Received update on input map changed.") if (inputMap.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) { return } for (inputGroup in inputMap.inputGroups()) { if (inputGroup.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) { continue } for (inputAction in inputGroup.inputActions()) { if (inputAction.inputRemappingOption() != InputEnums.REMAP_OPTION_DISABLED) { // Found InputAction remapped by user processRemappedAction(inputAction) } } } } private fun processRemappedAction(remappedInputAction: InputAction) { // Get remapped action info val remappedControls = remappedInputAction.remappedInputControls() val remappedKeyCodes = remappedControls.keycodes() val mouseActions = remappedControls.mouseActions() val version = remappedInputAction.inputActionId().versionString() val remappedActionId = remappedInputAction.inputActionId().uniqueId() val currentInputAction: Optional<InputAction> currentInputAction = if (version == null || version.isEmpty() || version == InputSDKProvider.INPUTMAP_VERSION ) { getCurrentVersionInputAction(remappedActionId) } else { Log.i(TAG, "Detected version of user-saved input action defers from current version") getCurrentVersionInputActionFromPreviousVersion( remappedActionId, version) } if (!currentInputAction.isPresent) { Log.e(TAG, String.format( "can't find remapped input action with id %d and version %s", remappedActionId, if (version == null || version.isEmpty()) "UNKNOWN" else version)) return } val originalControls = currentInputAction.get().inputControls() val originalKeyCodes = originalControls.keycodes() Log.i(TAG, String.format( "Found input action with id %d remapped from key %s to key %s", remappedActionId, keyCodesToString(originalKeyCodes), keyCodesToString(remappedKeyCodes))) // TODO: make display changes to match controls used by the user } private fun getCurrentVersionInputAction(inputActionId: Long): Optional<InputAction> { for (inputGroup in InputSDKProvider.gameInputMap.inputGroups()) { for (inputAction in inputGroup.inputActions()) { if (inputAction.inputActionId().uniqueId() == inputActionId) { return Optional.of(inputAction) } } } return Optional.empty() } private fun getCurrentVersionInputActionFromPreviousVersion( inputActionId: Long, previousVersion: String ): Optional<InputAction7gt; { // TODO: add logic to this method considering the diff between the current and previous // InputMap. return Optional.empty() } private fun keyCodesToString(keyCodes: List<Int>): String { val builder = StringBuilder() for (keyCode in keyCodes) { if (!builder.toString().isEmpty()) { builder.append(" + ") } builder.append(keyCode) } return String.format("(%s)", builder) } companion object { private const val TAG = "InputSDKRemappingListener" } }
Java
public class InputSDKRemappingListener implements InputRemappingListener { private static final String TAG = "InputSDKRemappingListener"; @Override public void onInputMapChanged(InputMap inputMap) { Log.i(TAG, "Received update on input map changed."); if (inputMap.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) { return; } for (InputGroup inputGroup : inputMap.inputGroups()) { if (inputGroup.inputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) { continue; } for (InputAction inputAction : inputGroup.inputActions()) { if (inputAction.inputRemappingOption() != InputEnums.REMAP_OPTION_DISABLED) { // Found InputAction remapped by user processRemappedAction(inputAction); } } } } private void processRemappedAction(InputAction remappedInputAction) { // Get remapped action info InputControls remappedControls = remappedInputAction.remappedInputControls(); List<Integer> remappedKeyCodes = remappedControls.keycodes(); List<Integer> mouseActions = remappedControls.mouseActions(); String version = remappedInputAction.inputActionId().versionString(); long remappedActionId = remappedInputAction.inputActionId().uniqueId(); Optional<InputAction> currentInputAction; if (version == null || version.isEmpty() || version.equals(InputSDKProvider.INPUTMAP_VERSION)) { currentInputAction = getCurrentVersionInputAction(remappedActionId); } else { Log.i(TAG, "Detected version of user-saved input action defers " + "from current version"); currentInputAction = getCurrentVersionInputActionFromPreviousVersion( remappedActionId, version); } if (!currentInputAction.isPresent()) { Log.e(TAG, String.format( "input action with id %d and version %s not found", remappedActionId, version == null || version.isEmpty() ? "UNKNOWN" : version)); return; } InputControls originalControls = currentInputAction.get().inputControls(); List<Integer> originalKeyCodes = originalControls.keycodes(); Log.i(TAG, String.format( "Found input action with id %d remapped from key %s to key %s", remappedActionId, keyCodesToString(originalKeyCodes), keyCodesToString(remappedKeyCodes))); // TODO: make display changes to match controls used by the user } private Optional<InputAction> getCurrentVersionInputAction( long inputActionId) { for (InputGroup inputGroup : InputSDKProvider.gameInputMap.inputGroups()) { for (InputAction inputAction : inputGroup.inputActions()) { if (inputAction.inputActionId().uniqueId() == inputActionId) { return Optional.of(inputAction); } } } return Optional.empty(); } private Optional<InputAction> getCurrentVersionInputActionFromPreviousVersion( long inputActionId, String previousVersion) { // TODO: add logic to this method considering the diff between your // current and previous InputMap. return Optional.empty(); } private String keyCodesToString(List<Integer> keyCodes) { StringBuilder builder = new StringBuilder(); for (Integer keyCode : keyCodes) { if (!builder.toString().isEmpty()) { builder.append(" + "); } builder.append(keyCode); } return String.format("(%s)", builder); } }
C#
#if PLAY_GAMES_PC using System.Text; using Java.Lang; using Java.Util; using Google.Android.Libraries.Play.Games.Inputmapping; using Google.Android.Libraries.Play.Games.Inputmapping.Datamodel; using UnityEngine; public class InputSDKRemappingListener : InputRemappingListenerCallbackHelper { public override void OnInputMapChanged(InputMap inputMap) { Debug.Log("Received update on remapped controls."); if (inputMap.InputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) { return; } List<InputGroup> inputGroups = inputMap.InputGroups(); for (int i = 0; i < inputGroups.Size(); i ++) { InputGroup inputGroup = inputGroups.Get(i); if (inputGroup.InputRemappingOption() == InputEnums.REMAP_OPTION_DISABLED) { continue; } List<InputAction> inputActions = inputGroup.InputActions(); for (int j = 0; j < inputActions.Size(); j ++) { InputAction inputAction = inputActions.Get(j); if (inputAction.InputRemappingOption() != InputEnums.REMAP_OPTION_DISABLED) { // Found action remapped by user ProcessRemappedAction(inputAction); } } } } private void ProcessRemappedAction(InputAction remappedInputAction) { InputControls remappedInputControls = remappedInputAction.RemappedInputControls(); List<Integer> remappedKeycodes = remappedInputControls.Keycodes(); List<Integer> mouseActions = remappedInputControls.MouseActions(); string version = remappedInputAction.InputActionId().VersionString(); long remappedActionId = remappedInputAction.InputActionId().UniqueId(); InputAction currentInputAction; if (string.IsNullOrEmpty(version) || string.Equals( version, InputSDKMappingProvider.INPUT_MAP_VERSION)) { currentInputAction = GetCurrentVersionInputAction(remappedActionId); } else { Debug.Log("Detected version of used-saved input action defers" + " from current version"); currentInputAction = GetCurrentVersionInputActionFromPreviousVersion( remappedActionId, version); } if (currentInputAction == null) { Debug.LogError(string.Format( "Input Action with id {0} and version {1} not found", remappedActionId, string.IsNullOrEmpty(version) ? "UNKNOWN" : version)); return; } InputControls originalControls = currentInputAction.InputControls(); List<Integer> originalKeycodes = originalControls.Keycodes(); Debug.Log(string.Format( "Found Input Action with id {0} remapped from key {1} to key {2}", remappedActionId, KeyCodesToString(originalKeycodes), KeyCodesToString(remappedKeycodes))); // TODO: update HUD according to the controls of the user } private InputAction GetCurrentVersionInputAction( long inputActionId) { List<InputGroup> inputGroups = InputSDKMappingProvider.gameInputMap.InputGroups(); for (int i = 0; i < inputGroups.Size(); i++) { InputGroup inputGroup = inputGroups.Get(i); List<InputAction> inputActions = inputGroup.InputActions(); for (int j = 0; j < inputActions.Size(); j++) { InputAction inputAction = inputActions.Get(j); if (inputAction.InputActionId().UniqueId() == inputActionId) { return inputAction; } } } return null; } private InputAction GetCurrentVersionInputActionFromPreviousVersion( long inputActionId, string version) { // TODO: add logic to this method considering the diff between your // current and previous InputMap. return null; } private string KeyCodesToString(List<Integer> keycodes) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < keycodes.Size(); i ++) { Integer keycode = keycodes.Get(i); if (builder.Length > 0) { builder.Append(" + "); } builder.Append(keycode.IntValue()); } return string.Format("({0})", builder.ToString()); } } #endif
ユーザーが保存した割り当て変更済みのコントロールを読み込んだ後の起動時と、ユーザーがそれらのキーの割り当てを変更した後で、InputRemappingListener
に通知が届きます。
初期化
InputContexts
セットを使用している場合、初期シーンに使用される最初のコンテキストを含め、新しいシーンへの遷移のそれぞれにコンテキストを設定します。InputContext
は InputMap
を登録した後で設定する必要があります。
割り当て変更イベントの通知を受け取るために InputRemappingListeners
を使用している場合、InputMappingProvider
を登録する前に InputRemappingListener
を登録します。そうしなかった場合、起動時に重要なイベントを受け取り損なう可能性があります。
次のサンプルは、この API の初期化方法を示しています。
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (isGooglePlayGamesOnPC()) { val inputMappingClient = Input.getInputMappingClient(this) // Register listener before registering the provider inputMappingClient.registerRemappingListener(InputSDKRemappingListener()) inputMappingClient.setInputMappingProvider( InputSDKProvider()) // Set the context after you have registered the provider. inputMappingClient.setInputContext(InputSDKProvider.menuSceneInputContext) } }
Java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (isGooglePlayGamesOnPC()) { InputMappingClient inputMappingClient = Input.getInputMappingClient(this); // Register listener before registering the provider inputMappingClient.registerRemappingListener( new InputSDKRemappingListener()); inputMappingClient.setInputMappingProvider( new InputSDKProvider()); // Set the context after you have registered the provider inputMappingClient.setInputContext(InputSDKProvider.menuSceneInputContext); } }
C#
#if PLAY_GAMES_PC using Google.Android.Libraries.Play.Games.Inputmapping; using Google.Android.Libraries.Play.Games.InputMapping.ExternalType.Android.Content; using Google.LibraryWrapper.Java; #endif public class GameManager : MonoBehaviour { #if PLAY_GAMES_PC private InputSDKMappingProvider _inputMapProvider = new InputSDKMappingProvider(); private InputMappingClient _inputMappingClient; #endif public void Awake() { #if PLAY_GAMES_PC Context context = (Context)Utils.GetUnityActivity().GetRawObject(); _inputMappingClient = Google.Android.Libraries.Play.Games.Inputmapping .Input.GetInputMappingClient(context); // Register listener before registering the provider. _inputMappingClient.RegisterRemappingListener( new InputSDKRemappingListener()); _inputMappingClient.SetInputMappingProvider(_inputMapProvider); // Register context after you have registered the provider. _inputMappingClient.SetInputContext( InputSDKMappingProvider.menuSceneInputContext); #endif } }
クリーンアップ
ゲームを閉じる際には、InputMappingProvider
インスタンスとすべての InputRemappingListener
インスタンスの登録を解除しますが、それを実行しなくても Input SDK により自動的にリソースのリークが回避されます。
Kotlin
override fun onDestroy() { if (isGooglePlayGamesOnPC()) { val inputMappingClient = Input.getInputMappingClient(this) inputMappingClient.clearInputMappingProvider() inputMappingClient.clearRemappingListener() } super.onDestroy() }
Java
@Override protected void onDestroy() { if (isGooglePlayGamesOnPC()) { InputMappingClient inputMappingClient = Input.getInputMappingClient(this); inputMappingClient.clearInputMappingProvider(); inputMappingClient.clearRemappingListener(); } super.onDestroy(); }
C#
public class GameManager : MonoBehaviour { private void OnDestroy() { #if PLAY_GAMES_PC _inputMappingClient.ClearInputMappingProvider(); _inputMappingClient.ClearRemappingListener(); #endif } }
テスト
Input SDK の実装をテストするには、オーバーレイを手動で開いてプレーヤーから見た動作を確認するか、adb シェルから自動でテストと検証を行います。
PC 版 Google Play Games エミュレータでは、一般的な誤りに照らして入力マップの正しさを確認します。一意の ID の重複、異なる入力マップの使用、割り当て変更ルールの違反(割り当て変更が有効になっている場合)などの状況では、オーバーレイに次のようなエラー メッセージが表示されます。
コマンドラインで adb
を使用し、Input SDK の実装を検証します。現在の入力マップを取得するには、次の adb shell
コマンドを使用します(MY.PACKAGE.NAME
は実際のゲーム名に置き換えます)。
adb shell dumpsys input_mapping_service --get MY.PACKAGE.NAME
InputMap
が正常に登録されると、次のような出力が表示されます。
Getting input map for com.example.inputsample...
Successfully received the following inputmap:
# com.google.android.libraries.play.games.InputMap@d73526e1
input_groups {
group_label: "Basic Movement"
input_actions {
action_label: "Jump"
input_controls {
keycodes: 51
keycodes: 19
}
unique_id: 0
}
input_actions {
action_label: "Left"
input_controls {
keycodes: 29
keycodes: 21
}
unique_id: 1
}
input_actions {
action_label: "Right"
input_controls {
keycodes: 32
keycodes: 22
}
unique_id: 2
}
input_actions {
action_label: "Use"
input_controls {
keycodes: 33
keycodes: 66
mouse_actions: MOUSE_LEFT_CLICK
mouse_actions_value: 0
}
unique_id: 3
}
}
input_groups {
group_label: "Special Input"
input_actions {
action_label: "Jump"
input_controls {
keycodes: 51
keycodes: 19
keycodes: 62
mouse_actions: MOUSE_LEFT_CLICK
mouse_actions_value: 0
}
unique_id: 4
}
input_actions {
action_label: "Duck"
input_controls {
keycodes: 47
keycodes: 20
keycodes: 113
mouse_actions: MOUSE_RIGHT_CLICK
mouse_actions_value: 1
}
unique_id: 5
}
}
mouse_settings {
allow_mouse_sensitivity_adjustment: true
invert_mouse_movement: true
}
ローカライズ
Input SDK は、Android のローカライズ システムを使用しません。そのため InputMap
を送信するときは、ローカライズした文字列を指定する必要があります。また、ゲームエンジンのローカライズ システムを使用することもできます。
ProGuard
ProGuard を使用してゲームを軽量化する場合、ProGuard の設定ファイルに次のルールを追加して、この SDK が最終的なパッケージから取り除かれないようにします。
-keep class com.google.android.libraries.play.hpe.** { *; }
-keep class com.google.android.libraries.play.games.inputmapping.** { *; }
次のステップ
Input SDK をゲームに統合したら、PC 版 Google Play Games の残りの要件に引き続き取り組むことができます。詳細については、PC 版 Google Play Games のスタートガイドをご覧ください。