[Unity] マウスとタッチを同じように扱いDown/Up/Drag/Flickに対応する

Androidでタップやドラッグを行う場合にはInput.touchesを使用しますが、開発中はPC+マウスの環境でも実行したいのが初心者の心情です。両方の環境でマウス/タッチを同じように扱いたい。

そんなわけでマルチタッチを必要とせず、タッチは1点で十分だよ!でもマウスにも対応したいよ!という自分が必要な機能を実装してみた結果をまとめたいと思います。

今回も実装方法が正しいかどうかわかりませんが、公式ドキュメントを見たり、Google先生に聞いてみたりなるべく「右へならえ」になるようにしたつもりです。

 

 

 

実装する機能

  • Android(タッチパネル)とPC環境(マウス)で同じように動作すること
  • Down/Up/Drag/Flickの4種類が判定できること

としました。

 

参考にしたサイトはこちら

Unity2D フリック操作
http://stickpan.hatenablog.com/entry/2014/01/23/193922

 

実装方法

いきなりですがクラス、インターフェースを定義します。

 

GestureInfoクラス

入力情報を扱います。

using UnityEngine;
using System.Collections;

/// <summary>
/// ジェスチャーパラメータ
/// </summary>
public class GestureInfo
{
  /// <summary>
  /// mouse/touch位置を取得します
  /// </summary>
  /// <value>The position.</value>
  public Vector3 ScreenPosition {
    get;
    set;
  }

  /// <summary>
  /// 前回からの移動量を取得します
  /// </summary>
  /// <value>The delta position.</value>
  public Vector3 DeltaPosition {
    get;
    set;
  }

  /// <summary>
  /// mouse/touch downステータスを取得します
  /// </summary>
  /// <value>押された時にtrueになります</value>
  public bool IsDown {
    get;
    set;
  }

  /// <summary>
  /// mouse/touch upステータスを取得します
  /// </summary>
  /// <value>離れた時にtrueになります</value>
  public bool IsUp {
    get;
    set;
  }

  /// <summary>
  /// mouse/touch dragステータスを取得します
  /// </summary>
  /// <value>ドラッグ移動した時にtrueになります</value>
  public bool IsDrag {
    get;
    set;
  }

  /// <summary>
  /// 経過時間を取得します
  /// </summary>
  /// <value></value>
  public double DeltaTime {
    get;
    set;
  }

  /// <summary>
  /// 経過時間で移動した距離を取得します
  /// </summary>
  /// <value></value>
  public Vector3 DragDistance {
    get;
    set;
  }
}

 

InputGestureインターフェース

通知受け取り用のメソッドを定義しています。これを継承してごにょごにょします。

using UnityEngine;
using System.Collections;

/// <summary>
/// Input gesture.
/// </summary>
public interface InputGesture 
{
  /// <summary>
  /// ジェスチャーの処理順番番号
  /// </summary>
  /// <value>0が一番速い、数値が大きくなると判定順番が遅くなる</value>
  int Order {
    get;
  }

  /// <summary>
  /// 指定ジェスチャーを処理する必要があるかどうかを取得します
  /// </summary>
  /// <returns>処理する必要があるならtrueを返す</returns>
  /// <param name="info">Info.</param>
  bool IsGestureProcess( GestureInfo info );

  /// <summary>
  /// Down時に呼び出されます
  /// </summary>
  /// <param name="info">Info.</param>
  void OnGestureDown( GestureInfo info );

  /// <summary>
  /// Up時に呼び出されます
  /// </summary>
  /// <param name="info">Info.</param>
  void OnGestureUp( GestureInfo info );

  /// <summary>
  /// Drag時に呼び出されます
  /// </summary>
  /// <param name="info">Info.</param>
  void OnGestureDrag( GestureInfo info );

  /// <summary>
  /// Flick時に呼び出されます
  /// </summary>
  /// <param name="info">Info.</param>
  void OnGestureFlick( GestureInfo info );
}

 

タッチ入力かマウス入力かの判定

