gracetory’s blog

東池袋にある合同会社グレストリのエンジニアブログです

【Unity】タッチ処理の実装(エディタ、実機両対応)

f:id:gmatsu:20180523183850j:plain

どうも、暑くなったり急に寒くなったりで軽く風邪を引いたgmatsuです。

流石にこの寒暖の差はひどすぎです笑。

私の家は漫画で溢れているのですが、そんな大量の漫画を収納する為に引戸タイプの本棚を購入しました。

収納力はかなりあるのですが、正直かっこよくない…。

背は高いし奥行きもあるので、圧迫感がかなり強く、部屋を狭く見せる原因になっていました。

そんなわけで自作の本棚を作れないだろうかと考えてます。

壁一面の本棚とか夢はありますが、不器用な自分に作れるのかが不安です笑。

さて、前回の記事ではこんな事を書いてました↓↓↓

techblog.gracetory.co.jp

プレハブ化、SpriteAtlas等の「キャラクターの表示」に必要な事を書いてました。

そこで今回は「操作」に関して書いてみます。

スマホアプリにおいて操作と言えば画面のタッチですよね。

画面、キャラクターをタッチしたか否かの判定を取ってみたいと思います。

今回やりたいこと

タッチ処理の実装が今回の目的になります。

タッチとは言ってもエディタと実機ではタッチ情報の取得方法に違いがあるので、どちらでタッチしたとしても判定が行えるようにしておきたいですよね。

これをやっておかないとデバッグが捗りません…。

後は表示されているキャラクター(GameObject)をタッチした時の処理も作ってみます。

実行環境

Mac mini (Late 2014)

MacOS Sierra (10.12.2)

Unity (2017.1.1f1)

エディタ、実機の判別方法

プログラムからエディタ、実機のどちらで実行されているかを判断する事ができます。

// エディタ、実機で処理を分ける
if (Application.isEditor) {
    // エディタで実行中
} else {
    // 実機で実行中
}

「Application.isEditor」がtrueならエディタで実行中である事を表しています。

これでエディタ、実機実行時の処理分けが簡単に行なえます。

タッチ取得方法(エディタ)

エディタではマウスをクリックする事でタッチを行いますよね。

InputクラスのGetMouseButton系メソッドを使えばクリック情報を得ることが出来ます。

if (Input.GetMouseButtonDown(0)) {
    Debug.Log("クリックした瞬間");
}

if (Input.GetMouseButtonUp(0)) {
    Debug.Log("離した瞬間");
}

if (Input.GetMouseButton(0)) {
    Debug.Log("クリックしっぱなし");
}

クリックした瞬間、離した瞬間、押しっぱなしを調べるメソッドが用意されています。

タッチ取得方法(実機)

実機での取得方法もエディタと同様にInputクラスを使用します。

// タッチされているかチェック
if (Input.touchCount > 0) {
    // タッチ情報の取得
    Touch touch = Input.GetTouch(0);

    if (touch.phase == TouchPhase.Began) {
        Debug.Log("押した瞬間");
    }

    if (touch.phase == TouchPhase.Ended) {
    Debug.Log("離した瞬間");
    }

    if (touch.phase == TouchPhase.Moved) {
        Debug.Log("押しっぱなし");
    }
}

まずは「Input.touchCount」でタッチされているか否かを判断。

その後、「Input.GetTouch(0)」でタッチの状態を取得します。

取得情報の「phase」にタップの状態が入っていますので、これを元に処理を状態ごとに分けます。

touchCountはタッチされているか否かではなく、同時にタッチされた数(指の数)が格納されてますので、マルチタップを処理する時にも使えるようでした。

GetTouch(X)のXにタッチされている番号を指定する事で、そのタッチ(指)の状態を得られるみたいですね(一本目の指が0です)。

今回は書きませんが、機会があったらマルチタップもやってみたいと思います。

タッチ状態取得クラスの作成

ここまででエディタ、実機ごと、加えて各タップ状態の時の処理分けが行えるようになりました。

このままではタップ処理ごとに上記処理を記入する事になりますので、それを避ける為にもクラス化してみましょう。

StateManager.cs

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

/**
 * 状態管理
 */
namespace StateManager {
    /**
     * タッチ管理クラス
     */
    public class TouchManager {
        public bool _touch_flag;      // タッチ有無
        public Vector2 _touch_position;   // タッチ座標
        public TouchPhase _touch_phase;   // タッチ状態

        /**
         * コンストラクタ
         *
         * @param bool flag タッチ有無
         * @param Vector2 position タッチ座標(引数の省略が行えるようにNull許容型に)
         * @param Touchphase phase タッチ状態
         * @access public
         */
        public TouchManager(bool flag = false, Vector2? position = null, TouchPhase phase = TouchPhase.Began) {
            this._touch_flag = flag;
            if (position == null) {
                this._touch_position = new Vector2(0, 0);
            } else {
                this._touch_position = (Vector2)position;
            }
            this._touch_phase = phase;
        }

