[Unity][Android]マルチタップ判定を使いこなす

Android
https://amzn.to/3bd8XOX

Androidには複数個所をタップするマルチタップ機能があります。Unityでこのマルチタップを検出した時、どの指がどのオブジェクトを選択しているかを判定して、追従したい場合があるかと思います。今回はこのマルチタップの検出とタップ毎に独立してオブジェクトを追従するサンプルについて説明します。

Goal

マルチタップでの複数オブジェクト選択を検出しする!




オブジェクトがタップされたことを知る

まず、オブジェクトがタップされたことを知る方法について説明します。Update()で随時タップを見張ることもできますが、Update()で無駄な処理をなるべく減らしたいので、以前のこちらの記事で紹介したEventSystemを利用したタップ検出を使用することにします。

こちらを参考に、まずはマウスによるイベントをスクリプトで受けられるところまでスクリプトを書いてみます。今回は三つのCubeを使用します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine.EventSystems; // 追加
using UnityEngine;

public class TapTest : MonoBehaviour , IPointerDownHandler, IPointerUpHandler
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

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

    public void OnPointerDown(PointerEventData eventData)
    {
        Debug.Log("Down"+GetComponent<Transform>().name);
    }
    public void OnPointerUp(PointerEventData eventData)
    {
        Debug.Log("Up" + GetComponent<Transform>().name);
    }
}

これを実行すると、それぞれのオブジェクトに対するマウスの押下、解放でログが出力されます。

指をスワイプ中にオブジェクトに触れたことを検出したい場合はOnPointerEnter()、指がオブジェクトから離れたことを検出したい場合はOnPointerExit()など、用途によって、必要なHandlerを実装してください。

タップの情報を取得する

タップの情報取得には、マウスの座標を取得するのと同じ用にInputを使用します。

Input.touchCountはタップされている数、Input.GetTouch(int index)でタップ情報(Touch)を取得できます

シングルタップ時の情報取得

シングルタップ前提の時はタップを検出した時に、Input.GetTouch(0)といったようにインデックス0を指定して情報を取得すればよいです。以下のコードでは一応タップ数が0でないことを確認してから、タップの情報を取得し、taouchinfoに格納しています。

    public void OnPointerDown(PointerEventData eventData)
    {
        Touch taouchinfo;

        Debug.Log("Down" + GetComponent<Transform>().name);

        if (Input.touchCount != 0) {
            taouchinfo = Input.GetTouch(0);

        }
    }

マルチタップ時の情報取得

マルチタップの時はそのオブジェクトがマルチタップのうち、何番目にタップされたものなのかわかりません。

よって、タップの情報を検索して、自身がフォーカスされているタップの情報を検出していく必要があります。これにはカメラからスクリーンへのRayを作成してそれが自身にヒットしているかどうかを確認します。

マルチタップの検出例を以下に示します。

    public void OnPointerDown(PointerEventData eventData)
    {
        Touch taouchinfo;
        Ray ray;
        RaycastHit[] hits = new RaycastHit[10]; // Rayがヒットするオブジェクトを格納
        int touchindex = 0;
        int hitNum;

        Debug.Log("Down" + GetComponent<Transform>().name);

        for (int i = 0; i < Input.touchCount; i++) {
            // タッチ数分、タッチ情報を確認する
            taouchinfo = Input.GetTouch(i);
            // タッチ情報の座標からRayを生成する
            ray = Camera.main.ScreenPointToRay(taouchinfo.position);
            // Rayがヒットしているオブジェクトを検索する
            hitNum = Physics.RaycastNonAlloc(ray, hits);
            for (int j = 0; j < hitNum; j++) {
                // hitを検索して自身のBoxColliderにヒットしていれば、このタッチで
                // ハンドラが実行されたと判断する
                if (hits[j].collider == GetComponent<BoxCollider>()) {
                    touchindex = i;
                    break;
                }
            }
        }
    }

