【Unity】フリックとスワイプ入力の同時取得方法【C#】

Unityでフリックとスワイプ入力の同時取得方法
スポンサーリンク

Unityフリック入力とスワイプ入力の判定を行い、現状の入力情報を取得する方法について解説します。また、PC操作時は自動でマウス入力を取得するようにしている方法なので、アプリ制作の時はわりと便利なものだと思います。

こんな人におすすめ

・Unityでアプリを制作している
・フリックとスワイプの入力イベントを取得したい
・PC実行時も操作を行いたい

フリックとスワイプの取得タイミング

今回のサンプルにおけるフリックとスワイプの定義及び、それぞれの情報取得タイミングについてです。

フリックについては「ユーザーが画面をタッチし、上下左右いずれかの方向に指を動かした後、画面を離した瞬間」に方向判定を行っています。動きが一定以下の場合はただのタップとして入力を処理しています。

これにより画面を弾くような動作(フリック)を判定しています。

またスワイプについては「ユーザーが画面をタッチし、上下左右いずれかの方向に指を一定以上動かした時」に方向判定を行っています。こちらも一定以下の動きの場合はただのタップとして入力を処理します。

こちらは、画面上に触れた状態で指を動かす(スワイプ)ではあるものの、動きとしては開始位置を起点とした動作になるので、仮想アナログパッドのような入力判定に近くなります。使う際は少し工夫が必要です。

とりあえず、サンプルを置いているので、実装後の動きを見て判断してください!

Unityでスワイプとフリック入力の取得(動きサンプル)

テストプロジェクトを作る

実際に入力を確認するために、テストプロジェクトを作りましょう! テストプロジェクトは3Dでも2Dでも大丈夫です。

テストプロジェクト

テストプロジェクトを作ったら画面上のメニューから「File → Build Settings」を選び、iOSかAndroidにプラットフォームを変更します。変更が終わったら、同じく画面上のメニューから「Edit → Project Settings」を開き、QualityのVSync Countを「Don’t Sync」にしておきます。

Project Settings

この設定はFPSをソース上から制御するために必要な設定です。

設定が終わったら、動きを確認するためのオブジェクトをいくつか作ります。今回は以下のオブジェクトが必要です。

・空のオブジェクト:1
入力処理を割り当てるため

・テキストオブジェクト:1
入力状況を見るため

・Cubeオブジェクト:2
入力に従って動かすため

それぞれ画面上のメニューにある「GameObject」から作ることができます。空のオブジェクトについては「Create Empty」で作ります。

ここまでできたら次はサンプルコードをプロジェクト内に作ります。

サンプルコード

今回のサンプルはメインの動きを判定する「ScreenInput」と、サンプルシーンで入力を確認するための「TestText」です。

UnityのAssetフォルダ直下でも大丈夫なので、それぞれの名前でC#のスクリプトを作ってください。※名前を変更するとサンプルが動かない場合がありますのでご注意ください。

また、スクリプトの新規作成方法がわからない場合は以下のサンプルを参考に作ってみてください! なおフォルダ内で右クリックををすると新規作成メニューは開きます。

Unityスクリプトの新規作成手順

新規作成が完了したら、それぞれに以下のコードをコピペしてください!

■ScreenInput

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

public class ScreenInput : MonoBehaviour
{
    // フリック最小移動距離
    [SerializeField]
    private Vector2 FlickMinRange = new Vector2(30.0f,30.0f);
    // スワイプ最小移動距離
    [SerializeField]
    private Vector2 SwipeMinRange = new Vector2(50.0f,50.0f);
    // TAPをNONEに戻すまでのカウント
    [SerializeField]
    private int NoneCountMax = 2;
    private int NoneCountNow = 0;
    // スワイプ入力距離
    private Vector2 SwipeRange;
    // 入力方向記録用
    private Vector2 InputSTART;
    private Vector2 InputMOVE;
    private Vector2 InputEND;
    // フリックの方向
    public enum FlickDirection
    {
        NONE,
        TAP,
        UP,
        RIGHT,
        DOWN,
        LEFT,
    }
    private FlickDirection NowFlick = FlickDirection.NONE;
    // スワイプの方向
    public enum SwipeDirection
    {
        NONE,
        TAP,
        UP,
        RIGHT,
        DOWN,
        LEFT,
    }
    private SwipeDirection NowSwipe = SwipeDirection.NONE;


    // Update is called once per frame
    void Update()
    {
        GetInputVector();
    }