        /**
         * 更新
         *
         * @access public
         */
        public void update() {
            this._touch_flag = false;

            // エディタ
            if (Application.isEditor) {
                // 押した瞬間
                if (Input.GetMouseButtonDown(0)) {
                    this._touch_flag = true;
                    this._touch_phase = TouchPhase.Began;
                    Debug.Log("押した瞬間");
                }

                // 離した瞬間
                if (Input.GetMouseButtonUp(0)) {
                    this._touch_flag = true;
                    this._touch_phase = TouchPhase.Ended;
                    Debug.Log("離した瞬間");
                }

                // 押しっぱなし
                if (Input.GetMouseButton(0)) {
                    this._touch_flag = true;
                    this._touch_phase = TouchPhase.Moved;
                    Debug.Log("押しっぱなし");
                }

                // 座標取得
                if (this._touch_flag) this._touch_position = Input.mousePosition;

            // 端末
            } else {
                if (Input.touchCount > 0) {
                    Touch touch = Input.GetTouch(0);
                    this._touch_position = touch.position;
                    this._touch_phase = touch.phase;
                    this._touch_flag = true;
                }
            }
        }

        /**
         * タッチ状態取得
         *
         * @access public
         */
        public TouchManager getTouch() {
            return new TouchManager(this._touch_flag, this._touch_position, this._touch_phase);
        }
    }
}

クラス本体は以上のように作ってみました。

タッチ状態だけでは実際には扱い辛いかと思ったので、

  • タッチ有無
  • タッチした座標
  • タッチ状態

を呼び出し元で取得出来るように作りました。

このクラスをタッチしたい箇所で次のように使う事を想定しています。

using StateManager;

public class XXX : MonoBehaviour {
    TouchManager _touch_manager;

    // 初期化
    void Start() {
        // タッチ管理マネージャ生成
        this._touch_manager = new TouchManager();
    }
    
    // 更新
    void Update() {
        // タッチ状態更新
        this._touch_manager.update();

        // タッチ取得
        TouchManager touch_state = this._touch_manager.getTouch();

        // タッチされていたら処理
        if (touch_state._touch_flag) {
            if (touch_state._touch_phase == TouchPhase.Began) {
                // タッチした瞬間の処理
            }
        }
    }
}

タッチした箇所にタッチ対象がいるか調べる

ここまでの実装で、タッチした事を調べることが出来るようになりました。

次はGameObjectをタッチしたか否かの判定を作成してみます。

この処理を作成する方法は

  1. Rayを飛ばす
  2. Eventを利用する

といった2つの方法があります。

今回はRayを飛ばす方法で判定を取ってみましょう。

Rayっていうのはその名の通り「光線」の事です。

タッチした位置にRay(光線)を飛ばして、Rayがオブジェクトに当たったらそこにオブジェクトが居る、つまりオブジェクトをタッチしたとして判定します。

当たらなかったらそこにはオブジェクトは無い、つまりタッチしていないって事になりますね。

タッチ対象となるオブジェクトにはRayが当たるようにする必要があるので、Collider系のComponentを追加しておきましょう。

f:id:gmatsu:20180523182118p:plain

自分は対象となるプレハブに「Box Collider 2D」をAddComponentしました。

これは2Dオブジェクトだから2D系のColliderを使用しているので、3Dオブジェクトの場合は3D系のColliderを追加するようにしてください。

ちなみにColliderの「Size」は必ず指定しましょう(私はスクリプトから指定してます)。

私はやっちゃいましたが、せっかくRayを飛ばしているのに当たる範囲(サイズ)が小さ過ぎて当たらない状態になりました笑。

Colliderの設定が終わったら次はRayを飛ばす処理です。

先程のタッチ処理を流用してみました。

public class XXX : MonoBehaviour {

    // ...省略...

    // 更新
    void Update() {
        // タッチ状態更新
        this._touch_manager.update();

        // タッチ取得
        TouchManager touch_state = this._touch_manager.getTouch();

        // タッチされていたら処理
        if (touch_state._touch_flag) {
            // 座標系変換
            Vector2 world_point = Camera.main.ScreenToWorldPoint(touch_state._touch_position);

            if (touch_state._touch_phase == TouchPhase.Began) {
                // タッチした瞬間の処理
                RaycastHit2D hit = Physics2D.Raycast(world_point, Vector2.zero);
                // チップ切り替え
                if (hit) {
                    Debug.Log("オブジェクトにタッチしたよ");
                    // タッチ時の処理を行う
                    hit.collider.GetComponent<XXX>().タッチした時の処理();
                } else {
                    Debug.Log("そこにオブジェクトは無いよ");
                }
            }
        }
    }
}

Rayを飛ばすにはPhysics2D.Raycast()を使用します。

Raycast()にタッチした座標を渡すことで、その位置にRayを打つ事が出来ます(自作のTouchManagerから取得したタッチ座標を使ってます)。

結果はRaycastHit2Dで返り、当たった対象は「.collider.GetComponent」で取り出すことが出来ます。

これで当たった対象に何か処理をする事も可能ですね。

具体的な例はGitHubに公開しましたのでそちらもご確認下さい。

github.com

季節は大分夏に近づいてきましたけど、その前に梅雨が待ち伏せていますね。

雨風に負けず頑張っていきましょー。