📱

【Unity】ピンチした位置を中心にズームする

2024/09/09に公開

Unityを用いたスマートフォン向け2Dアプリ開発で、ピンチした位置を中心にズームする方法です。検証に利用したUnityバージョンは2022.3.38f1です。

ピンチでズームする場合には、CameraのorthographicSizeを変更する方法とズーム対象のオブジェクトのスケールを変更する方法の2通り考えられますが、今回は後者の方法を採用しています。

using UnityEngine;

public class PinchZoom : MonoBehaviour
{
    [SerializeField] Transform pinchTarget;
    [SerializeField] float pinchSensitivity = 0.01f;

    // スクリーン位置からワールド位置を求めるメソッド。カメラが(0, 0)にある前提。
    public static Vector2 ConvertScreenPosToWorldPos(Vector2 screenPos) {
        var orthoY = Camera.main.orthographicSize;
        var orthoX = orthoY * Camera.main.aspect;
        var screenY = Screen.height;
        return (screenPos * (orthoY * 2) / screenY) - new Vector2(orthoX, orthoY);
    }

    void Update()
    {
        if (Input.touchCount == 2) {
            var touch1 = Input.GetTouch(0);
            var touch2 = Input.GetTouch(1);
            // 2本指でが動いているときのみピンチとして処理
            if (touch1.phase == TouchPhase.Moved && touch2.phase == TouchPhase.Moved) {
                // ピンチの中心からターゲットまでのベクトルを求める
                var pinchCenterInScreenSpace = (touch1.position + touch2.position) / 2;
                var pinchCenterInWorldSpace = ConvertScreenPosToWorldPos(pinchCenterInScreenSpace);
                var centerToTarget = (Vector2)pinchTarget.position - pinchCenterInWorldSpace;

                // 指の動きからピンチの強さを計算する
                var distance = Vector2.Distance(touch1.position, touch2.position);
                var prevDistance = Vector2.Distance(touch1.position - touch1.deltaPosition, touch2.position - touch2.deltaPosition);
                var speed1 = touch1.deltaPosition.magnitude / touch1.deltaTime;
                var speed2 = touch2.deltaPosition.magnitude / touch2.deltaTime;
                var pinchSpeed = (speed1 + speed2) / 2;
                var pinchIntensity = pinchSpeed * Time.deltaTime * Mathf.Sign(distance - prevDistance) * pinchSensitivity;

                // ピンチの中心と強さからターゲットのスケールと位置を調整する
                var currentScale = pinchTarget.transform.localScale.x;
                var nextScale = currentScale + pinchIntensity;
                var scaleRate = nextScale / currentScale;
                pinchTarget.transform.localScale = new Vector3(nextScale, nextScale, 1.0f);
                pinchTarget.transform.position += (scaleRate - 1.0f) * (Vector3)centerToTarget;
            }
        }
    }
}

pinchTargetにはズームする対象のオブジェクトを指定します。Unityにデフォルトで入っているQuadメッシュのように原点が中心にある長方形のメッシュのみで機能します。

2本指の動きをもとにピンチ量を取得してズームに反映する流れはインターネットを検索すれば記事がたくさんでてくるので説明は割愛しますが、ピンチした位置を中心にズームするために以下の一文でターゲットの位置を調整しています。

pinchTarget.transform.position += (scaleRate - 1.0f) * (Vector3)centerToTarget;

考え方として次の図を用いて説明します。

黒い四角形がpinchTargetで丸がその原点、黒いバツ印がピンチの中心です。ピンチ操作により赤い四角形のように拡大したとします。赤いバツ印は拡大後のピンチの中心に対応した位置です。ピンチした位置を中心にズームするとは、ズーム前後で画面の見た目上でピンチの中心の位置が変わらないことなので、赤いバツ印が黒いバツ印と同じ位置になるように位置を補正する必要があります。黒い四角形に対する赤い四角形の拡大率をscaleRatepinchTargetの原点から拡大前のピンチの中心までのベクトルcenterToTarget(黒い矢印)とするとpinchTargetの原点から拡大後のピンチの中心まではscaleRate * centerToTarget(赤い矢印)となります。拡大後のピンチの中心を拡大前のピンチの中心の位置に戻すにはベクトルの差分を取ればいいので、scaleRate * centerToTarget - centerToTarget=(scaleRate - 1) * centerToTarget(青い矢印)だけ位置を補正すればよいということになります。

Discussion