UIのボタンを押す時だけInput Systemのクリックを反応させたくない場合、どうすればいいの?
カーソルや指の位置がUI上にあるかどうかチェックすれば良いわ。Event Systemのレイキャストなどの機能を活用できるわ。
Unity UI(uGUI)とInput Systemを併用している環境において、UI操作時にマウス左ボタンなどの入力を無効化する方法の解説です。
普通に実装すると、UIのボタンをクリックした時に、Input System側のマウスの左ボタンなどの入力が反応してしまいます。
これは、Input System側は特にUI操作関係なしに入力値を返す挙動になっているためです。
このような場合、ボタン入力を取得する際にマウスカーソルや指などがボタンの上にあるかどうかを判定し、上にない時だけ入力を受け付けるようにする必要があります。
また、Input Fieldのキー入力との競合を避けたい場合は、Input Fieldが選択されている場合に入力を受け付けないようにする必要があります。
本記事では、Unity UIを使用した環境下でUI操作とInput Systemの特定入力を排他的に扱う方法を解説します。
- Unity 2023.1.16f1
- Input System 1.7.0
目次 非表示
前提条件
予めInput Systemパッケージがインストールされ、有効化されているものとします。
ここまでの導入方法、および基本的な使い方は以下記事で解説しています。
また、本記事ではInput Actionを使用してマウスの左ボタンやタッチ入力などを取得する場面を想定します。Input Actionの基本的な使い方は以下記事で解説しています。
例では、次のようにCanvas上に複数の操作可能なUIが配置されているものとします。
Input System使用化でUI操作を可能にするためには、Event SystemのStandard Input ModuleをInput System UI Input Moduleに置き換える必要があります。
参考:UI support | Input System | 1.7.0
カーソルや指の位置がUI上にあるかどうかの判定
主に次の2通りの方法があります。
- EventSystem.currentSelectedGameObjectプロパティを使用する
- EventSystem.RaycastAllメソッドを使用する
前者は、全てのUIを対象としてUI上にカーソルなどがあれば入力をブロックする方法です。そのため、Imageコンポーネントなど操作系意外のUIも対象となります。
次のようなコードでUIの上にあるかどうかを判定できます。
参考:Method IsPointerOverGameObject | Unity UI | 1.0.0
後者は、特定のUIに限定して判定する方法です。こちらのほうが柔軟性はありますが、レイキャストして得られた要素に対し型チェックを行う処理が走ります。
UIのボタン(Buttonクラス)やスライダー(Sliderクラス)などはSelectableクラスを継承しているため、次のようなコードでレイキャストして得られた結果に対して型チェックを行うことで実現します。
参考:Class Selectable | Unity UI | 1.0.0
参考:Method RaycastAll | Unity UI | 1.0.0
ただ、この判定処理だけではScrollRectのドラッグ操作など一部対応できないものも存在するため、必要に応じてタグ名などのチェック処理を追加すると良いでしょう。
タグ名による判定はあくまでも一例です。これ以外にも、コンポーネントで判断したりすることも可能です。
開発するゲームやアプリケーションに合わせて適切に設計してください。
UI全体を対象とした場合の実装例
サンプルスクリプト
Imageなども含むUI要素全てを対象に、UI上にカーソルがあれば入力を受け付けなくする例です。
上記をUIExclusiveExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりInput Actionの入力情報の設定を行うと機能するようになります。
例では、Pointer > Press(Control Pathは<pointer>/press)を入力の受け取り対象として設定するものとします。
Control Pathに「Pointer」を指定すると、マウス(Mouse)、タッチ(Touch)、ペン(Pen)全ての操作を対象とすることができます。
参考:Pointers | Input System | 1.7.0
もしマウス入力だけにしたい場合は、Mouse > Left Buttonとしてください。
実行結果
マウスクリックした時、UI上にカーソルが無い状態では反応し、カーソルがある状態では反応しません。
すべてのUIを対象とするため、Imageコンポーネント上で押された時も反応しません。
スクリプトの説明
カーソルがUIの上にあるかどうかの判定は、以下処理で行なっています。
EventSystem.currentプロパティで現在のEvent Systemインスタンスを取得し、EventSystem.IsPointerOverGameObjectメソッドでカーソルがEvent System管理下のゲームオブジェクト上に位置しているかどうかを取得します。
参考:Property current | Unity UI | 1.0.0
参考:Method IsPointerOverGameObject | Unity UI | 1.0.0
コールバックでUI上にカーソルがあるかどうかを判定し、カーソルがある時だけ入力を受け付けるまでの流れは以下のようになります。
操作可能なUIに限定した場合の実装例
サンプルスクリプト
SelectableなUIのみを対象に、UI上にカーソルがあれば入力を受け付けなくする例です。
ScrollRectなどにも対応させるため、入力ブロックするタグで判定する処理も挟んでいます。
上記をSelectableExclusiveExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターより各種Actionやタグ名の設定を行います。
Button ActionにUI操作との競合を防ぎたい対象のAction、Cursor Position Actionにカーソル位置を受け取るActionを設定してください。
また、必要に応じてUi Tagに操作対象とみなすUIのタグを指定します。
例では、Scroll Viewのスクロール領域が非SelectableなUIのため、Scroll View配下のContentオブジェクトに「InputExclusiveUI」タグを指定し、インスペクターにも「InputExclusiveUI」を指定するものとします。
実行結果
操作可能なUI(ボタン、スクロールビューなど)のみ入力がブロックされ、Imageなどの操作対象でないUIではブロックされず反応するようになりました。
スクリプトの説明
指定したカーソル位置でレイキャスト判定する必要があるため、次のようにカーソル位置を受け取るActionが必要です。
また、レイキャスト判定の入出力用の変数を用意しておきます。
レイキャスト用の情報は、コールバックの先頭の以下処理で行います。
_pointerがnullなら(初回なら)、新しいPointerEventDataインスタンスを生成してフィールドとして保持しておきます。
そして、PointerEventData.positionフィールドにカーソル位置用のActionからカーソル位置(スクリーン座標)を読んでセットしています。
作成されたPointerEventDataの情報に基づいて、レイキャストを行う処理は以下部分です。
これにより、_results変数にヒットしたUIオブジェクトの一覧が格納されます。
参考:Method RaycastAll | Unity UI | 1.0.0
ヒットしたオブジェクトに対して入力のブロック対象かどうかを判定する処理は以下部分です。
指定したブロック対象のタグ名である、またはSelectableなコンポーネントがあれば入力ブロックするUIとみなします。
Input Fieldへの対応
ここまでは、マウスなどによるクリック操作を主な対象として解説しました。
しかし、Input Fieldでのテキスト入力では、「選択されている時」にキーボードなどの入力をブロックする必要があります。
サンプルスクリプト
Input Field(TextMesh Proまたはレガシー版)が選択中に指定されたActionの入力をブロックする例です。
上記をInputFieldExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターからActionを設定すると機能するようになります。
ここでは、キーボードの「A」キーを入力として受け取るものとします。
実行結果
Input Fieldにフォーカスしている時は、入力が反応せずにログ出力されなくなります。
スクリプトの説明
Input Fieldが選択中なら入力をブロックする処理は以下部分です。
選択中のオブジェクトはEventSystem.currentSelectedGameObjectプロパティから取得できます。
参考:Property currentSelectedGameObject | Unity UI | 1.0.0
TextMesh Pro版とレガシー版でInput Fieldのクラスが異なるため、これらのどちらのクラスかをOR条件で調べています。
TextMesh Pro版はTMP_InputFieldクラス、レガシー版はInputFieldクラスとなります。
参考:Class TMP_InputField | TextMeshPro | 3.0.9
参考:Class InputField | Unity UI | 1.0.0
アプリのフォーカス復帰時に入力を受け付けない
ここまで解説した例では、アプリケーションのフォーカスを一度失って復帰した時に、UIをクリックしたにも関わらずスルーせず反応してしまうことがあります。
これを回避するためには、アプリケーションのフォーカス復帰直後は入力をブロックするようにすれば良いです。
フォーカス復帰のタイミングは、MonoBehaviourのOnApplicationFocusイベントで取得できます。
参考:MonoBehaviour-OnApplicationFocus(bool) – Unity スクリプトリファレンス
サンプルスクリプト
以下、フォーカス復帰時の考慮も取り入れた例です。
SelectableなオブジェクトかどうかでUI判定する処理も含まれています。
上記をCheckFocusExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりActionの設定を行います。
設定内容は2つ目の例と一緒のため割愛します。
実行結果
フォーカスを失った状態から画面をクリックしてフォーカス復帰した時は、ボタンが反応しないようになりました。
フォーカス中は通常通りボタンが反応します。
スクリプトの説明
まず、フォーカス復帰直後かどうかを判定するために、復帰直後のフレームカウントを格納するフィールドを用意しておきます。
そして、フォーカス復帰時にこのフレームカウントを更新します。
ボタン入力のコールバックでは、フォーカス復帰より後かどうかの条件判定が加わっています。
これは以下部分です。
直近のフォーカス復帰時のフレームカウント以前のフレームなら、入力をブロックしています。
さいごに
マウス操作とUI操作の競合回避は、入力を読み込む時にUI上にカーソルがあるかどうかをチェックすることで実現できます。
キーボード入力とInput Field入力では、Input Fieldが選択中の時に入力をブロックするようにすれば良いです。
これらの処理を共通化したい場合、このようなマウスオーバー判定処理などをユーティリティクラスでラップするのが一つの解決策と言えるでしょう。