【Unity】マウスの位置の一番手前にUIがあるか判定する

C#

以前作ったマウス入力管理クラスの追加機能として作成した。UIかどうかの判定はもっといい方法がありそうだが、一旦うまくいったのでメモとして記事にしておく。

マウス入力管理クラスに関しては以下の記事で解説している。

コード

完成したコードがこちら。

public bool IsPointerOnUI(Vector2 screenPosition)
{
    if (EventSystem.current == null) { return false; }

    PointerEventData eventDataCurrent = new PointerEventData(EventSystem.current);
    eventDataCurrent.position = screenPosition;

    EventSystem.current.RaycastAll(eventDataCurrent, raycastResults);
    if(raycastResults.Count == 0){ return false; }

    bool result = raycastResults[0].gameObject.layer == 5;

    raycastResults.Clear();
    return result;
}

メソッドの外で、リストを作っておく必要がある。

private List<RaycastResult> raycastResults = new List<RaycastResult>();

これは、以下のページのコードにUIかどうかを判定する内容を加えたものである。

自分用Unityメモ:タップした場所がuGUI上か調べる - かさたな日記
タップやスワイプ、ドラッグをした時に何か動作をさせたい、 でも押した場所にボタンとかがあったら反応して欲しくない、 そんな感じの時に使うメソッド。 ※ちょっと修正。 そもそもEventSystemが置かれてないときの処理を追加しました。 u...

参考記事と同様に、引数にはマウスのスクリーン座標を渡す。以下のようにメソッドを呼べば、現在のマウスのスクリーン座標上にUIがあるかどうかが得られる。

IsPointerOnUGUI(Input.mousePosition);

また、このコードはUIのレイヤーがレイヤー番号が5の「UI」であることを前提にしている。そのため、レイヤーが変更されているUIが判定をすり抜けたり、レイヤーがUIになっているオブジェクトがUIと判定されてしまったりするので注意が必要である。

作った経緯

以前作ったマウス入力管理クラスではUIが検知されない。なぜなら、カメラから飛ばしたRayで当たり判定をしていたからである。Rayは衝突の判定にCollderを用いる。そのためColliderを持たないUIをRayでは検知することができないのである。

UIを検知しないRayは、手前にUIがあったとしても奥のオブジェクトの当たり判定を拾ってしまう。つまり、プレイヤーはUI越しにオブジェクトに触れてしまう。そのため、手前にUIがあることを検知するメソッドが必要になった。

中身の解説

EventSystem.RaycastAll

EventSystem.RaycastAll()を利用してマウスが触れているものをすべて取得する。

この関数は、イベントデータと結果を格納するリストを渡すことでリストに結果を入れてくれる。

EventSystems.EventSystem-RaycastAll - Unity スクリプトリファレンス
Raycast into the Scene using all configured BaseRaycasters.

格納用のリストは、メソッドの外で定義しておく。

private List<RaycastResult> raycastResults = new List<RaycastResult>();

現在のイベントシステムをEventSystem.currentで取得し、イベントデータを作成する。

PointerEventData eventDataCurrent = new PointerEventData(EventSystem.current);

イベントデータに座標を設定する。

eventDataCurrent.position = screenPosition;

用意した変数を引数として渡し、関数を呼び出す。

EventSystem.current.RaycastAll(eventDataCurrent, raycastResults);

UIかどうかの判定

UIかどうかの判定はレイヤーを用いて行っている。UIを作った場合、基本的にはレイヤーがUIになっていると思う。

レイヤーには番号が割り振られており、UIは5に固定されている。そのため、この番号を用いてUIかどうかを判定することにした。

ゲームオブジェクトが何番のレイヤーにあるかは、GameObject.layerで取得できる。

RaycastResultは検知したゲームオブジェクトを変数として持っているので、それを利用してゲームオブジェクトを取得し、そのレイヤーがUI(5)かどうかを調べる。

今回のコードでは、結果をbooleanの変数に格納している。

bool result = raycastResults[0].gameObject.layer == 5;

ちなみに、raycastResultsの最初の要素を取得しているのは、一番手前のゲームオブジェクトがUIかどうか調べるためである。raycastResultsにはカメラの手前から順に情報が格納されているようなので、配列の最初の要素が一番手前のRaycastResultとなる。

まとめ

今回は、EventSystem.RaycastAllとレイヤーを用いて、一番手前にUIがあるかどうかを検知するメソッドを作成した。ただ、ゲームオブジェクトがUIかどうかの判定にレイヤーを用いるのは少々、強引な方法な気もするのでもっといい方法がありそう。

あと、EventSystem.RaycastAllを何度も呼び出すのが、重い処理かどうかがわからない。直感で軽くはなさそうな気がする。しらんけど。

今後、もっといい方法が見つかったら改良していきたいとは思う。

参考

【Unity】Raycastで、imageを取得したい!!
こんばんは、ちきなるです。   マウスをクリックした場所に、imageがあるかチェックしたい!! (imageは、UIから追加できるやつです)   imageが有ったら、情報を取得して、 imageが無かったら、何もしない。   と、言うわ
自分用Unityメモ:タップした場所がuGUI上か調べる - かさたな日記
タップやスワイプ、ドラッグをした時に何か動作をさせたい、 でも押した場所にボタンとかがあったら反応して欲しくない、 そんな感じの時に使うメソッド。 ※ちょっと修正。 そもそもEventSystemが置かれてないときの処理を追加しました。 u...
EventSystems.EventSystem-RaycastAll - Unity スクリプトリファレンス
Raycast into the Scene using all configured BaseRaycasters.