【Unity】GameObjectをフリックに合わせて追尾させてみた

初めまして。今回ブログ記事を担当します、コンテンツ開発事業部の安井です。よろしくお願いします。
普段はUnityを用いたゲームアプリの開発を行っております。 今回は直近で行った実装に関してまとめようと思います。

行った内容はタッチ入力から座標を受け取り、画面内の指定されたGameObjectの識別です。さらに識別したものをフリック入力に合わせて追尾する仕組みを作成しました。

f:id:Teco_Yasui:20200420175439g:plain

実装のサンプルを見るとわかりますがいくつか選択できるGameObjectのうちから一つを選び、フリックを追尾させるという実装物です。 機能の実現に当たって主に以下の二点に絞って実装を進めています。
・タッチ入力範囲の判定処理
・選択したGameObjectに座標を再設定する処理

タッチ入力範囲の判定処理

こちらの機能については以下のソースコードを書きました。 それぞれ入力時にTouchPhase.Beganかどうかを確認して呼び出しています。

public void SearchActiveCard(Vector2 touchArea)
{
    //アクティブなカードの識別用変数
    int activeIndex = 0;
    foreach (SingleCard card in cardList)
    {
        // カードの座標取得
        RectTransform cardRect = card.gameObject.GetComponent<RectTransform>();
        activeIndex++;
        // 渡した引数をキャンバス内のローカル座標に変換してもらう
        Vector2 localPoint = Vector2.zero;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(cardRect, touchArea, null, out localPoint);
        // タップした箇所がカードの範囲内かの識別を行う
        if ((localPoint.x > cardRect.rect.xMin && localPoint.y > cardRect.rect.yMin) &&
            (localPoint.x < cardRect.rect.xMax && localPoint.y < cardRect.rect.yMax))
        {
            if (activeCard == card)
                isSameCard = true;

            if (activeCard != card)
            {
                isSameCard = false;
                ResetCardContentPosition();
            }

            activeCard = card;
            activeCardNum = activeIndex;
            if (!isSameCard)
            {
                Vector3 pos = activeCard.GetContent().transform.localPosition;
                defaultPosition = new Vector3(pos.x, pos.y, pos.z);
            }

            return;
        }
    }
}

処理の内容は入力された座標を比較対象の矩形のローカル座標へ変換を行い、最大・最小値の内側に収まっているかを見るというものです。

特に気を使っている点は画面から得た座標情報をCanvas内にあるRectTransformとの比較を行えるように変換を行う工程です。 座標の変換には以下の関数を使用しています。

docs.unity3d.com

こちらはスクリーン空間上の座標情報を矩形の平面上にあるRectTransformのローカル空間の位置に変換を行う関数です。
以下の部分が使っている個所の抜粋です。

Vector2 localPoint = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle(cardRect, touchArea, null, out localPoint);

関数に渡す引数に関して解説します。
第一引数にはローカル座標を探すためのRectTransformを渡します。出力引数になっていることからも想像できると思いますが第四引数はここで渡したものに対するローカル座標になります。
第二引数は変換元の座標情報です。今回は画面から入力した座標がそれにあたります。
第三引数に関してはリファレンスによると「スクリーン空間位置に関連するカメラ」と記載されています。これだけ読んでもあまりピンと来ないかもしれません。
ここに渡すCameraCanvasに設定したRender Modeによって変わってきます。
第一引数に指定したRectTransformが所属するCanvasRender Modeの設定から以下二つのどちらかを渡せばいいようです。
Screen Space - Cameraになっている場合にはRender Cameraに設定したCamera
Screen Space - Overlayになっている場合にはnull
これらの値が正しければ最終的に第四引数に算出したローカル座標の値が入っています。

最終的にlocalPointと比較対象のrectとの間でX・Y座標それぞれの比較を行い、矩形の範囲内に収まっているかの確認を行いました。

選択したGameObjectに座標を再設定する処理

位置の再設定の関数は以下のように書きました。 それぞれタッチからはTouchPhase.Movedの確認を行い呼び出しています。

private void SetCardMovedVector(Vector3 newPosition)
{
    if (activeCard == null)
        return;

    RectTransform cardRect = activeCard.gameObject.GetComponent<RectTransform>();
    // 渡した引数をキャンバス内のローカル座標に変換してもらう
    Vector2 localPoint = Vector2.zero;
    RectTransformUtility.ScreenPointToLocalPointInRectangle(cardRect, newPosition, null, out localPoint);
    GameObject cardContent = activeCard.GetContent();
    cardContent.transform.localPosition = localPoint;
}

この実装ではスクロールビューの中のアイテムを移動させるという目的のために要素の子供を移動させるという作りにしました。 そのため入力箇所の特定でも使用したScreenPointToLocalPointInRectangleを使い、入力座標を使い親から見たローカル座標を求めて再設定を行っています。
過去の座標を取得し差分を加算する処理での実装も考えたのですが、差分をローカル座標に変換を行い過去の座標に加算を行うという方法を行うのは煩雑になるため今回は避けました。

終わりに

今回ほかにも書けそうなネタはいくつかあったのですが一番困ったのがある程度可視化できる内容かどうかという点でした。そのため機能実装が目に見えやすい形にできるフリックで座標の再設定を行うという内容を選択しました。
個人的にもちょうどUnityで扱う空間の座標に関しても理解を深めるいい機会になったと感じました。

参考文献

UnityEngine.RectTransform - Unity スクリプトリファレンス UnityEngine.Rect - Unity スクリプトリファレンス UnityEngine.RectTransformUtility - Unity スクリプトリファレンス 【Unity】【uGUI】RectTransformUtilityでスクリーン座標をUIのローカル座標やワールド座標に変換する - LIGHT11

PR

tecotec.co.jp