    // 入力の取得
    private void GetInputVector()
    {
        // Unity上での操作取得
        if (Application.isEditor)
        {
            if (Input.GetMouseButtonDown(0))
            {
                InputSTART = Input.mousePosition;
            }
            else if (Input.GetMouseButton(0))
            {
                InputMOVE = Input.mousePosition;
                SwipeCLC();
            }
            else if (Input.GetMouseButtonUp(0))
            {
                InputEND = Input.mousePosition;
                FlickCLC();
            }
            else if(NowFlick != FlickDirection.NONE || NowSwipe != SwipeDirection.NONE)
            {
                ResetParameter();
            }
        }
        // 端末上での操作取得
        else
        {
            if (Input.touchCount > 0)
            {
                Touch touch = Input.touches[0];
                if (touch.phase == TouchPhase.Began)
                {
                    InputSTART = touch.position;
                }
                else if (touch.phase == TouchPhase.Moved)
                {
                    InputMOVE = Input.mousePosition;
                    SwipeCLC();
                }
                else if (touch.phase == TouchPhase.Ended)
                {
                    InputEND = touch.position;
                    FlickCLC();
                }
            }
            else if (NowFlick != FlickDirection.NONE || NowSwipe != SwipeDirection.NONE)
            {
                ResetParameter();
            }
        }
    }

    // 入力内容からフリック方向を計算
    private void FlickCLC()
    {
        Vector2 _work = new Vector2((new Vector3(InputEND.x, 0, 0) - new Vector3(InputSTART.x, 0, 0)).magnitude, (new Vector3(0, InputEND.y, 0) - new Vector3(0, InputSTART.y, 0)).magnitude);

        if (_work.x <= FlickMinRange.x && _work.y <= FlickMinRange.y)
        {
            NowFlick = FlickDirection.TAP;
        }
        else if (_work.x > _work.y)
        {
            float _x = Mathf.Sign(InputEND.x - InputSTART.x);
            if (_x > 0) NowFlick = FlickDirection.RIGHT;
            else if (_x < 0) NowFlick = FlickDirection.LEFT;
        }
        else
        {
            float _y = Mathf.Sign(InputEND.y - InputSTART.y);
            if (_y > 0) NowFlick = FlickDirection.UP;
            else if (_y < 0) NowFlick = FlickDirection.DOWN;
        }
    }

    // 入力内容からスワイプ方向を計算
    private void SwipeCLC()
    {
        SwipeRange = new Vector2((new Vector3(InputMOVE.x, 0, 0) - new Vector3(InputSTART.x, 0, 0)).magnitude, (new Vector3(0, InputMOVE.y, 0) - new Vector3(0, InputSTART.y, 0)).magnitude);

        if (SwipeRange.x <= SwipeMinRange.x && SwipeRange.y <= SwipeMinRange.y)
        {
            NowSwipe = SwipeDirection.TAP;
        }
        else if (SwipeRange.x > SwipeRange.y)
        {
            float _x = Mathf.Sign(InputMOVE.x - InputSTART.x);
            if (_x > 0) NowSwipe = SwipeDirection.RIGHT;
            else if (_x < 0) NowSwipe = SwipeDirection.LEFT;
        }
        else
        {
            float _y = Mathf.Sign(InputMOVE.y - InputSTART.y);
            if (_y > 0) NowSwipe = SwipeDirection.UP;
            else if (_y < 0) NowSwipe = SwipeDirection.DOWN;
        }
    }

    // NONEにリセット
    private void ResetParameter()
    {
        NoneCountNow++;
        if (NoneCountNow >= NoneCountMax)
        {
            NoneCountNow = 0;
            NowFlick = FlickDirection.NONE;
            NowSwipe = SwipeDirection.NONE;
            SwipeRange = new Vector2(0, 0);
        }
    }

    // フリック方向の取得
    public FlickDirection GetNowFlick()
    {
        return NowFlick;
    }

    // スワイプ方向の取得
    public SwipeDirection GetNowSwipe()
    {
        return NowSwipe;
    }

    // スワイプ量の取得
    public float GetSwipeRange()
    {
        if (SwipeRange.x > SwipeRange.y)
        {
            return SwipeRange.x;
        }
        else
        {
            return SwipeRange.y;
        }
    }

    // スワイプ量の取得
    public Vector2 GetSwipeRangeVec()
    {
        if (NowSwipe != SwipeDirection.NONE)
        {
            return new Vector2(InputMOVE.x - InputSTART.x, InputMOVE.y - InputSTART.y);
        }
        else
        {
            return new Vector2(0, 0);
        }
    }
}


■TestText

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

public class TestText : MonoBehaviour
{
    [SerializeField]
    ScreenInput Input;

    // テスト用
    [SerializeField]
    private Text _testText;
    [SerializeField]
    private GameObject testOBJ;
    [SerializeField]
    private GameObject testOBJ2;

