【Unity】スクリプトでフリック判定を実装する

はじめに

フリックした時にキャラを動かすといった当たり前のことをやろうとした時に、

InputManagerだとできないということを知って絶望しました。

なので自前でフリック判定を作ってみましたので諸々共有していきます。

フリックとはなんなのか?技術的な視点で考える

フリックは 画面にタッチ → 指を高速でスライド → 離す の一連の操作ですよね。

これだけだと同じ位置でタッチして離してもフリックになっちゃうので、
押し離し時の距離に閾値を設けることでフリックとタップの判断にできると思います。

上記を考慮し以下のような手順でプログラムを作ればフリック判定を作れるはず!

  • 画面をタッチした(開始)位置を取得
  • 画面から指を離した(終了)位置を取得
  • 開始と終了の位置の差を算出
  • 閾値を超えてたらフリックとする
  • フリック方向を決める

スクリプト実装

ここからはプログラムの実装について解説していきます。

画面をタッチした位置を取得する

画面にタッチしたかを判定するにはInput.GetMouseButtonDown(0)を使います。

戻り値がtrueならタッチ(開始)したことになります。

位置はInput.mousePositionで取得できます。

上記を踏まえたプログラムはこんな感じで実装します。

 Vector3 _clickStartPosition = Vector2.zero;

 void Update()
{
    if (Input.GetMouseButtonDown(0))
    {
         // 差を計算するために位置を退避
        _clickStartPosition = Input.mousePosition;
    }
}

Mouse(マウス)という単語があるのでPC限定でスマホに使えないように思えますが、スマホでのタッチも反応します。

画面から指を離した位置を取得

画面から指を離したしたかを判定するにはInput.GetMouseButtonUp(0)を使います。

使い方は先ほどと一緒ですね。

開始と終了の位置の差を算出

指を離した時に押したときと離した時の差を取ります。

Vector3 dif = Input.mousePosition - _clickStartPosition;

閾値でフリック判定する

タッチ座標の差を閾値を超えてるかをチェックする処理を作ります。

差は負(マイナス)の可能性があるので絶対値(Abs)にしてから閾値チェックをします。

float abs_x = Mathf.Abs(dif.x);
float abs_y = Mathf.Abs(dif.y);

if (abs_x >= _threshold || abs_y >= _threshold)
{
  // フリックとみなす 
}

フリック方向判定

フリック判定ができたら左右上下どちらにフリックがされたかを判定します。

まず最初は左右か上下のどちらかを絞ります(仕様的に両方はないと思ってる)。

差が大きい方をとることにします。

// 横方向
if (abs_x > abs_y)
{
}
// 縦方向
else
{
}

横方向であれば差がプラスだと右、マイナスだと左
縦方向であれば差がプラスだと上、マイナスだと下

という風にすれば4方向に分別することができました。

// 横方向
if (abs_x > abs_y)
{
    Flick(dif.x > 0 ? FlickDirection.Right : FlickDirection.Left);
}
// 縦方向
else
{
    Flick(dif.y > 0 ? FlickDirection.Up : FlickDirection.Down);
}

~~~~~~~省略~~~~~~~~

public enum FlickDirection
{
        Left,
        Right,
        Up,
        Down,
}

public void Flick(FlickDirection dir)
{
    Debug.Log($"Flick={dir}"); 
}

あとはthreshold(閾値)をお好みで調整すれば完成です。

コード全文

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Hoge : MonoBehaviour
{
    Vector3 _clickStartPosition = Vector2.zero;

    [SerializeField] float _threshold = 30;

    public enum FlickDirection
    {
        Left,
        Right,
        Up,
        Down,
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            _clickStartPosition = Input.mousePosition;
        }
        else if (Input.GetMouseButtonUp(0))
        {
            Vector3 dif = Input.mousePosition - _clickStartPosition;

            Debug.Log($"Flick: x={dif.x} y={dif.y}");

            float abs_x = Mathf.Abs(dif.x);
            float abs_y = Mathf.Abs(dif.y);

            if (abs_x >= _threshold || abs_y >= _threshold)
            {
                // 横方向
                if (abs_x > abs_y)
                {
                    Flick(dif.x > 0 ? FlickDirection.Right : FlickDirection.Left);
                }
                // 縦方向
                else
                {
                    Flick(dif.y > 0 ? FlickDirection.Up : FlickDirection.Down);
                }
            }
        }

    }

    public void Flick(FlickDirection dir)
    {
        // ここでフリックされた方向に応じて色々して!
    }

}

あとがき

一応iOSのシミュレータでも確認しましたが問題なく動作していました。

ただし解像度の違い操作感が変わる気がするので、そこは課題ですね。