Input Systemで入力値を読むとき、違う型が来ると次のようにエラーが出てしまうの…
Input Actionから値を読む前に型チェックすれば良いわ。この辺を拡張メソッド化すれば楽に使えるようになるわ。
Input SystemのActionから入力値を読み込む時は、次のようなコードを書くことが多いでしょう。
実際にAction側から受け取れる値は、取得先のControl(ボタンやスティックなど)によって型が異なる場合があります。
例えば、上記のfloat型の入力値として取得するコードに対し、次のようにスティックなどの2軸入力(Vector2型)をActionから受け取れるようにすると実行時エラーとなります。
これは、ReadValueメソッド側で入力値の型と受取り側の要求する型が一致しない場合、InvalidOperationException例外をスローするためです。
参考:Struct InputAction.CallbackContext| Input System | 1.6.3
このようなエラーを回避するためのアプローチの一つとして、入力値を取得する前に型チェックが出来れば防ぐことが可能です。
特に、Input Action側で複数の型のBindingを指定し、2通り以上の型で入力値が渡ってくるケースなどで活躍できるでしょう。
本記事では、Input Actionの入力値を事前チェックしてエラーを防ぐ方法を解説します。また、拡張メソッドを自作して使いやすくする例も紹介します。
- Unity 2023.1.1f1
- Input System 1.6.3
目次 非表示
前提条件
事前にInput Systemパッケージがインストールされ、有効化されているものとします。
この手順は以下記事で解説しています。
また、本記事ではInput Action経由で入力を取得するものとします。
Input Actionの基本的な使い方は以下記事で解説しています。
コールバック経由で型をチェックする場合
コールバック経由で入力値を取得する場合、引数から得られるInputAction.CallbackContext構造体のReadValueメソッドを用いて値を取得します。
このInputAction.CallbackContext構造体には、渡ってきた入力(Control)の型をvalueTypeプロパティから取得できます。
参考:Struct InputAction.CallbackContext| Input System | 1.6.3
これを次のようなコードで取得可能かどうかをチェックします。
Type.IsAssignableFromメソッドは、引数に指定された型が呼び元の型に代入可能かどうかを判定します。
型同士の比較をせずにType.IsAssignableFromメソッドを使う理由は、入力値の型が要求している型の派生型でも取得可能にするためです。
例えば、構造体Aを継承した構造体Bがある場合、構造体Bの入力値が来た時に構造体Aにキャストして取得可能になります。
サンプルスクリプト
以下、指定されたActionの入力値がfloat型かどうかチェックし、float型なら値を取得してログ出力する例です。
上記をCallbackExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターより受け取る入力を設定します。
例ではゲームパッドの左トリガー(float型)と左スティック(Vector2型)を設定するものとします。
実行結果
float型の入力が来た時はログ出力するが、それ以外の型(例では左スティックのVector2型)の入力が来た時は無視するようになりました。
左スティックが操作されてもエラーメッセージが表示されません。
スクリプトの説明
型チェックして入力値を受け取る処理は以下部分です。
例ではfloat型として入力値を受け取りたいため、typeof(float)に対してIsAssignableFromメソッドを呼び出し、引数contextのvalueTypeプロパティを指定しています。
このコードにより、context.valueTypeがfloat型変数に代入可能ならtrue、それ以外ならfalseが得られるため分岐処理しています。
float型変数に代入可能なら、context.ReadValue<float>メソッドでfloat型として入力値を安全に取得できます。
ポーリング方式で型をチェックする場合
コールバックではなく、InputActionインスタンスから直接ReadValueメソッドで入力値を取得する場合でも型チェックができます。
ただし、コールバック経由で取得する場合と比べて若干面倒で、まず現在アクティブなInputControlを調べる必要があります。
入力が何もない場合など、アクティブなControlが存在しない場合もあるため、nullチェックも必要です。
参考:Class InputAction| Input System | 1.6.3
Controlが取得出来たら、valueTypeプロパティに入力値の型が格納されているため、前述の例と同じ要領で型チェックが行えます。
サンプルスクリプト
ポーリング方式で入力値を受け取る際に型チェックを行う例です。
上記をPollingExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりActionを設定してください。
例ではゲームパッドのSouthボタンと左スティックを割り当てることとしました。
実行結果
ボタン(float型のControl)を押すと、その間入力値がログ出力され続けます。
こちらもfloat型以外のControl(例では左スティック)が入力されてもログ出力されません。
スクリプトの説明
アクティブなControl取得から型チェック、入力値取得までの一連の流れは、以下Update内で行っています。
1つ目の例と大きく変わった部分はInputActionからアクティブなControlを取得する部分になります。
拡張メソッドを自作して使いやすくする
ここまで入力値の取得前に型チェックを行う方法を解説してきました。しかしながら、このような型チェックのコードを毎回書くのは大変かもしれません。
これは拡張メソッドとして使いまわせるようにすると楽です。
コールバックで使うInputAction.CallbackContext構造体、ポーリングなどで使うInputActionクラスそれぞれに対して型チェック付きの拡張メソッドを実装する方法を紹介します。
拡張メソッドの実装例
以下、拡張メソッドを実装したクラスの完成形です。
上記をReadValueExtensions.csなどとUnityプロジェクトに保存すると使用可能になります。
使用例
以下、コールバックとポーリング方式それぞれで前述の拡張メソッドを使用して入力値を取得する例です。
上記をUseExtensionExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりコールバック用、ポーリング用それぞれのActionを設定する動くようになります。
例では、コールバック用ActionにゲームパッドのWestボタンと左スティック、ポーリング用ActionにゲームパッドのEastボタンと左スティックを割り当てることとします。
実行結果
コールバックとポーリングそれぞれで型チェック付きで入力値が取得できていることが確認できます。
左スティックを操作してもエラーになりません。
スクリプトの説明
コールバック側では、入力値の取得に失敗した場合は処理を止めたいため、自作のTryReadValue拡張メソッドで成否チェックもしています。
一方、ポーリング方式での取得では、入力値の取得に失敗した場合は0として処理したいため、自作のReadValueSafe拡張メソッドで取得し、成否チェックは行いません。
さいごに
Input SystemのActionから入力値を取得する時は、Input Actionの設定次第では期待する型と異なる型の入力が渡ってくる場合があります。
設定を間違えなければ起こり得ないですが、複数の型を許容するActionの場合、異なる入力値を取得するとエラーとなるため型チェックが出来たほうが望ましい場合があります。
型チェックのコードを毎回書くのが大変な場合、拡張メソッドを自作して使いやすくすると良いでしょう。