    // Start is called before the first frame update
    void Start()
    {
        Application.targetFrameRate = 30; // 30FPSに設定
    }

    // Update is called once per frame
    void Update()
    {
        // 文字表示処理
        _testText.text = "Flick:" + Input.GetNowFlick().ToString();
        _testText.text += "\nSwipe:" + Input.GetNowSwipe().ToString();
        _testText.text += "\nRange:" + Input.GetSwipeRange();

        // ひとつ目のオブジェクトを動かす処理
        float _work = Input.GetSwipeRange() * Time.deltaTime * 0.01f;

        switch (Input.GetNowSwipe())
        {
            case ScreenInput.SwipeDirection.UP:
                testOBJ.transform.localPosition = new Vector3(testOBJ.transform.localPosition.x, testOBJ.transform.localPosition.y + _work);
                break;
            case ScreenInput.SwipeDirection.DOWN:
                testOBJ.transform.localPosition = new Vector3(testOBJ.transform.localPosition.x, testOBJ.transform.localPosition.y - _work);
                break;
            case ScreenInput.SwipeDirection.LEFT:
                testOBJ.transform.localPosition = new Vector3(testOBJ.transform.localPosition.x - _work, testOBJ.transform.localPosition.y);
                break;
            case ScreenInput.SwipeDirection.RIGHT:
                testOBJ.transform.localPosition = new Vector3(testOBJ.transform.localPosition.x + _work, testOBJ.transform.localPosition.y);
                break;
        }

        // ふたつ目のオブジェクトを動かす処理
        if (Input.GetNowSwipe() != ScreenInput.SwipeDirection.TAP)
        {
            testOBJ2.transform.localPosition = new Vector3(testOBJ2.transform.localPosition.x + Input.GetSwipeRangeVec().x * Time.deltaTime * 0.01f, testOBJ2.transform.localPosition.y + Input.GetSwipeRangeVec().y * Time.deltaTime * 0.01f);
        }
    }
}

コピーが完了したら次のオブジェクト設定を行います。なお、コピー後は必ずスクリプトの上書き保存を行ってください!

8方向(斜め移動)にフリックとスワイプ を変更

現在サンプルスクリプトは4方向(上下左右)のフリックとスワイプ を判定します。斜めが欲しい! 8方向移動にしたい! という場合は以下の書き換えを行ってください。

・23行目〜44行目
方向判定の追加を行います。

    // フリックの方向
    public enum FlickDirection
    {
        NONE,
        TAP,
        UP,
        RIGHT,
        DOWN,
        LEFT,
        UP_LEFT,
        UP_RIGHT,
        DOWN_LEFT,
        DOWN_RIGHT
    }
    private FlickDirection NowFlick = FlickDirection.NONE;
    // スワイプの方向
    public enum SwipeDirection
    {
        NONE,
        TAP,
        UP,
        RIGHT,
        DOWN,
        LEFT,
        UP_LEFT,
        UP_RIGHT,
        DOWN_LEFT,
        DOWN_RIGHT
    }
    private SwipeDirection NowSwipe = SwipeDirection.NONE;