hitという配列にRayがヒットしているオブジェクトが格納されています。今回は適当に10としています。Rayは自身のGameObjectのColliderコンポーネントにヒットしたものを探しています。

タップの状態

タップ情報のphaseパラメータにはタップの状態が格納されています。

この状態を見張ることで、タップが開始、移動、タッチされたまま移動なし、離された、強制的に話されたといった情報が取得できます。

タップ検出後にUpdate()でタップの動きに合わせてオブジェクトを動かし、タップが離されたらオブジェクトも離すようなサンプルは以下になります。

using System.Collections;
using System.Collections.Generic;
using UnityEngine.EventSystems; // 追加
using UnityEngine;

public class TapTest : MonoBehaviour , IPointerDownHandler, IPointerUpHandler
{
    private bool _isTouched = false; // タッチ検出有無
    private Vector3 _prevTouchPosi; // 前回検出時のタッチ座標(ワールド座標)
    private int _touchIndex = 0;  // タッチ情報のindex

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

    // Update is called once per frame
    void Update()
    {
        Touch touchinfo;
        Vector3 nowposi;
        Vector3 diff;

        if (_isTouched) {
            touchinfo = Input.GetTouch(_touchIndex);
            if (touchinfo.phase != TouchPhase.Ended && touchinfo.phase != TouchPhase.Canceled) {
                // タッチ終了、キャンセル以外はオブジェクトを動かす
                // 新しいタッチ位置の座標取得
                nowposi = Camera.main.ScreenToWorldPoint(touchinfo.position);
                // 前回保存の位置から差分を取得
                diff = nowposi - _prevTouchPosi;
                // 自身のオブジェクトに差分を追加して移動
                GetComponent<Transform>().position += diff;
                // 前回の位置を更新
                _prevTouchPosi = nowposi;
            } else {
                // タッチ終了、キャンセルはオブジェクトを離す
                _isTouched = false;
            }
        }
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        Touch taouchinfo;
        Ray ray;
        RaycastHit[] hits = new RaycastHit[10]; // Rayがヒットするオブジェクトを格納
        
        int hitNum;

        Debug.Log("Down" + GetComponent<Transform>().name);

        for (int i = 0; i < Input.touchCount; i++) {
            // タッチ数分、タッチ情報を確認する
            taouchinfo = Input.GetTouch(i);
            // タッチ情報の座標からRayを生成する
            ray = Camera.main.ScreenPointToRay(taouchinfo.position);
            // Rayがヒットしているオブジェクトを検索する
            hitNum = Physics.RaycastNonAlloc(ray, hits);
            for (int j = 0; j < hitNum; j++) {
                // hitを検索して自身のBoxColliderにヒットしていれば、このタッチで
                // ハンドラが実行されたと判断する
                if (hits[j].collider == GetComponent<BoxCollider>()) {
                    // タッチ情報のindex保存
                    _touchIndex = i;
                    // タッチが発生した
                    _isTouched = true;
                    // 検出したタッチの座標をワールド座標に変換し保存
                    _prevTouchPosi = Camera.main.ScreenToWorldPoint(taouchinfo.position);
                    break;
                }
            }
        }
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        Debug.Log("Up" + GetComponent<Transform>().name);
    }
}

タップのphaseが終了かキャンセルでない限り、タップの座標の差分だけオブジェクトを移動させています。

タップを離すとphaseはTouchPhase.Endedになるので、オブジェクトは落下してきます。

3つのCubeそれぞれでタップ情報を取得していますので、3つのタップで同時に動かすこともできました。また、オブジェクト外を不要にタップした状態で、オブジェクトをタップして動かすこともできました。

まとめ

今回、マルチタップでオブジェクトを選択して自由に動かせるようになりました。

常にUpdate()でタップの状態を見張る方が簡単ですが、頻繁に実行されるUpdate()の処理を重くしてしまうと、全体的に重たくなってしまうので、なるべくハンドラを使用することをお勧めします。

https://amzn.to/3bd8XOX

コメント

タイトルとURLをコピーしました