Input Systemで独自のコントローラーを作って使いたいの。例えば仮想コントローラーなどを作りたいの。
可能だわ。InputDevice継承クラスを実装してInput Systemにデバイス登録すれば使えるようになるわ。
Input Systemでカスタムデバイスを実装する方法の解説記事です。
カスタムデバイスはInputDevice継承クラスを実装し、これをInput System側に登録すると使えるようになります。
独自デバイスを実装することで、例えば次のようなことが実現できます。
- 独自のハードウェアをコントローラーとして認識させる
- Input Systemから認識されない、または予期せぬ形で認識されたHIDのレイアウトを強制的に上書きする
- 仮想デバイスを作成する
参考:Devices | Input System | 1.7.0
参考:Class VirtualMouseInput| Input System | 1.7.0
Input System側で提供されているKeyboard、Mouse、Gamepadなどのクラスも全てInputDeviceクラスを継承して実装されています。
参考:Class InputDevice| Input System | 1.7.0
本記事では、Input Systemでカスタムデバイスを実装する方法を解説していきます。また、Input Actionから実際に設定したり、スクリプトから入力値を取得する部分にも触れます。
- Unity 2023.2.5f1
- Input System 1.7.0
目次 非表示
前提条件
事前にInput Systemがインストールされ、有効化されているものとします。
ここまでのセットアップ手順は以下記事にて解説しています。
また、本記事を読み進めるにあたり、Input Systemのデバイスの仕組みについて把握しておくと理解がスムーズです。
デバイスの仕組みの解説は以下記事をご覧ください。
カスタムデバイスの実装
カスタムデバイスは以下の流れで実装できます。
- 入力状態の生データを定義する構造体の実装
- InputDevice継承クラスの実装
- デバイスのレイアウトの登録
- Control(入力ソース)の実装
- デバイスの登録・解除処理の実装
順番に解説していきます。
入力状態の生データを定義する構造体の実装
ボタンやアナログ入力などのデバイスから得られる入力は、構造体に入力状態として保持されます。
これは、IInputStateTypeInfoインタフェースを実装した構造体として定義します。
例では、ボタンをただ1つ持つカスタムデバイスとして解説を進めます。
IInputStateTypeInfoインタフェースは、次のようにFour CCを返すformatプロパティを持ちます。
Four CCは、デバイスを識別するための4文字で表現される値です。例では、「1」「B」「T」「N」という値を返しています。
構造体の各種フィールドは、次のように明示的にオフセットを指定する形でメモリ上にレイアウトしています。
レイアウトに特にこだわりがなければ、以下のように自動的に連続的な配置にすることも可能です。
InputDevice継承クラスの実装
カスタムデバイスクラスはInputDevice継承クラスとして実装します。
参考:Class InputDevice| Input System | 1.7.0
デバイス名やどの構造体を入力状態データとして扱うかなどの情報は、InputControlLayout属性で指定します。
それぞれdisplayName、stateTypeに設定すれば良いです。
参考:Class InputControlLayoutAttribute| Input System | 1.7.0
デバイスを登録する
実装したカスタムデバイスをInput System側から認識させるためには、デバイスをレイアウトとして登録する必要があります。
これはInputSystem.RegisterLayoutメソッドで行います。
参考:Class InputSystem| Input System | 1.7.0
実際の処理の実行タイミングは、UnityエディタならUnity起動時、ビルドではアプリ起動時が相応しいでしょう。
Unityエディタ上でカスタムデバイスを登録すると、Input ActionのBindingの候補に登録したデバイスが表示されるようになります。
入力イベントの通知
ボタンの押下状態などのControlの入力状態が変化した場合、そのままではInput System側は入力を検知できません。
そのため、変化した瞬間または定期的にInput System側に入力状態の更新を通知する必要があります。
入力の通知はInputSystem.QueueStateEventメソッドで行います。これは次の3つの引数を持つstaticメソッドです。
第1引数にデバイスのインスタンス、第2引数に入力状態の構造体データ、第3引数にイベント発生時刻(省略可能)を指定します。
参考:Class InputSystem| Input System | 1.7.0
入力の通知は低レイヤーのドライバからコールバックなどの通知として受け取った時、Input Systemが更新されるタイミングなどいずれの方法でも可能です。
Input Systemが更新されるタイミングで通知したい場合は、デバイスクラスにIInputUpdateCallbackReceiverインタフェースを実装すればよいです。これでOnUpdateメソッドを通じて更新タイミングをフックできます。
参考:Interface IInputUpdateCallbackReceiver| Input System | 1.7.0
例では、次のようにInput Systemが更新される度に、マウス左ボタン入力をバイパスするような形で通知することとします。
Control(入力ソース)の初期化
Input Action経由でしか入力を受け取らない場合は必須ではありませんが、カスタムデバイスクラスに直でアクセスして入力を取得するためのプロパティを実装します。
ボタン入力などは、Controlを通じて取得できます。このようなControlはInputControl継承クラスとして表現されます。
参考:Class InputControl| Input System | 1.7.0
例えば、ボタンを表すControlはButtonControl型プロパティとして定義すればよいです。
参考:Class ButtonControl| Input System | 1.7.0
そして、独自定義したControlは、デバイスが接続されて初期化されるタイミングなどで外部からアクセスできるようにします。
初期化タイミングはFinishSetupメソッドとして実装すればよいです。
参考:Class InputControl| Input System | 1.7.0
ここまでの解説で、カスタムデバイス側の実装は一通り出来たことになります。
デバイスを追加・削除する
実装したカスタムデバイスは、Input Action側やスクリプトから扱えるようになりますが、そのままでは未接続のままなので入力を取得できません。
実際にデバイスを機能させるためには、Input System側にデバイスを追加する必要があります。
参考:Class InputSystem| Input System | 1.7.0
また、一度追加したデバイスは使わなくなった時点などで削除する必要があります。
削除を忘れると、永遠にデバイスが接続された状態になります。この状態で追加すると、重複してデバイスが登録されて増えていってしまいます。
カスタムデバイスの実装例
ここまでの解説を踏まえたカスタムデバイスの実装例です。
カスタムデバイスから入力を受け取るスクリプトは以下のようになります。
上記2つのスクリプトをUnityプロジェクトに保存し、GetCustomDeviceButtonExampleの方を適当なゲームオブジェクトにアタッチすると機能するようになります。
実行結果
マウスの左ボタンをクリックすると、ボタンが反応するようになりました。
スクリプトの説明
カスタムデバイスのスクリプトは前述の通りのため割愛します。
受取り側のスクリプトでは、初期化と終了のタイミングでデバイスの追加と削除を行なっています。
これにより、次のコードで入力を取得できるようになります。
カスタムデバイスのbuttonプロパティ(自作したボタンのControl)のプロパティを参照してボタンの押した瞬間と離した瞬間にログ出力しています。
Input Actionで使用する
カスタムデバイスを登録すると、Input Action側から入力を取得できるようになります。
Input Actionから入力を受け取る方法はいくつか存在しますが、本記事ではスクリプト中から動的生成して取得する例を示します。
実装例
以下、Action経由で前述のカスタムデバイスの入力を受け取る例です。
上記をInputActionExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりカスタムデバイスのActionを設定してください。
実行結果
Action経由でも入力を受け取れるようになりました。
本記事の実装例のように、既存のデバイスのControlの入力をそのままカスタムデバイス入力として反映すると、0と1の値が交互に通知され、毎フレーム押した瞬間と離した瞬間として処理されてしまうことがあります。
低レイヤーから入力値を取得する場合、取得タイミングなどに注意しながら実装するとよいでしょう。
スクリプトの説明
Action経由で入力を受け取るため、次の行でInputActionフィールドを定義しています。
入力があった時のコールバックは、以下のようにperformedコールバックを購読する形で受け取るようにしています。
定義したActionはそのままでは有効にならないため、コンポーネントが有効になるタイミングなどでEnableメソッドで有効化する必要があります。
また、スクリプト側から生成したInput Actionは、最後にDisposeメソッドで破棄しています。
様々なControlの追加例
ここまでの例では、ただ一つのボタンを持つカスタムデバイスの実装例を紹介しました。
複数のボタンやスティックなどの2軸入力など、あらゆる型のデバイスを実装することが可能です。
カスタムゲームパッドの実装例
以下は、独自のカスタムゲームパッドを実装した例です。
実際の使用例は以下のようになります。
この例では左右スティックのみを取得していますが、当然ながら他のボタン入力も同様に取得可能です。
さいごに
Input Systemの既存デバイスは全てInputDevice継承クラスとして管理されます。カスタムデバイスもInputDevice継承クラスとして実装してInput System側に登録することで使用可能になります。
Input System側では期待通りに認識されないHIDをデバイスとして実装したり、仮想デバイスを自由に追加できるようになるため、うまく活用すれば様々な応用が期待できるでしょう。