・107行目〜150行目(元のスクリプトでの行番号)
方向計算式の書き換えを行います。※上の書き換えを行った後だと114行目〜158行目とかになっていると思います。

    // 入力内容からフリック方向を計算
    private void FlickCLC()
    {
        Vector2 _work = new Vector2((new Vector3(InputEND.x, 0, 0) - new Vector3(InputSTART.x, 0, 0)).magnitude, (new Vector3(0, InputEND.y, 0) - new Vector3(0, InputSTART.y, 0)).magnitude);

        if (_work.x <= FlickMinRange.x && _work.y <= FlickMinRange.y)
        {
            NowFlick = FlickDirection.TAP;
        }
        else
        {
            float _angle = Mathf.Atan2(InputEND.y - InputSTART.y, InputEND.x - InputSTART.x) * Mathf.Rad2Deg;

            if (-22.5f <= _angle && _angle < 22.5f) NowFlick = FlickDirection.RIGHT;
            else if (22.5f <= _angle && _angle < 67.5f) NowFlick = FlickDirection.UP_RIGHT;
            else if (67.5f <= _angle && _angle < 112.5f) NowFlick = FlickDirection.UP;
            else if (112.5f <= _angle && _angle < 157.5f) NowFlick = FlickDirection.UP_LEFT;
            else if (157.5f <= _angle || _angle < -157.5f) NowFlick = FlickDirection.LEFT;
            else if (-157.5f <= _angle && _angle < -112.5f) NowFlick = FlickDirection.DOWN_LEFT;
            else if (-112.5f <= _angle && _angle < -67.5f) NowFlick = FlickDirection.DOWN;
            else if (-67.5f <= _angle && _angle < -22.5f) NowFlick = FlickDirection.DOWN_RIGHT;
        }
    }

    // 入力内容からスワイプ方向を計算
    private void SwipeCLC()
    {
        SwipeRange = new Vector2((new Vector3(InputMOVE.x, 0, 0) - new Vector3(InputSTART.x, 0, 0)).magnitude, (new Vector3(0, InputMOVE.y, 0) - new Vector3(0, InputSTART.y, 0)).magnitude);

        if (SwipeRange.x <= SwipeMinRange.x && SwipeRange.y <= SwipeMinRange.y)
        {
            NowSwipe = SwipeDirection.TAP;
        }
        else 
        {
            float _angle = Mathf.Atan2(InputMOVE.y - InputSTART.y, InputMOVE.x - InputSTART.x) * Mathf.Rad2Deg;

            if(-22.5f <= _angle && _angle < 22.5f) NowSwipe = SwipeDirection.RIGHT;
            else if(22.5f <= _angle && _angle < 67.5f) NowSwipe = SwipeDirection.UP_RIGHT;
            else if (67.5f <= _angle && _angle < 112.5f) NowSwipe = SwipeDirection.UP;
            else if (112.5f <= _angle && _angle < 157.5f) NowSwipe = SwipeDirection.UP_LEFT;
            else if (157.5f <= _angle || _angle < -157.5f) NowSwipe = SwipeDirection.LEFT;
            else if (-157.5f <= _angle && _angle < -112.5f) NowSwipe = SwipeDirection.DOWN_LEFT;
            else if (-112.5f <= _angle && _angle < -67.5f) NowSwipe = SwipeDirection.DOWN;
            else if (-67.5f <= _angle && _angle < -22.5f) NowSwipe = SwipeDirection.DOWN_RIGHT;
        }
    }

やっていることは、2点間の角度を計算で出して、それを8等分した角度で判定しています。注意点として、右の水平方向が0度で、左の水平方向が180度または-180度になります。

もっと細かく割れば、16方向とかできなくもないですが……すごい数のif文が並ぶことになりそうなので今回はやっていません!

オブジェクトを設定

まずはスクリプトをプロジェクト上のオブジェクトに設定します。

・空のオブジェクト
「ScreenInput」を設定

・テキストオブジェクト
「TestText」を設定

「TestText」を設定したテキストオブジェクトのインスペクターに、今回動きを確認するためのテストオブジェクトの割り当て場所を作っています。スクリプト割り当てが完了したら、それぞれの項目に対象オブジェクトを入れてください。

※スクリプト設定からオブジェクトの割り当てまでの手順は以下のサンプルから確認できます。

【Unity】スクリプト設定とインスペクターへのオブジェクト割り当て

あとはプロジェクトを再生すれば動きが確認できます。また端末へのビルドを行うとそちらでも動きが確認できると思います。

スクリプトの解説

簡単に使い方を解説します。

ScreenInputにはいくつかの設定項目を置いています。※ScreenInputを割り当てたゲームオブジェクトのインスペクターから確認できます!

ScreenInputのインスペクター

それぞれ、以下の項目を調整できます。

・Flick Min Range
フリック入力判定になる最小移動距離(タップしてから指が動いた距離)です。この数値以下の移動距離の場合は全てフリックではなくタップとして判定されます。

・Swipe Min Range
最小移動距離のスワイプ版です。ここで設定する数値以下は全てタップとして入力が判定されます。

・None Count Max
ユーザーが指を離してから、入力判定がニュートラル(何もない状態)に戻るまでのカウントです。入力情報がニュートラルに戻るのが早いと感じた時はここの数値を増やしてください。ほとんどの場合は1や2で大丈夫です。

実際に使う時は「TestText」にあるように[SerializeField]などでScreenInput Input;を取得してそれぞれの判定を呼び出してください。

・「Input.GetNowFlick()」
フリックの入力方向を返します。ifやswitchで「ScreenInput.FlickDirection.UP」などの数値を使い判定を行ってください。

・「Input.GetSwipeRange()」
スワイプの入力量を確認できます。方向判定と合わせることで移動をさせることもできます。ただし、動きが大きい方向の量のみを返すので使い方には気をつけてください。

・「Input.GetSwipeRangeVec().x」
・「Input.GetSwipeRangeVec().y」
スワイプ入力量の現在の数値をそのまま返します。オブジェクトの移動に割り当てると仮想アナログパッドのような動きが再現できます。

以上がフリックとスワイプの入力判定についてでした! 色々と入力を試してみてください!

※その他おすすめ記事
アプリ収益化のためのAdMob実装方法解説はこちら