Unity でマウスイベントを扱いやすくするクラスを実装する

f:id:shogonir:20190510010305p:plain

 

目次

  1. この記事の目的
  2. 概要
  3. マウスイベントを扱いやすくする
  4. 実際に使ってみる
  5. まとめ

 

1. この記事の目的

この記事ではUnityでマウスイベントを扱いやすくする方法を紹介します。

ことの発端は、自分でマウス操作を実装しようとした際に Input クラスを使ったのですが、
これが意外と使いにくいというか、基本的な機能しか提供していないことに気づきました。

「これは似たような実装をみんなやっているのでは」と思い、記事にすることにしました。

Input クラスではマウスボタンが「ダウンした瞬間」や「アップした瞬間」を知ることができますが、
普通やりたいのは「クリックされたら」とか「ドラッグされたら」というイベントを知ることです。

この記事ではクリックとドラッグを検知するクラスを実装したいと思います。

 

2. 概要

この記事で目指すものは下記の通りです。

  1. クリックやドラッグを検知するクラス(Watcherと呼ぶことにする)を実装する
  2. イベント通知を受け取るクラス(Handlerと呼ぶことにする)を実装する

クリックとドラッグは下記のように定義します。

イベント 定義
クリック マウスダウンから、ポインタが一定以上動かされずにマウスアップされた時
ドラッグ マウスダウンされた状態で、ポインタが一定以上動かされた時

3. マウスイベントを扱いやすくする

では早速 Watcher を実装しようと思います。

Watcher は常にマウスの動きを監視し、イベントを通知する必要があるため、 どうしても実装をもつ必要があります。 そのため、今回は Watcher を抽象クラスで実装することにしました。

実際のソースコードは以下の通りです。

using UnityEngine;

public abstract class MouseEventWatcher : MonoBehaviour
{
    /**
     * マウスボタンが押されている状態で、どの程度ポインタが動いたらドラッグとみなすか。
     * 単位はピクセルで、この値以上ポインタが動いた際にドラッグ判定となる。
     * コンストラクタでのみ実装クラスから設定可能になっている。    
     */
    private float dragDistanceThreshold;

    /**
     * 現在のフレームでマウスボタンが押されていればtrue, そうでない場合はfalse
     */
    private bool isMouseDown;

    /**
     * 現在ドラッグを行なっていると判定されていればtrue, そうでない場合はfalse   
     */   
    private bool isDragging;

    /**
     * ドラッグ中に、ドラッグされている距離を格納する。単位はピクセル。   
     */   
    private float dragDistance;

    /**
     * 1つ前のフレームのマウスの座標。   
     */   
    private Vector3 previousMousePosition;

    /**
     * 前回マウスボタンが押された時のマウス座標。   
     */   
    private Vector3 downedMousePosition;

    protected MouseEventWatcher(float dragDistanceThreshold)
    {
        this.dragDistanceThreshold = dragDistanceThreshold;
        this.isMouseDown = false;
        this.isDragging = false;
        this.dragDistance = 0.0f;
        this.previousMousePosition = Vector3.zero;
        this.downedMousePosition = Vector3.zero;
    }

    void Update()
    {
        this.WatcjMouseEvent();
    }

    private void WatchMouseEvent()
    {
        Vector3 mousePosition = Input.mousePosition;

        if (Input.GetMouseButtonDown(0))
        {
            this.isMouseDown = true;
            this.downedMousePosition = mousePosition;
            this.dragDistance = 0.0f;
        }

        if (this.isMouseDown)
        {
            this.dragDistance += (mousePosition - this.downedMousePosition).magnitude;
            if (this.dragDistance >= this.dragDistanceThreshold)
            {
                if (!this.isDragging)
                {
                    this.OnMouseDragStart(this.downedMousePosition, this.downedMousePosition, mousePosition);
                    this.isDragging = true;
                }
                this.OnMouseDragging(this.downedMousePosition, this.previousMousePosition, mousePosition);
            }
        }

        if (Input.GetMouseButtonUp(0))
        {
            if (this.isDragging)
            {
                this.OnMouseDragEnd(this.downedMousePosition, this.previousMousePosition, mousePosition);
                this.isDragging = false;
            }
            else
            {
                this.OnMouseClick(mousePosition);
            }
            this.isMouseDown = false;
        }

        this.previousMousePosition = mousePosition;
    }