判定方法はOSで分岐させればよいのではないでしょうか。

  /// <summary>
  /// タッチパネル向けのプラットフォームかどうか取得します
  /// </summary>
  /// <returns>Android/iOSの場合にtrueを返します</returns>
  bool IsTouchPlatform()
  {
    if (Application.platform == RuntimePlatform.Android || Application.platform == RuntimePlatform.IPhonePlayer) {
      return true;
    }
    return false;
  }

 

位置、押された、離れたなどの判定

タッチ用とマウス用の2つを用意して違いを吸収します。今回はマルチタッチ無しの1点のみなので簡単そうです。

まずはタッチ情報があるかどうか、かつ前回と同じ指かどうかを判断しタッチ情報を取り出します。マルチタッチして誤動作しないように注意しました。

  /// <summary>
  /// タッチされたタッチ情報を取得します
  /// </summary>
  /// <returns>タッチ情報を返します。何もタッチされていなければnullを返します</returns>
  Touch? GetTouch()
  {
    // 前回と同じタッチを追跡します
    // 新しいタッチの場合は最初のタッチを使用します
    if (Input.touchCount <= 0) {
      return null;
    }
    for (int n=0; n<Input.touchCount; n++) {
      if( this.TouchId == Input.touches[n].fingerId ) {
        return Input.touches[n];    // 前回と同じタッチ
      }
    }
    // 新規タッチ(タッチ開始時のみ)
    if (Input.touches [0].phase == TouchPhase.Began) {
      this.TouchId = Input.touches [0].fingerId;
      return Input.touches [0];
    }
    return null;
  }
タッチ用の処理

GestureInfoにデータを入れてタッチ、マウス両方で扱えるようにしています。

  /// <summary>
  /// タッチ入力情報をGestureInfoへ変換します
  /// </summary>
  /// <returns>入力情報があればtrueを返します</returns>
  /// <param name="info"></param>
  bool InputForTouch( ref GestureInfo info )
  {
    // 基本的にタッチは1点のみ検出するインターフェースとする
    Touch? touch = GetTouch ();
    if (!touch.HasValue) {
      return false;
    }

    // 2015.02.03 update begin
    //info.ScreenPosition = touch.GetValueOrDefault().position;
    //info.DeltaPosition = touch.GetValueOrDefault().deltaPosition;
    var saveOldPosition = info.ScreenPosition;
    info.ScreenPosition = touch.GetValueOrDefault().position;

    // どうもdeltaPositionの値が実機だと正しく取れない
    // 前回のScreenPositionからDeltaPositionを求めるように変更する
    // info.DeltaPosition = touch.GetValueOrDefault().deltaPosition;
    if (touch.GetValueOrDefault ().phase != TouchPhase.Began) {
      info.DeltaPosition = info.ScreenPosition - saveOldPosition;
    } else {
      info.DeltaPosition = new Vector3();
    }
    // 2015.02.03 update end

    switch (touch.GetValueOrDefault().phase) {
    case TouchPhase.Began:
      info.IsDown = true;
      break;
    case TouchPhase.Moved:
    case TouchPhase.Stationary:
      info.IsDrag = true;
      break;
    case TouchPhase.Ended:
    case TouchPhase.Canceled:
      info.IsUp = true;
      this.TouchId = -1;      // タッチ終了
      break;
    }
    return true;
  }
2015.02.03 追記

手持ちのAndroid機(Memopad7 ME176)で試したところdeltaPositionの値がピクセル単位ではない模様。
ピクセル単位を想定していたので前回の位置と今回の位置の差をdeltaPositionとするように変更しました。

 マウス用の処理

