Device
Input Device (入力デバイス) とは、物理的には、コンピューターに接続され、ユーザーがアプリケーションを制御するために使用できる Device を意味します。論理的には、Input Device は Control の最上位のコンテナです。InputDevice
クラス自体は、InputControl
の特殊化です。Input System で現在サポートされている Device の種類については、サポートされている Device に関するドキュメントを参照してください。
現在、存在するすべての Device のセットを照会するには、InputSystem.devices
を使用できます。
Device の説明
InputDeviceDescription
は、Device の説明を表します。Input System は、主に Device の検出プロセスでこれを使用します。新しい Device が (ランタイムまたはユーザーによって) 報告されると、そのレポートには Device の説明が含まれています。この説明に基づいて、システムは、説明に一致する Device レイアウト を特定しようと試みます。このプロセスは、Device マッチャー に基づいて行われます。
Device を作成した後は、InputDevice.description
プロパティを介して、作成に使用した説明を取得できます。
それぞれの説明には、以下の一連の標準フィールドがあります。
フィールド | 説明 |
---|---|
interfaceName |
Device を使用可能にしているインターフェース/API の識別子。これは、多くの場合、プラットフォーム名と一致しますが、より特有のインターフェースとして一般的に使用されるのは、HID、RawInput、XInput です。 このフィールドは必須です。 |
deviceClass |
Device の大分類。例えば、"Gamepad" や "Keyboard" があります。 |
product |
Device/ドライバー自体から報告される製品の名前。 |
manufacturer |
Device/ドライバー自体から報告される製造元の名前。 |
version |
使用可能な場合は、Device のドライバー、またはハードウェアのバージョンを示します。 |
serial |
使用可能な場合は、Device のシリアル番号を示します。 |
capabilities |
Device/インターフェース固有の機能を記述する JSON 形式の文字列。機能に関するセクション を参照してください。 |
機能
Device の説明には、product
や manufacturer
などのいくつかの標準フィールドに加えて、JSON 形式の capabilities
(機能) 文字列を含めることができます。この文字列は、Input System が Device からのデータを解釈し、Control 表現にマップするときに役立つ特性を記述します。Device インターフェースの中には、Device 機能を報告しないものもあります。インターフェース固有の Device 機能の例は HID 記述子 です。WebGL、Android、Linux では、同様のメカニズムを使用して、接続されたゲームパッドで使用できる Control が報告されます。
照合
InputDeviceDescription
と登録済みのレイアウトとの照合は、InputDeviceMatcher
インスタンスによって処理されます。各マッチャーは、一種の正規表現として、大まかに機能します。説明の各フィールドは、プレーン文字列または正規表現に個別に一致させることができます。照合時に、大文字と小文字は区別されません。マッチャーが適用されるには、マッチャーの個々の式がすべて一致する必要があります。
レイアウトにマッチャーを登録するには、InputSystem.RegisterLayoutMatcher
を呼び出します。レイアウトの登録時に指定することもできます。
//新しいレイアウトを登録し、そのマッチャーを指定します。
InputSystem.RegisterLayoutMatcher<MyDevice>(
matches:new InputDeviceMatcher()
.WithInterface("HID")
.WithProduct("MyDevice.*")
.WithManufacturer("MyBrand");
//既に登録済みのレイアウトに別のマッチャーを登録します。
InputSystem.RegisterLayoutMatcher<MyDevice>(
new InputDeviceMatcher()
.WithInterface("HID")
複数のマッチャーが、同じ InputDeviceDescription
に一致する場合は、Input System により、一致するプロパティの数がより多いマッチャーが選択されます。
照合プロセスの置き換え
内部の照合プロセスを外部から無効にして、システムが通常選択するレイアウトとは異なるレイアウトを Device に選択することができます。これにより、新しいレイアウトをすばやく構築することも可能になります。これを行うには、InputSystem.onFindControlLayoutForDevice
イベントにカスタムハンドラーを追加します。null 以外のレイアウト文字列がハンドラーから返されると、そのレイアウトが Input System によって使用されます。
Device のライフサイクル
Device の作成
システムでデバイスの レイアウト が選択されると、InputDevice
がインスタンス化され、レイアウトに従って InputControl
が追加されます。このプロセスは内部で自動的に行われます。
ノート: 手動で
new
を使用してインスタンス化して、有効なInputDevice
とInputControl
を作成することはできません。作成プロセスを適切に進めるには、レイアウト を使用する必要があります。
Input System は、InputDevice
を構築した後、デバイス上の各コントロールとデバイス自体の FinishSetup
を呼び出します。これを使用して、Control の設定を完了します。
InputDevice
が完全に構築されると、Input System によってシステムに追加されます。このプロセスの一部として、Device の MakeCurrent
が呼び出され、InputSystem.onDeviceChange
で InputDeviceChange.Added
が通知されます。さらに、InputDevice.OnAdded
も呼び出されます。
追加された Device では、InputDevice.added
フラグが true に設定されます。
手動でデバイスを追加するには、InputSystem.AddDevice(layout)
など、InputSystem.AddDevice
メソッドのいずれかを呼び出します。
//ゲームパッドを追加します。照合プロセスをバイパスし、Gamepad レイアウトを使用して
//デバイスを直接作成します。
InputSystem.AddDevice<Gamepad>();
//照合プロセスが実行される方法でデバイスを追加します。
InputSystem.AddDevice(new InputDeviceDescription
{
interfaceName = "XInput",
product = "Xbox Controller",
});
デバイスが追加されると、Input System により自動的に、そのデバイスで 同期リクエスト が発行されます。このリクエストで、現在の状態を表すイベントを送信するようにデバイスが指示されます。リクエストが成功するかどうかは、そのデバイスが同期コマンドをサポートしているかどうかによって決まります。
Device の削除
Device の接続が切断されると、その Device はシステムから削除されます。InputDeviceChange.Removed
が通知され (InputSystem.onDeviceChange
経由で送信)、devices
リストから Device が削除されます。さらに、システムによって InputDevice.OnRemoved
も呼び出されます。
このプロセスで、InputDevice.added
フラグは false にリセットされます。
Device は、削除されたときに破棄されないことに注意してください。Device のインスタンスは有効のままであり、コードから引き続きアクセスできます。ただし、これらの Device のコントロールから値を読み取ろうとすると、例外が発生します。
Device のリセット
Device をリセットすると、その Control がデフォルトの状態にリセットされます。InputSystem.ResetDevice
を使用すると、これを手動で実行できます。
InputSystem.ResetDevice(Gamepad.current);
リセットには 2 つのタイプがあり、InputSystem.ResetDevice
の 2 番目のパラメーターによって決定されます。
タイプ | 説明 |
---|---|
"ソフト" リセット | これがデフォルトです。このタイプでは、dontReset とマーク されていない コントロールだけがデフォルト値にリセットされます。Pointer.position などのコントロールはリセットから除外されるため、マウスの位置は (0,0) にリセットされません。 |
"ハード" リセット | このタイプでは、dontReset が設定されているかどうかにかかわらず、すべて のコントロールがデフォルト値にリセットされます。 |
Action には、この方法で Control をリセットした結果が明らかに現れます。1 つ以上の Action を作動させている最中の Device をリセットした場合、それらの Action はキャンセルされます。このキャンセルは、デフォルトの状態のイベントを送信する操作とは異なります。後者では、Action が想定外に 実行 される (例えば、押されたボタンが解放されていなかったように扱われる) 場合があるのに対して、リセットでは、強制的に完全にキャンセルされます。
アプリケーションのフォーカス によっては、リセットが Input System によって自動的にトリガーされることがあります。
Device の同期
RequestSyncCommand
を介して、現在の状態を通知するイベントを送信することが Device にリクエストされることがあります。これがサポートされるかどうかは、プラットフォームと Device のタイプによって異なります。
InputSystem.TrySyncDevice
を使用すると、同期リクエストを明示的に送信できます。デバイスで同期リクエストがサポートされている場合、このメソッドは true を返し、次回の 更新 で処理されるように、InputEvent
がデバイスのキューに追加されます。
同期リクエストは、特定の状況で Input System から自動的に送信されることもあります。詳細については、バックグラウンドとフォーカス変更の動作 を参照してください。
Device の有効化と無効化
Device が追加されると、Input System により最初の QueryEnabledStateCommand
が送信されて、そのデバイスが現在有効になっているかどうかが特定されます。その結果は InputDevice.enabled
プロパティに反映されます。
無効になっている場合は、Device に対して処理されるイベントが、削除 (DeviceRemoveEvent
) と設定変更 (DeviceConfigurationEvent
) だけになり、それ以外のイベントは送信された場合も処理されません。
Device を手動で無効、および再び有効にするには、InputSystem.DisableDevice
および InputSystem.EnableDevice
をそれぞれ使用できます。
センサー はデフォルトで無効な状態で起動するため、イベントが生成されるようにするために有効にする必要があります。
特定の状況では、Input System によって自動的に Device の無効化と再有効化が行われることがあります。詳細は 次のセクション で説明します。
バックグラウンドとフォーカス変更の動作
一般に、入力は アプリケーションのフォーカス と結び付いています。つまり、アプリケーションがフォアグラウンドにないときは Device が入力を受け取らず、したがって、Action が入力を受け取ることもありません。アプリケーションにフォーカスが戻ると、すべての Device が 同期 リクエストを受け取り、現在の状態をアプリケーションに送信することを求められます (アプリケーションがバックグラウンドになっていた間に状態が変化した可能性があるためです)。同期リクエストをサポートしていない Device には、ソフトリセット が適用されます。ソフトリセットでは、dontReset
としてマークされていないすべての Control がデフォルトの状態にリセットされます。
Unity アプリケーションのバックグラウンド実行がサポートされない、iOS や Android などのプラットフォームでは、これは、唯一のサポートされる動作です。
バックグラウンド (つまり、フォーカスがない状態) で実行されるようにアプリケーションが設定されている場合は、入力の動作をいくつかのオプションから選択できます。以下の 2 つのシナリオがサポートされます。
- Unity の プレイヤー設定 では、この機能をサポートする特定のプレイヤー (Windows や Mac のスタンドアロンプレイヤーなど) に対して明示的に
Run In Background
を有効にすることができます。このようなプレイヤーでは、開発 プレイヤーの場合に、この設定が常に自動的に有効になります。 - エディターでは、アプリケーションのフォーカスがゲームビューのフォーカスに結び付いています。どのゲームビューにもフォーカスがない場合、アプリケーションはバックグラウンドで動作していると見なされます。再生モードにすると、ゲームビューウィンドウにフォーカスがあるかどうかにかかわらず、エディターは 常に Player Loop を実行し続けます。つまり、エディターでは、
Run In Background
が常に有効と見なされます。
アプリケーションがこのように設定され、バックグラウンドで実行され続ける場合は、Player Loop と Input System も同様に、アプリケーションにフォーカスがないときでも継続して実行されます。この場合の入力関連の動作は、以下の 2 つの要因によって決まります。
- アプリケーションがフォアグラウンドで実行されていないときに、個々のデバイスが入力を受け取ることができるかどうか。この機能は、少数のデバイスとプラットフォームのサブセットでのみサポートされます。一般に、HMD や VR コントローラーなどの VR デバイス (
TrackedDevice
) ではサポートされています。
特定のデバイスでサポートされているかどうかを特定するには、InputDevice.canRunInBackground
プロパティを照会します。このプロパティは、Device の レイアウト を使用して強制的に true または false に設定することもできます。 - プロジェクトレベルの 入力設定 にある 2 つの設定。具体的には、1 つが
InputSettings.backgroundBehavior
です。
もう 1 つがInputSettings.editorInputBehaviorInPlayMode
です。
以下の表は、これらの 2 つの設定と、Unity のプレイヤー設定のRun In Background
に関連して、入力動作の変化の詳細な内容を示しています。
ノート: 特定の状況では、
InputDevice.canRunInBackground
はエディターによってオーバーライドされます (以下の表を参照)。一般に、エディターとプレイヤーでプロパティの値が一致するとは限らず、特定のプラットフォームやデバイスによって異なります。
以下の表は、入力設定 と、ゲームがエディターとプレイヤーのどちらで実行されているかに従って、動作の違いを詳しく比較したものです。
エディターでのドメインの再ロード
エディターでスクリプトの再ロードと再コンパイルが行われるときや、エディターが再生モードに移行するときは、毎回、C# アプリケーションドメインが再ロードされます。各ドメインの再ロード後には、Input System の再初期化が必要になります。このプロセスの間に Input System では、ドメインの再ロード前にインスタンス化された Device の再作成が試みられます。ただし、各 Device の状態は引き継がれないため、ドメインの再ロード時には Device がデフォルトの状態にリセットされます。
ドメインの再ロード時には、前回のレイアウト登録は維持されません。Input System では、初期化プロセスの一環として (例えば、[InitializeOnLoad]
を使用して、エディターでのドメインの起動コードの一部として登録を実行して)、すべての登録が行われることが想定されています。これにより、スクリプト内で登録とレイアウトを変更でき、それが、ドメインの再ロード後にすぐに反映されます。
ネイティブ Device
ネイティブバックエンド から報告される Device は、(スクリプトコードから作成される Device とは異なり) ネイティブと見なされます。このような Device を特定するには、InputDevice.native
プロパティを確認します。
Input System では、ネイティブ Device が記憶されています。例えば、Device が最初に報告されたときに、一致するレイアウトがシステムに存在していない場合でも、デバイスに一致するレイアウトが後で登録されると、そのレイアウトを使用して Device が再作成されます。
接続が切断された Device
Input Device の接続の切断時に通知を受け取るには、InputSystem.onDeviceChange
イベントにサブスクライブし、タイプ InputDeviceChange.Disconnected
のイベントを待機します。
Input System では、接続が切断された Device が InputSystem.disconnectedDevices
で追跡されます。後でこれらの Device のいずれかが再接続されると、その Device が以前に接続されていたことが検出され、該当する InputDevice
インスタンスが再び使用されます。これにより、PlayerInputManager
は、同じ ユーザー に Device を再び割り当てることができます。
Device ID
作成された各 Device には、一意の数値 ID が割り当てられます。この ID には、InputDevice.deviceId
を介してアクセスできます。
すべての ID は、Unity セッションごとに 1 回だけ使用されます。
Device の使用法
InputControl
と同様に、Device に使用法を関連付けることができます。usages
プロパティを使用して使用法を照会し、InputSystem.SetDeviceUsage()
を使用して設定することができます。使用法は、任意の意味を持つ任意の文字列にすることができます。Input System で Device の使用法が割り当てられる一般的な例は、"LeftHand" または "RightHand" という使用法でタグ付けされる、XR コントローラーの利き手の設定です。
Device コマンド
入力 イベント が Device からデータを配信するのに対して、コマンドは Device にデータを返送します。Input System では、コマンドを使用して Device から特定の情報の取得したり、Device の機能 (振動効果など) をトリガーしたり、また、その他のさまざまなニーズに使用します。
Device へのコマンドの送信
Input System では、InputDevice.ExecuteCommand<TCommand>
を介して Device にコマンドを送信します。Device コマンドを監視するには、InputSystem.onDeviceCommand
を使用します。
各 Device コマンドには IInputDeviceCommandInfo
インターフェースが実装されています。このインターフェースに必要なのは、コマンドのタイプを識別する typeStatic
プロパティだけです。そのコマンドの処理方法は、Device のネイティブ実装に任せられます。一般的な例の 1 つは、""HID への HID 出力レポート の送信に使用される HIDO
コマンドタイプです。
カスタムデバイスコマンドの追加
カスタムの Device コマンドを作成する (特定の HID の機能をサポートする場合などに) には、Device に送信する必要のあるすべてのデータを含む struct
(構造体) を作成し、typeStatic
プロパティを追加して、その構造体に IInputDeviceCommandInfo
インターフェースを実装します。HID にデータを送信するには、このプロパティが "HIDO"
を返す必要があります。
次に、この構造体のインスタンスを作成し、InputDevice.ExecuteCommand<TCommand>
を使用して Device に送信します。構造体のデータレイアウトは、デバイスが解釈するデータのネイティブ表現と一致している必要があります。
Device の状態
他のタイプの Control と同様に、各 Device には、その Device に関連付けられたすべての Control の状態を格納するメモリブロックが割り当てられます。
状態変更
状態変更は通常、ネイティブバックエンドからの 状態イベント によって開始されますが、InputControl<>.WriteValueIntoState()
を使用すると、任意の Control の状態を手動で上書きできます。
状態変更の監視
InputState.AddChangeMonitor()
を使用して、Control の状態が変更されるたびに呼び出されるコールバックを登録できます。Input System には、同じメカニズムを使用して Input Action が実装されています。
状態の合成
Input System では、既存の状態から新しい状態を合成できます。このような合成された状態は、例えば、Pointer
から Touchscreen
に継承された press
ボタン Control で使用されます。物理ボタンのあるマウスとは異なり、Touchscreen
の場合、このボタンは、Device のバックエンドから送信される実際のデータとは対応しない シンセティック Control です。Input System では、現在継続中のタッチがある場合は、ボタンが押されていると見なされ、それ以外の場合は、解放されていると見なされます。
これを実現するために、Input System では、入力キューを使用せずに任意の状態変更をシステムに追加できる InputState.Change
が使用されます。Input System では、状態変更が同期して直接取り込まれます。その場合も、状態変更 モニター は想定どおりにトリガーされます。
Device の操作
Device の監視
新しい Device が作成されたときや、既存の Device が削除されたときに通知を受け取るには、InputSystem.onDeviceChange
を使用します。
InputSystem.onDeviceChange +=
(device, change) =>
{
switch (change)
{
case InputDeviceChange.Added:
Debug.Log("New device added:" + device);
break;
case InputDeviceChange.Removed:
Debug.Log("Device removed:" + device);
break;
}
};
InputSystem.onDeviceChange
には、デバイスに関連する他の変更についても通知が配信されます。詳細については、InputDeviceChange
enum を参照してください。
Device の追加と削除
API を介して手動で Device の追加と削除を行うには、InputSystem.AddDevice()
と InputSystem.RemoveDevice()
を使用します。
これによって独自の Device を作成でき、テストの目的に、または他のイベントから入力を合成する仮想 Input Device が必要な場合に役立つ可能性があります。例として、Input System で提供されている on-screen Control を参照してください。オンスクリーン Control に使用される Input Device は完全にコードで作成されます。ネイティブ表現 はありません。
カスタム Device の作成
ノート: この例では、固定レイアウトを持つ Device (つまり、実装する具体的なモデルがわかっている場合) のみが対象となります。これは、HID のようなインターフェースとは異なります。HID は、Device がインターフェースを介して自己記述でき、その構造はさまざまです。固定 Device レイアウトは自己記述型 Device に対応できないため、この場合は、レイアウトビルダー を使用して、ランタイムに取得される情報から Device レイアウトを構築する必要があります。
カスタム Device の作成が必要になる状況は、主に以下の 2 つです。
- 入力を生成する既存の API があり、それを Input System に反映させる場合。
- 使用しようとしている HID が Input System で無視されるか、Input System で自動生成されたレイアウトの機能ではニーズが満たされない場合。
2 つ目のシナリオについては、HID フォールバックのオーバーライド を参照してください。
以下のステップでは、1 つ目のシナリオに対処しています。このシナリオでは、新しい Input Device を完全にゼロから作成し、サードパーティの API からその Device に入力を提供します。
ステップ 1: 状態構造体
最初のステップでは、システムが入力を受け取って格納する形態を表す C# struct
(構造体) を作成します。また、この構造体では、Device の状態を取得するために Input System で作成される必要のある InputControl
インスタンスが記述されます。
//"状態構造体" は、Device が使用するメモリ形式を記述します。各 Device は、
//カスタム形式でメモリを取得し、格納することができます。その後、InputControl は、
//メモリのそれぞれの部分に接続して値を読み出します。
//
//メモリ形式と外部表現を 1 対 1 でバイナリレベルで一致させることが
//重要な場合は、通常、LayoutLind.Explicit の使用が
//推奨されます。
[StructLayout(LayoutKind.Explicit, Size = 32)]
public struct MyDeviceState :IInputStateTypeInfo
{
//タイプを確認できるように、すべての状態を FourCC コードでタグ付けする
//必要があります。任意の文字を使用できます。独自の Device に関連付けられた
//メモリであることを容易に認識できる文字を選択してください。
public FourCC format => new FourCC('M', 'Y', 'D', 'V');
//フィールドに InputControlAttribute を設定して、構造体のパブリックフィールドに対応する
//Control を作成するように Input System に指示します。
//複数のボタンを 16 ビットのフィールドで表します。1 つのボタンを作成し、ビット #3
//(ゼロベース) に関連付けます。ボタンは、必ずしもビットとして格納する必要はありません。
//例えば、float や short として格納することもできます。データの
//格納形式は、InputControlAttribute.format プロパティによって
//特定されます。省略した場合は、システムによって通常は、フィールドの値型から
//推測されます。
[InputControl(name = "button", layout = "Button", bit = 3)]
public ushort buttons;
//浮動小数点軸を作成します。名前を指定しない場合は、フィールドから
//取得されます。
[InputControl(layout = "Axis")]
public short axis;
}
Input System のレイアウトメカニズムでは、InputControlAttribute
による注釈を使用して、Control を Device のレイアウトに追加します。詳細については、レイアウトシステム のドキュメントを参照してください。
これで状態構造体が定義され、入力データを Input System に送信して格納できるようになりました。次に必要となるのは、カスタムデータ構造体を使用してカスタム Device を表す InputDevice
です。
ステップ 2: Device クラス
次に、InputDevice
基本クラスの 1 つから派生したクラスが必要です。InputDevice
に直接基づく Device を作成するか、より具体的な Gamepad
などの Device タイプを選択することができます。
ここでは、目的の Device が、既存のどの Device クラスにも適合しない場合を想定し、InputDevice
から直接派生する例を示します。
//InputControlLayoutAttribute 属性は、Device をレイアウトとして
//登録するときのデフォルトの動作をオーバーライドする場合にのみ
//必要です。
//InputControlLayoutAttribute の最も一般的な用途は、カスタムの "状態構造体" を
//`stateType` プロパティを使用してシステムに指示することです。詳細は以下を参照してください。
[InputControlLayout(displayName = "My Device", stateType = typeof(MyDeviceState))]
public class MyDevice :InputDevice
{
//状態構造体では、便宜上、2 つの Control を Device の
//サーフェスに追加しました。いずれの場合も、Device に
//Control が追加されます。 Control をプロパティとして
//公開すると、コード内でアクセスしやすくなります。
public ButtonControl button { get; private set; }
public AxisControl axis { get; private set; }
//Input System では、Device を構築した後で、それをシステムに
//追加する前に、このメソッドを呼び出します。最後に追加する設定がある場合は、
//ここで実行します。
protected override void FinishSetup()
{
base.FinishSetup();
//ノート: Input System では、Control が自動的に作成されます。
//そのため、ここでは `new` を実行せず、Control の検索のみを
//行います。
button = GetChildControl<ButtonControl>("button");
axis = GetChildControl<AxisControl>("axis");
}
}
ステップ 3: 更新メソッド
これで Device と、その Device に関連付けられた状態の形式が定義されました。以下のメソッドを呼び出すと、2 つの Control を持つ完全に設定済みの Device を作成できます。
InputSystem.AddDevice<MyDevice>();
ただし、この Device は、まだ入力を受け取りません。なぜなら、入力を生成するコードを、まだ追加していないためです。入力を生成するには、スレッドなどの任意の場所から、InputSystem.QueueStateEvent
または InputSystem.QueueDeltaStateEvent
を使用できます。以下の例では IInputUpdateCallbackReceiver
を使用しています。これを InputDevice
に実装すると、OnUpdate()
メソッドが追加され、InputSystem.onBeforeUpdate
から自動的に呼び出されます。ここで、現在の入力更新に入力イベントを提供します。
ノート: デバイスへの入力を生成する場所が既にある場合は、このステップをスキップし、
IInputUpdateCallbackReceiver
を使用する代わりに、独自の場所から入力イベントをキューに追加できます。
public class MyDevice :InputDevice, IInputUpdateCallbackReceiver
{
//...
public void OnUpdate()
{
//実際には、ここで外部の API からデータを
//読み出します。この例では、空の入力を使用しています。
var state = new MyDeviceState();
InputSystem.QueueStateEvent(this, state);
}
}
ステップ 4: Device の登録と作成
これでデバイスが機能できるようになりましたが、まだ登録 (システムに追加) されていません。このため、例えば、Action エディター でバインディングを作成しようとしても、このデバイスは表示されません。
システムへのデバイスタイプの登録は、Unity の起動時に自動的に実行されるコード内から行うことができます。そのためには、MyDevice
の定義を以下のように変更します。
//InitializeOnLoad 属性を追加すると、各 C# ドメインのロード後に
//クラスの静的コンストラクターが自動的に実行されます。
# if UNITY_EDITOR
[InitializeOnLoad]
# endif
public class MyDevice :InputDevice, IInputUpdateCallbackReceiver
{
//...
static MyDevice()
{
//RegisterLayout() で "Control レイアウト" がシステムに登録されます。
//これは、個々の Control (スティックなど) のレイアウトや、
//この例のような Device 全体 (それ自体が Control) のレイアウトに
//することができます。
InputSystem.RegisterLayout<MyDevice>();
}
//さらに、プレイヤーで静的コンストラクターの実行がトリガーされるようにする
//しくみも必要です。そのためには、空のメソッドに RuntimeInitializeOnLoadMethod
//を追加できます。
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void InitializeInPlayer() {}
}
これで Device タイプがシステムに登録され、Control ピッカーで選択できるようになります。それでもまだ、Device の接続時にその Device のインスタンスを追加するしくみが必要です。
理論的には、InputSystem.AddDevice<MyDevice>()
をどこかで呼び出すことができます。しかし、現実のシナリオでは、作成した Input Device を、サードパーティ API での ID に関連付けることが必要になる可能性があります。
例えば、以下のようなコードを記述することが考えられます。
public class MyDevice :InputDevice, IInputUpdateCallbackReceiver
{
//...
//これは正しく機能しません。
public ThirdPartyAPI.DeviceId externalId { get; set; }
}
これを、AddDevice<MyDevice>
を呼び出した後に Device に設定すると、機能するように見えます。しかし、これは、エディターでは想定どおりに機能しません。Input System では、InputDeviceDescription
と、選択されたレイアウト (およびレイアウトのバリアント) の組み合わせからのみ Device を作成する必要があるためです。さらにシステムでは、デバイスの使用法 (InputSystem.SetDeviceUsage()
および関連メソッド) など、デバイスごとの可変プロパティの特定のセットがサポートされています。これによってシステムでは、ドメインの再ロード後にエディターで Device を再作成したり、プレイヤーへの接続時にリモート Device の複製を作成したりすることが簡単になります。この要件に従うために、サードパーティ API から提供される情報を InputDeviceDescription
にキャストしてから、InputDeviceMatcher
を使用して説明をカスタムの MyDevice
レイアウトに一致させる必要があります。
例えば、サードパーティ API に以下のような 2 つのコールバックがあるとします。
public static ThirdPartyAPI
{
//この例では、引数が、Device の名前を格納した文字列であることと、
//同じ名前を持つ複数の Device が外部 API に存在しないことが
//想定されています。
public static Action<string> deviceAdded;
public static Action<string> deviceRemoved;
}
これらのコールバックにフックしてから、その応答としてデバイスを作成し、破棄することができます。
//この例では、[ExecuteInEditMode] が設定された MonoBehaviour を
//使用して設定コードを実行します。他にもさまざまな方法を使用できます。
[ExecuteInEditMode]
public class MyDeviceSupport :MonoBehaviour
{
protected void OnEnable()
{
ThirdPartyAPI.deviceAdded += OnDeviceAdded;
ThirdPartyAPI.deviceRemoved += OnDeviceRemoved;
}
protected void OnDisable()
{
ThirdPartyAPI.deviceAdded -= OnDeviceAdded;
ThirdPartyAPI.deviceRemoved -= OnDeviceRemoved;
}
private void OnDeviceAdded(string name)
{
//システムに Device の説明を提供します。システムは応答として、
//これを登録済みのレイアウトと照合し、Device を作成します。
InputSystem.AddDevice(
new InputDeviceDescription
{
interfaceName = "ThirdPartyAPI",
product = name
});
}
private void OnDeviceRemoved(string name)
{
var device = InputSystem.devices.FirstOrDefault(
x => x.description == new InputDeviceDescription
{
interfaceName = "ThirdPartyAPI",
product = name,
});
if (device != null)
InputSystem.RemoveDevice(device);
}
//MyDevice の登録を静的コンストラクターから
//ここに移動し、登録を変更してマッチャーも
//提供されるようにします。
protected void Awake()
{
//インターフェースを "ThirdPartyAPI" として報告する Input Device を
//取得するための一致を追加します。
InputSystem.RegisterLayout<MyDevice>(
matches:new InputDeviceMatcher()
.WithInterface("ThirdPartyAPI"));
}
}
ステップ 5: current
と all
(任意)
利便性のために、最後に使用した特定のタイプのデバイスにすばやくアクセスしたり、特定のタイプのすべてのデバイスを一覧表示したりすることができます。これを行うには、MyDevice
の API に、current
および all
ゲッターのサポートを追加します。
public class MyDevice :InputDevice, IInputCallbackReceiver
{
//...
public static MyDevice current { get; private set; }
public static IReadOnlyList<MyDevice> all => s_AllMyDevices;
private static List<MyDevice> s_AllMyDevices = new List<MyDevice>();
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}
protected override void OnAdded()
{
base.OnAdded();
s_AllMyDevices.Add(this);
}
protected override void OnRemoved()
{
base.OnRemoved();
s_AllMyDevices.Remove(this);
}
}
ステップ 6: Device コマンド (任意)
最後に、必要に応じて Device コマンドのサポートを追加します。"デバイスコマンド" は、入力とは逆のコマンドです。つまり、デバイスコマンドは入力デバイス に 送信されるデータで構成されます。入力デバイスは、動作の一部としてデータを返す場合もあります (関数呼び出しとよく似ています)。これを使用すると、デバイスのバックエンドと通信して、設定を照会したり、触覚フィードバックなどの効果を開始したりできます。現時点では、この用途に適したインターフェースは用意されていませんが、現状のインターフェースを使用して解決できるシナリオもあります。
例えば、以下のコードは、ハードウェアに基づかないデバイス (シミュレートされたデバイス) を実装する場合に、ハードウェアからの報告をシミュレートして、デバイスがバックグラウンドで実行できること、および同期コマンドをサポートしていることを知らせる方法を示しています。これは、アプリケーションのフォーカスが失われた後に戻ったときに、デバイスによって Action がキャンセルされることを防ぐために役立ちます。詳細については、Device の同期 を参照してください。
public class MyDevice :InputDevice, IInputCallbackReceiver
{
//...
protected override unsafe long ExecuteCommand(InputDeviceCommand* commandPtr)
{
var type = commandPtr->type;
if (type == RequestSyncCommand.Type)
{
//デバイスが同期コマンドをサポートし、処理していることを報告します。
//これにより、フォーカス変更時のデバイスのリセットを防ぐことができます。
result = InputDeviceCommand.GenericSuccess;
return true;
}
if (type == QueryCanRunInBackground.Type)
{
//デバイスがバックグラウンド実行をサポートしていることを通知します。
((QueryCanRunInBackground*)commandPtr)->canRunInBackground = true;
result = InputDeviceCommand.GenericSuccess;
return true;
}
result = default;
return false;
}
}