マウスカーソルがクリックされたら、その位置を取得するようなコードを書くにはどうすればいいの?
いくつか方法はあるけど、クリックされた瞬間などにコールバック側からマウス位置を取得する処理を書けば良いわ。
Input Systemでマウスカーソルのクリック位置や、画面タップされた位置などを取得する方法の解説記事です。
方法は一通りではありませんが、次の方法があります。
- クリックされた瞬間にポインタ位置を取得する
- カスタムComposie Bindingを実装する
1つ目の方法は、Input Systemを拡張せずに手軽に実現できる基本的な方法ですが、いくつか注意点もあります。
2つ目の方法は、Input Systemを拡張する必要がありますが、受け取り側のコードがシンプルになるメリットがあります。
いずれもタップやマルチタップ、長押しなどの操作にも対応しています。
本記事では、上記の2種類の方法でクリックまたはタップされた位置を取得する方法を解説していきます。
本記事では、「ポインタの位置」という言葉が出てきますが、マウスカーソルの位置やタッチパネルでタッチされた指の位置などを総称したものを意味します。
- Unity 2023.2.0f1
- Input System 1.7.0
目次 非表示
前提条件
事前にInput Systemがインストールされ、有効化されているものとします。
また、本記事のサンプルではInput Actionを使用します。
ここまでの基本的な解説は以下記事をご覧ください。
クリックされた瞬間にポインタ位置を取得する
クリックまたはタップされたときに、コールバックからポインタの位置を受け取る方法です。
ポインタの押下状態を受け取るActionを1つ用意し、クリックまたはタップされたときにポインタ位置を取得するコードを書くことで、現在のポインタ位置を取得できます。
この方法には、次のメリットとデメリットがあります。
- メリット
- Input System側の機能を拡張せずに実現できる
- Updateイベント内での受け取りでも動作する
- デメリット
- 受取側のコードがやや複雑になる
- コールバック内でポインタ位置をAction経由で受け取ったとき、フォーカス復帰時に正しく位置を取得できないことがある
最後のデメリットの理由については後述します。
Actionの設定
マウス左ボタンや指の押下状態などのボタン入力を受け取るActionを定義します。
Actionを次のように設定します。
- Action Type – Button
- Binding – <Pointer>/press
Actionの設定方法の基本については、以下で解説しています。
MouseやTouchscreenではなくPointerを指定すると、マウスやタッチディスプレイにまとめて対応できるようになります。
Pointerはこれらを抽象化したデバイスであるためです。
また、必要に応じてInteractionを設定します。
次のようなInteractionが設定できます。
- Press – 押した瞬間、離した瞬間、その両方を検知する
- Hold – 長押しされた瞬間を検知する
- Tap – ボタンを押してすぐに離した瞬間を検知する
- Multi Tap – 指定回数素早くタップされた瞬間を検知する
- Slow Tap – ボタンをゆっくり押して離した瞬間を検知する
参考:Interactions | Input System | 1.7.0
サンプルスクリプト
以下、指定されたActionがトリガー された瞬間にポインタの位置をログ出力する例です。
上記をPressPositionExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターより押し込み入力のActionを設定してください。
本記事では、前述のInput Action Assetで定義したActionを指定することとします。
実行結果
ゲーム画面をクリックすると、コンソールログにカーソルのスクリーン座標が出力されます。
画面外をクリック・タップしたときは反応しません。
スクリプトの説明
ボタンの押下状態を取得するActionは、以下のようにInputActionProperty型フィールドとして定義しています。
InputAction型やInputActionReference型でも良いですが、InputActionProperty型とすることで両者に対応できます。
参考:Struct InputActionProperty| Input System | 1.7.0
コールバックの登録はAwake、解除はDestroyのタイミングで行っています。
このままではActionが有効にならず入力を受け取れないため、InputAction.Enableメソッドで有効化する必要があります。
本記事では、コンポーネントの有効化・無効化のタイミングでActionの有効化・無効化を制御しています。
ボタン入力を受け取るコールバック内の処理では、Pointerクラスに直接アクセスして入力を受け取っています。
Pointer.currentプロパティを取得し、これがnullでなければデバイスが存在しているので、positionプロパティのReadValueメソッドからクリック位置を取得しています。
参考:Class Pointer| Input System | 1.7.0
位置をActionで受け取る場合の問題点
ポインタ位置をPointerクラスではなくAction経由で受け取ることも可能です。
しかし、この方法には次の問題があります。
- コールバックから別のActionを呼び出す場合、コールバック順序によって入力値が変わることがある
- 特にフォーカス復帰時では、順序の問題によりポインタ位置が(0, 0)となることがある
以下は、その問題のあるコードです。
このスクリプトでは、次のようにフォーカス復帰した瞬間だけカーソル位置が(0, 0)になります。
特に、Input System Package > Background Behaviour項目がIgnore Focusになっていないと、フォーカスを失った瞬間にデバイスが無効化されるため、復帰した瞬間にポインタ位置を返すActionが先に動作しないとこのような現象が発生してしまいます。
Input Actionは内部的にはInteractionと呼ばれる一種のステートマシンを持っています。入力が無い場合はWaiting、入力され始めにStarted、特定入力があったときなど(ボタンが一定の深さ以上押し込まれる等)にPerformed、入力が終わるとCanceledの状態(フェーズ)に遷移します。
また、Input Actionが無効化された場合はDisabled状態になります。
このうち、InputAction.ReadValueメソッドが(0, 0)、すなわち初期値以外の値を返すためにはInteractionのフェーズがStartedまたはPerformedになっている必要があります。
カスタムComposie Bindingを実装する
カスタムComposite Bindingを実装してクリックされたときにポインタ位置を取得することも可能です。
これは、ボタンが押されたときにポインタ位置を返す独自のComposite Bindingを自作して適用することで解決します。
クリックされたときなどにポインタ位置を返す挙動ですが、Composite Bindingが返す値は常にPosition(ポインタ位置)、返す大きさは常にPressの大きさとします。
このようにすることで、ボタンの離された瞬間に反応するInteractionでもポインタ位置が受け取れるようになります。
似たようなComposite BindingにOne Modifierがありますが、こちらは前述のPositionに対する大きさを返す挙動になっているので、離された瞬間にトリガーするInteractionでは座標(0, 0)を返す挙動になってしまいます。
また、スクリーン座標の原点(0, 0)をクリック・タップしたときも入力の大きさが0であるため、入力なしと判断されてしまいます。
これを回避するために、本記事ではボタンを離した時でも(0, 0)ではなく本来のポインタ座標を返すComposite Bindingを実装する方法を提案しています。
この方法には、次のメリットとデメリットがあります。
- メリット
- 受け取り側のコードをシンプルにできる
- デバイス依存しないコードが書ける
- フォーカス復帰処理にも対応可能
- デメリット
- Composite Bindingを独自実装する必要がある
- Updateイベント内などでは使用できない(コールバック限定)
したがって、コールバック内限定でクリック位置を取得したいケースでは、こちらの方法の方がより適切と言えるでしょう。
カスタムComposite Bindingの実装例
以下、指定されたボタン入力があったときにポインタ位置を返すComposite Bindingの実装例です。
ボタン入力とポインタ位置はそれぞれActionとして指定可能になっています。
上記をPressPositionComposite.csという名前でUnityプロジェクトに保存すると、カスタムComposite Bindingとして使用可能になります。
適用方法
クリックやタップされたらポインタの位置を取得するようにActionを設定します。
本記事では、新しくポインタ位置を返すActionを定義するものとします。Action TypeはValue、Control TypeはVector 2とします。
次に、どの瞬間に反応(トリガー)するかをInteractionsから指定します。例では、押してすぐ離す操作を表す「Tap」を指定するものとします。この辺はお好みで設定してください。
次に、前述の自作のComposite BindingをActionに追加します。
Action作成時に自動作成されるBindingを削除し、Action右の「+」アイコンからAdd Press Position Composite を選択し、カスタムComposite Bindingを追加します。
そして、Pressにポインタの押し込み入力などのBinding、Positionにポインタ位置などのBindingをそれぞれ指定します。
取得側のスクリプトの例(必要ならば)
前述のCompoisite Bindingを適用したActionの入力を受け取る処理を実装します。本記事では、次の新しいスクリプトで入力を受け取るものとします。
上記をCompositeExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチし、インスペクターより前述のComposite Bindingを適用したActionを指定します。
実行結果
フォーカス復帰した瞬間も正しくクリック位置が取得できていることが確認できます。
スクリプトの説明
ボタンが押されたときだけポインタ位置を返すカスタムComposite Bindingは、次のように実装しています。
InputBindingComposite<T>クラスを継承する形で行います。
参考:Class InputBindingComposite
Composite Bindingでは、ボタン入力とポインタ位置の2つのBindingを入力とするため、次のようにint型フィールドを定義しています。
フィールドの型はint型ですが、[InputControl]属性を付加しています。これによってBindingとして識別可能になります。
例では、layoutプロパティでBindingの入力型を制限しています。
参考:Class InputControl| Input System | 1.7.0
Composite Bindingが出力する値は、ポインタ位置そのものです。
また、Composite Bindingが返す入力値の大きさは、次のようにボタン入力の大きさをそのまま返すようにしています。
さいごに
マウスがクリックされたときの位置を取得するには、コールバックなどからカーソルや指の位置を取得する必要がありますが、2つのActionを使用する場合は注意が必要です。
コールバック内から別の位置取得用Actionを参照しても実現はできますが、フォーカス復帰時にActionが有効になっていない可能性があり、原点位置(0, 0)を返してしまう場合があります。
これを回避するためには、コールバック内ではPointer.current.positionなどのプロパティに直接アクセスするか、カスタムComposite Bindingを実装して適用するのが安全です。