左マウスボタンの処理しています。

  /// <summary>
  /// マウス入力情報をGestureInfoへ変換します
  /// </summary>
  /// <returns>入力情報があればtrueを返します</returns>
  /// <param name="info"></param>
  bool InputForMouse( ref GestureInfo info )
  {
    // マウス用の処理
    if (Input.GetMouseButtonDown(0) ) {
      info.IsDown = true;
      info.DeltaPosition = new Vector3();
      info.ScreenPosition = Input.mousePosition;
    }
    if (Input.GetMouseButtonUp(0)) {
      info.IsUp = true;
      info.DeltaPosition = Input.mousePosition - info.ScreenPosition;
      info.ScreenPosition = Input.mousePosition;
    }
    if( Input.GetMouseButton(0) ) {
      info.IsDrag = true;
      info.DeltaPosition = Input.mousePosition - info.ScreenPosition;
      info.ScreenPosition = Input.mousePosition;
    }
    return true;
  }

入力チェック処理

上の関数を使用すると

  • Downした
  • Dragした
  • Upした

の3つの状態を検出できます。

まずはDown、Drag、Upへの処理を記述します。

  /// <summary>
  /// 
  /// </summary>
  void Update () 
  {
    this._gesture_info.IsDown = false;
    this._gesture_info.IsUp = false;
    this._gesture_info.IsDrag = false;

    // 入力チェック
    var isInput = IsTouchPlatform() ? InputForTouch( ref this._gesture_info ) : InputForMouse( ref this._gesture_info );
    if (!isInput) {
      return;   // 入力無し
    }

    // 各種移動
    if (this._gesture_info.IsDown) {
      DoDown (this._gesture_info);
    }
    if (this._gesture_info.IsDrag) {
      DoDrag (this._gesture_info);
    }
    if (this._gesture_info.IsUp) {
      DoUp (this._gesture_info);
    }
  }

ここまででDown、Drag、Upの3つの状態を検出し、処理できそうです。残りはFlick処理のみ。

 

Flickを発生させる

常に20フレーム分(20/60秒=333msと仮定しました)の位置を保存しておきます。(保持するフレーム数は要調整。)

  1. 長めの期間内のベクトル = フレーム[0(一番古い位置)]からフレーム[19(一番新しい位置)]へのベクトル
  2. 短めの期間内のベクトル = フレーム[15(少し前の位置)]からフレーム[19(一番新しい位置)]へのベクトル

の2つのベクトルを用意します。

2つのベクトルがほぼ同一方向を向いていれば「Flick発生」としました。これは素早く「上移動→下移動→離す」が行われた時の対処です。

(この処理はUp処理の中にあり、指やマウスが離されたときに一度だけ判定します)

    // Flick判定
    var v1 = GetTraceVector ( 0, 0 );
    var v2 = GetTraceVector ( TRACE_QUE_COUNT - 5, 0 );
    var dot = Vector3.Dot( v1.normalized, v2.normalized );
    if (dot > 0.9) {
      // Flick発生
      this.ProcessingGesture.OnGestureFlick (info);
    }

出来れば動いた距離も判定に加えた方がよさそうです。距離もピクセル単位ではなくcm/インチ単位で行うとどんな環境でもほぼ同じ条件でFlickを発生させることができるはず・・・。まだ試したことないのでわかりませんが、今後調べて実装すると思います。

 

カメラ移動サンプル

Flick操作でぬるっと動くカメラ移動サンプルを作成してみました。もちろんAndroid版でも同じように動いています。

使用Assetは「Animated Knight and Slime Monster

マウスの左ボタンでドラッグorフリックすれば、スーっとカメラが移動します。

(※ボタンを押したときでもカメラが移動してしまいます。)

 

説明に使用したソース

作成した4つのソースは こちら になります。

 

まとめ

Flick発生条件のみが特殊で、残りは通常のマウスと同じ処理になりました。Flick/Swipeのジェスチャーを何気なく使っていましたが、いざ作ってみると難しいですね。Flick/Swipeの動作がうまくいかないと「このアプリなんか使いづらいなぁ」と悪い印象を持たれてしまうので今後も調整していきたいですね。

参考にしようとタッチ入力対応のAssetを探したが1つしか見つけられなかった。アプリ毎に入力処理が全く異なるから、毎回作るアプリに合わせて実装したほうが楽 or 低コストということなんだろうか。


希木小鳥

Diablo1でハクスラの世界に。今はBorderlands2をプレイ中。ぬるゲーマー。

あわせて読みたい