    abstract protected void OnMouseClick(Vector3 mousePosition);

    abstract protected void OnMouseDragging(Vector3 dragStartPosition, Vector3 previousMousePosition, Vector3 mousePosition);

    abstract protected void OnMouseDragStart(Vector3 dragStartPosition, Vector3 previousMousePosition, Vector3 mousePosition);

    abstract protected void OnMouseDragEnd(Vector3 dragStartPosition, Vector3 previousMousePosition, Vector3 mousePosition);
}

各フィールドの意味はコメントに書いている通りとなります。 定義通りにクリックとドラッグを判定するために必要なものたちです。

実装をもつメソッドは WatchMouseEvent() のみで、毎 Update() 時に実行されます。 WatchMouseEvent() では定義通りにクリックやドラッグ状態であるかを判定します。

「クリックされた」や「ドラッグ中だ」などと判定されると、
一番下に定義されている4つの抽象メソッドのうち該当するものが実行されます。

このWatcherクラスを使うときは、HandlerクラスでWatcherクラスを継承し、 抽象メソッドを実装すればいいということです。

 

4. 実際に使ってみる

では実際に Watcher を使用して Handler クラスを書いてみようと思います。
今回は Watcher が正しく実装できているか確認できればいいので、
各イベントが発火した際に標準出力を行うだけにしたいと思います。

ソースコードは以下の通りです。

using UnityEngine;

public class MouseEventHandler : MouseEventWatcher
{
    private const float DragDistanceThreshold = 4.0f;

    public MouseEventHandler(): base(DragDistanceThreshold)
    {
    }

    override protected void OnMouseClick(Vector3 mousePosition)
    {
        Debug.Log("clicked: " + mousePosition.ToString());
    }

    override protected void OnMouseDragging(Vector3 dragStartPosition, Vector3 previousMousePosition, Vector3 mousePosition)
    {
        Debug.Log("dragging: " + previousMousePosition.ToString() + " -> " + mousePosition.ToString());
    }

    override protected void OnMouseDragStart(Vector3 dragStartPosition, Vector3 previousMousePosition, Vector3 mousePosition)
    {
        Debug.Log("drag start: " + dragStartPosition.ToString() + " -> " + mousePosition.ToString());
    }

    override protected void OnMouseDragEnd(Vector3 dragStartPosition, Vector3 previousMousePosition, Vector3 mousePosition)
    {
        Debug.Log("drag end: " + dragStartPosition.ToString() + " -> " + mousePosition.ToString());
    }
}

実際に適当な GameObject にアタッチして実行してみると、
正しくクリックとドラッグのイベントを扱えていることがわかります。

f:id:shogonir:20190510005119p:plain

 

5. まとめ

さて、いかがでしたでしょうか。

今回はクリックとドラッグのマウスイベントを簡単に扱えるクラスを実装しました。 今回の実装のいい点と悪い点をまとめます。

 

  • いい点
    • Watcherクラスを継承するだけでマウスイベントが扱いやすくなる
    • マウスの監視(Watcher)とイベントハンドリング(Handler)を分割できる
  • 悪い点
    • ダブルクリックや右クリックなど、他にも対応すべきマウスイベントはまだまだある
    • Watcherクラスを抽象クラスで実装したため、Handlerクラスは他のクラスを継承できない

 

ということで、また時間を見つけてダブルクリックや右クリックにも対応したいと思います。 その他にも気になった点などございましたらコメント等いただけると助かります。