マウスのシングルクリックとダブルクリックのどちらかを判定する方法を知りたいの。
Input SystemのInteractionを活用すれば出来るわ。シングルクリック判定との共存をどう解決していくかを交えて解説していくわ。
Input Systemの環境下でマウスやタッチパネルのクリック・タップとマルチクリック・マルチタップを判別する方法の解説記事です。
判別方法としては、主に次の2種類が考えられます。
- シングルタップとマルチタップのイベントを同時に拾う
- シングルタップ判定の遅延が無いが、マルチタップ時にシングルタップのロールバックが必要
- シングルタップとマルチタップのイベントを排他的に拾う
- ロールバック処理は不要だが、シングルタップ判定まで遅延がある
それぞれの方法には一長一短があり、状況に応じて使い分けるのが適切でしょう。
このようなシングルタップとマルチタップの判別方法は、以下ドキュメントでも言及されています。
また、Input Systemにはシングルタップとマルチタップを判定する機能がInteractionとして提供されており、これらを活用すると比較的楽に実装できます。
参考:Interactions | Input System | 1.7.0
本記事では、Input SystemのInteractionを活用して、シングルタップとマルチタップを判別する方法を解説していきます。
本記事では、シングルタップ(クリック)とマルチタップ(クリック)を検知する部分に絞って解説します。
マルチタップされた場合のシングルタップ処理のロールバックなど、アプリケーション側の対処方法までは取り扱いません。
- Unity 2023.2.19f1
- Input System 1.7.0
目次 非表示
前提条件
事前にInput Systemパッケージがインストールされ、有効化されているものとします。ここまでの手順が分からない方は、以下記事を参考にセットアップを済ませてください。
また、本記事のタップ判定を実現するためには、Input ActionおよびInteractionを用います。事前に両者の基本を押さえておくと理解がスムーズです。
シングル・マルチタップ両方を検知する
1つ目の方法はシングルタップとマルチタップ両方を検知する方法です。次のメリットとデメリットがあります。
- メリット
- シングル・マルチタップ共に判定までの遅延が無い
- デメリット
- マルチタップを検知したとき、アプリケーション側でシングルタップ処理をロールバックする必要がある
マルチタップされた場合でも、シングルタップイベントが必ず発火してしまうため、シングルタップ操作が競合してしまうケースに注意する必要があります。
- メニューのボタン操作
- シングルタップでフォーカス
- マルチタップで詳細情報のポップアップを開く
- キャラクター操作
- シングルタップで移動
- マルチタップで攻撃・特殊なアクション
- メニューのボタン操作(不適切な例)
- シングルタップで詳細情報のポップアップを開く
- マルチタップでボタンを押す
- キャラクター操作(不適切な例)
- シングルタップで攻撃・特殊なアクション
- マルチタップで移動
適さない例ですが、例えばシングルタップでポップアップを開き、マルチタップで別の意味のある操作を行おうとした場合、タップした瞬間にポップアップが必ず開いて消滅するなど不自然な挙動になってしまうでしょう。
このようにシングルタップとマルチタップが全く異なる意味を持ち、それぞれ排他的に実行したい場合は、後述する排他的に判別方法が適しています。
Input Actionの設定
シングルタップおよびマルチタップ用のActionを2つ用意し、アプリケーション側からそれぞれ入力を取得して判定するものとします。
本記事では、Input Action Assetを新規作成して、ここにそれぞれのActionを定義するものとします。
Input Action Assetが作成されていなければ、プロジェクトウィンドウより右クリック > Create > Input Actionsを選択して、新しいアセットを作成します。
Mapを作成します。例では「Player」としました。
シングルタップ(クリック)となるActionを作成して設定します。例では「Tap」という名前のActionを作成し、「<Pointer>/press」というパスのBindingを定義するものとします。
このActionではタップ判定を行いたいため、Tapという名前のInteractionを追加します。
同様に、マルチタップ判定用のActionも追加します。例では「MultiTap」という名前のActionを作成し、「<Pointer>/press」というパスのBindingを定義し、Multi TapというInteractionを追加するものとします。
ここまでの設定したら、Save Assetボタンをクリックして内容を保存しておきます。
サンプルスクリプト
前述で定義したActionを用いて、シングルタップおよびマルチタップを判別するスクリプトを実装します。
以下、シングルタップとマルチタップが検知されたらログ出力する例です。マルチタップする場合でも、必ずシングルタップは反応します。
上記をBothExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりタップおよびマルチタップ用のActionを指定してください。
最終的に次のようにActionが指定されていれば良いです。
実行結果
シングルタップ、マルチタップ時にそれぞれのログが出力されます。
マルチタップを検知した場合、シングルタップのロールバック処理を行ってからマルチタップ処理を行うような挙動になっています。
スクリプトの説明
まず、フィールドとしてタップ、マルチタップ用のActionをそれぞれ定義しています。
スクリプトが有効化された時に、それぞれのActionに対してコールバックを登録し、Actionを有効化しています。
参考:Class InputAction| Input System | 1.7.0
スクリプト無効化時は、逆にコールバック登録を解除してActionも無効化しています。
シングルタップのコールバック処理は以下の通りです。
タップ(ボタンを押してすぐ離した瞬間)を検知したときはフェーズ(context.phase)がPerformedとなるため、その場合にタップ処理を行っています。
ただし、マルチタップされる時は何度もタップを検知してしまうため、初回のタップかどうかを_isTapPerformedフラグでチェックしています。また、マルチタップされた際にもコールバックが呼ばれる可能性があるため、マルチタップActionのフェーズもチェックしています。
マルチタップのコールバック処理は以下の通りです。
マルチタップ(指定回数の指定時間以内の素早いタップ)を検知した際はフェーズがPerformedになるため、ここでシングルタップのロールバック処理を行ってからマルチタップ処理を行うようにしています。
マルチタップ操作が中断されるとフェーズがCanceledになるため、状態変数をリセットしています。
シングル・マルチタップを排他的に検知する
1つ目の例では、マルチタップ操作する際も必ず最初のタップでシングルタップを検知していました。シングルタップされた瞬間を検知できる反面、マルチタップされた時のロールバック処理が必須になります。
もしシングルタップの反応遅延を許容できるのであれば、次に紹介する排他的にイベントを通知する方法が適しています。
これは、シングルタップ後、マルチタップ操作の要件(タップ間隔)を満たさないと判断した瞬間に検知するため、若干の遅延があります。
- メリット
- マルチタップ操作する際、シングルタップイベントが発火されない
- デメリット
- シングルタップ判定ではイベント発火までのラグがある
Input Actionの設定
1つ目の例のAction構成とします。設定済みならそのままで問題ありません。
サンプルスクリプト
シングルタップとマルチタップを排他的に検知する例です。
上記をExclusiveExample.csという名前で保存し、適当なゲームオブジェクトにアタッチし、1つ目の例同様にインスペクターよりActionを設定してください。
実行結果
マルチタップ操作された時にシングルタップ処理が行われなくなりました。
シングルタップ操作では、ワンテンポ遅れて反応するようになっています。
スクリプトの説明
3回以上のマルチタップ対策のため、タップ数を独自で持つようにしています。
タップされるたびに、タップ数をカウントするようにしています。
マルチタップのコールバック処理は次のように変更されています。
マルチタップされた瞬間(フェーズがPerformed)はマルチタップ処理を行って状態リセット(タップ回数初期化)しています。
マルチタップが途中でキャンセルされた際(フェーズがCanceled)は、中途半端な回数のマルチタップを無視するため、1回だけのタップ(タップ回数が1)の時かつボタンが離された時だけタップとみなしています。
ボタン状態もチェックする理由は、押しっぱなしでタップ判定されてしまうのを防止するためです。
参考:Class InputAction| Input System | 1.7.0
カスタムInteractionで実現する
ここまで2通りの判別方法を紹介してきましたが、いずれもアプリケーション側のロジックが複雑になる弱点が存在します。
実装量が増えますが、カスタムInteractionを介して判定するようにすればこの問題をある程度解消できます。
メリットとデメリットは次の通りです。
- メリット
- アプリケーション側のロジックが単純になる
- 1つのActionだけで実現できる
- デメリット
- カスタムInteractionを独自実装する必要がある
- コールバック内部でInteractionオブジェクトにアクセスして判別する必要がある
カスタムInteractionの実装
シングル・マルチ両方のタップを検知するためのInteractionを実装します。
例では、シングルタップ、マルチタップ、シングルタップ中断それぞれのタイミングでperformedイベントを発火するようにしました。
以下、実装例です。
上記をTapAndMultiTapInteraction.csという名前でUnityプロジェクトに保存すると、Interactionが使用可能になります。
実装はInput SystemのInteractionのプリセットであるMultiTapInteractionクラスの内部実装を参考にしています。
参考:Class MultiTapInteraction| Input System | 1.7.0
Input Actionの設定
例で示したカスタムInteractionを登録したActionを一つ定義します。
例では、「TapAndMultiTap」という名前のActionを定義し、Bindingには「<Pointer>/press」なるパスを指定し、Interactionに実装例のTap And Multi Tapという名前のInteractionを登録するものとします。
Action Properties > Interactionsから各種パラメータを指定できます。
- Tap Time – ボタンを押し込む最大許容時間[s]
- Tap Delay – タップの最大時間間隔[s]
- Tap Count – マルチタップに必要な回数
- Press Point – ボタン押下判定の閾値(0でInput Systemのデフォルト設定値)
- Exclusive – シングル・マルチタップを排他的に反応させるかどうか
入力受取り側のスクリプトの実装例
カスタムInteractionを適用したActionからシングル・マルチタップを判定するためのスクリプトを実装します。
以下、実装例です。
上記をCustomInteractionExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターよりActionを指定してください。
実行結果
前述の例同様にシングル・マルチタップの操作でログ出力されます。
スクリプトの説明
Action Propertiesより指定可能な設定項目は、以下publicフィールドとして定義しています。
受取り側に通知する際、シングルタップ、マルチタップ、シングルタップのロールバックのどれかを判定可能にするため、enum型プロパティとして種別を提供するようにしています。
マルチタップ処理に必要な状態変数は、MultiTapInteractionの内部実装を参考に定義しています。
カスタムInteractionはそのままではInput System側から認識されないたえめ、初期化時に登録しています。
参考:Interactions | Input System | 1.7.0
デバイスから入力があった際のInteraction処理はProcessメソッドとして実装しています。
参考:Interface IInputInteraction| Input System | 1.7.0
Processメソッドの最初では、タップやマルチタップが中断したときの判定処理を行っています。
マルチタップが中断された時にシングルタップを排他的に通知する設定の場合は、シングルタップ通知してから一連のInteractionを終了します。
それ以外、例えばマルチタップの途中やタップを満たさない操作などで中断する場合は、そのままCanceledフェーズに遷移してInteractionを終了します。
参考:Struct InputInteractionContext| Input System | 1.7.0
処理のタイムアウト以外の場合は、入力に何らかの変化があったとみなし、タップ状態に基づいて処理を分岐します。
初期状態からボタン入力があった場合、タップ処理に移行します。タップ回数は必ず押された瞬間にカウントするようにしました。
ボタンが押された後、離されるまで待機する処理は以下部分です。
許容時間内にボタンが離されるとタップ処理をif文の内部で実行します。その際、排他フラグとタップ回数に応じてタップ種別(CurrentTapType)を指定した後にPerformedフェーズに遷移させ、performedイベントを通知しています。
ボタンを離した後、再度押下するまで待機する処理は以下部分です。
ここでタップ回数をカウントして、内部状態をボタンが離されるまで待機する状態に更新しています。
受取り側のスクリプトでは、コールバック処理内部でTapAndMultiTapInteractionインスタンスを取得して、タップ種別のプロパティCurrentTapTypeから処理を振り分けています。
複数のInteractionを共存させて実装する方法について
本記事では軽く触れる程度に留めますが、プリセットのInteractionであるTapとMulti Tapを1つのActionに定義して、スクリプトからInteractionを判別する方法もあります。
実装は手軽ですが、タップ後に押しっぱなしにするとシングルタップとして判定される挙動であったため、本記事では軽く触れる程度にとどめておきます。
これを許容できる場合は、全体的な実装量が大幅に減り、アプリケーション側のロジックもシンプルになって良いでしょう。
参考:(New Input System) Single Tap vs Double Tap – Unity Forum
参考:New Input System – Mouse Double Click – Unity Forum
さいごに
シングルタップとマルチタップをそれぞれ判別するためには、アプリケーション側の挙動を適切に設計する必要があります。
レスポンスを優先してシングルタップとマルチタップを両方通知する方法、レスポンスを犠牲にする代わりに排他的に通知する方法があります。
これらをInput Systemを用いて実装する場合として、2つのActionを用いたり、カスタムInteractionを用いて1つのActionにまとめて実装したりする方法を紹介させていただきました。