質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.95%

ピンチの中心とした拡大縮小について

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 5,314

score 7

 前提・実現したいこと

unityを始めて1週間くらいの初心者です。
現状、乙女ゲーのようなアプリを作成しようとしており
ギャラリー機能としてキャラの立ち画をピンチによる拡大縮小を
行おうとしております。

現状下記のサイトを参考にスクリプトを作成しています。
http://kohki.hatenablog.jp/entry/Unity-uGUI-Pinch-Scaling-forMobile

やりたい事としては参考サイトをベースとして
ピンチを行った際の中心を軸として拡大縮小を行いたいのです。

改良の方法が分かる方がいらっしゃいましたら
ご教授いただければと思います。

 試したこと

下記のスクリプトでピンチの中心点に移動したオブジェクトを
上記参照サイトの「Content」の親に設定するように
「Content.transform.parent = 中心点オブジェクト.transform;」を
参考サイトのスクリプトに追加しましたがうまく行きませんでした。

using UnityEngine;
using System.Collections;

public class NewBehaviourScript : MonoBehaviour

{
    RectTransform rectTransform = null;
    [SerializeField] Transform target = null;

    void Awake()
    {
        rectTransform = GetComponent<RectTransform>();
    }

    void Update()
    {

        if (Input.touchCount == 2)
        {

            Touch touchZero = Input.GetTouch(0);
            Touch touchOne = Input.GetTouch(1);


            if (touchOne.phase == TouchPhase.Began)
            {
                Vector3 screen_point = (touchZero.position + touchOne.position) / 2;
                screen_point.z = 2.0f;

                rectTransform.position = screen_point;
              Debug.Log(screen_point);
            }
        }


    }
}

 補足情報(FW/ツールのバージョンなど)

Unity 2017.3 

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

UI関連は不慣れなうえモバイル向けに製作したことがなく、実機も持っていないので、あくまでもご参考程度に...ということでお願いしたいのですが、ご提示の参考サイトにならって試してみました。

オブジェクトの階層構造は、Unityのバージョンの違いのためサイトとは一部異なっていますが(「Scroll View」と「Content」の間に「Viewport」が挟まっており、これにMaskコンポーネントが付いています)、各種コンポーネントの設定は同様になっています。
インスペクタ
ただし「Scroll View」のScrollRectに関しては、本来のコンポーネントを削除してから、ScrollRectを継承した下記スクリプトをアタッチして、それに元のScrollRectと同じようにインスペクタ上の値を設定しています。
どうもScrollRectのドラッグスクロールが拡縮時の中心合わせ処理と干渉するらしく、そのままではおかしな動きをするので、ドラッグイベントを適宜無効化するようにしたものです。

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

[AddComponentMenu("UI/Undraggable Scroll Rect")]
public class UndraggableScrollRect : ScrollRect {
    private bool isDraggable = true;
    private GameObject draggingTarget;

    // ドラッグによるスクロールが可能かを取得・設定する
    public bool IsDraggable
    {
        get
        {
            return this.isDraggable;
        }

        set
        {
            if (this.isDraggable != value)
            {
                this.isDraggable = value;
                if (this.draggingTarget != null)
                {
                    if (value)
                    {
                        // draggingTargetが存在する状態でisDraggableがfalseからtrueになった→ドラッグ再開の必要ありと見なし、ニセのドラッグ開始イベントを発行する
                        var eventData = new PointerEventData(EventSystem.current);
                        eventData.pointerDrag = this.draggingTarget;
                        eventData.position = Input.mousePosition;
                        ExecuteEvents.Execute<IBeginDragHandler>(this.draggingTarget, eventData, ExecuteEvents.beginDragHandler);
                    }
                    else
                    {
                        // draggingTargetが存在する状態でisDraggableがtrueからfalseになった→ドラッグが中断されたと見なし、ニセのドラッグ終了イベントを発行する
                        ExecuteEvents.Execute<IEndDragHandler>(this.draggingTarget, new PointerEventData(EventSystem.current), ExecuteEvents.endDragHandler);
                    }
                }
            }
        }
    }

    public override void OnBeginDrag(PointerEventData eventData)
    {
        if (isDraggable)
        {
            this.draggingTarget = eventData.pointerDrag;
            base.OnBeginDrag(eventData);
        }
    }

    public override void OnDrag(PointerEventData eventData)
    {
        if (isDraggable)
        {
            base.OnDrag(eventData);
        }
    }

    public override void OnEndDrag(PointerEventData eventData)
    {
        if (eventData.dragging)
        {
            // IsDraggable切り替え時に発行されたニセイベントはdraggingがfalseになっているので区別できる
            // 本物のドラッグ終了イベントの場合のみdraggingTargetをnullに戻す
            this.draggingTarget = null;
        }
        base.OnEndDrag(eventData);
    }
}


PinchScalingManagerはほとんどサイトのものと同じで、変更箇所としてはUpdateScalingでの拡縮中心合わせ方法の変更と、先ほど申し上げたドラッグスクロールを拡縮開始でオフ、終了でオンに切り替えるようにした点ぐらいです。

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

public class PinchScalingManager : MonoBehaviour
{
    [SerializeField] private GridLayoutGroup content; // 拡大縮小するコンテンツ
    [SerializeField] private RectTransform wrapper; // コンテンツのラッパー // 変更...TransformをRectTransformに

    // コンテンツのRectTransformの参照
    private RectTransform contentRect;

    [SerializeField] private float scale; // 現在の拡大率

    [System.Serializable]
    struct RangeClass
    {
        public float min, max;
    }

    [SerializeField] private RangeClass RangeScale; // 拡大縮小の範囲
    [SerializeField] private RangeClass RangeLimitedScale; // 収束する範囲

    [SerializeField] private float TweenSecond; // 収束するまでにかかる時間

    private bool isPinch = false; // ピンチ中であればtrue
    // private Vector3 center; // 現在の中心座標 // 削除
    private Vector2 scalingCenter; // 追加...拡大縮小の中心となるスクリーン座標
    private Vector2 defaultCellSize; // 拡大率が1の時のコンテンツの大きさ
    private Vector2 marginTopLeft; // 追加...画像の左上余白
    private UndraggableScrollRect scrollView; // 追加...スクロールビュー

    #if UNITY_IOS || UNITY_ANDROID
    private float max_distance = 0; // ピンチ開始時の指間の距離
    #endif

    void Start()
    {
        contentRect = content.GetComponent<RectTransform>(); // 参照を設定
        scrollView = contentRect.GetComponentInParent<UndraggableScrollRect>(); // 参照を設定

        defaultCellSize = content.cellSize;
        // center = contentRect.localPosition / scale; // 削除

        // 状態の初期化
        Canvas.ForceUpdateCanvases(); // 追加...レイアウトを強制更新
        UpdateScaling(ClampScale(scale)); // 変更...新形式に書き換え

        // 表示されている画面の中心を拡大率に合わせて調整する
        contentRect.anchoredPosition *= scale;

        // 追加...余白の大きさを取得しておく
        var padding = content.padding;
        marginTopLeft = new Vector2(padding.left, -padding.top);
    }

    void Update()
    {
        #if UNITY_EDITOR
        EditorControl();
        #endif

        #if !UNITY_EDITOR
        MobileControl();
        #endif
    }

    #if UNITY_EDITOR
    private void EditorControl()
    {
        // タッチ中の処理
        if (isPinch)
        {
            // タッチ終了を感知し、終了処理をする
            if (Input.GetAxisRaw("Vertical") == 0)
            {
                isPinch = false;
                scrollView.IsDraggable = true; // 追加...スクロールビューのドラッグを有効にする
                StartTweenCoroutine();
                return;
            }
            UpdateScaling(ClampScale(scale + Input.GetAxisRaw("Vertical") * 1f * Time.deltaTime)); // 変更...新形式に書き換え
            return;
        }
        // タッチ開始時を感知し、初期化処理をする
        if (Input.GetAxisRaw("Vertical") != 0)
        {
            scalingCenter = (Vector2)Input.mousePosition; // 追加...中心をマウス位置に設定する
            // center = contentRect.localPosition / scale; // 削除
            scrollView.IsDraggable = false; // 追加...スクロールビューのドラッグを無効にする
            isPinch = true;
        }
    }
    #endif

    #if UNITY_IOS || UNITY_ANDROID
    private void MobileControl()
    {
        // タッチ中の処理
        if (isPinch)
        {
            // タッチ終了を感知し、終了処理をする
            if (Input.touchCount < 2)
            {
                isPinch = false;
                scrollView.IsDraggable = true;
                StartTweenCoroutine();
                return;
            }
            float distance = Vector2.Distance(Input.touches[0].position, Input.touches[1].position);
            UpdateScaling(ClampScale(distance / max_distance)); // 変更...新形式に書き換え
            return;
        }
        // タッチ開始時を感知し、初期化処理をする
        if (Input.touchCount == 2)
        {
            // center = contentRect.localPosition / scale; // 削除
            scalingCenter = Input.mousePosition; // 追加...中心をピンチ中心に設定する
            scrollView.IsDraggable = false;
            isPinch = true;
            float distance = Vector2.Distance(Input.touches[0].position, Input.touches[1].position);
            max_distance = distance / scale;
        }
    }
    #endif

    // 変更...新しいスケールをセットするメソッドの代わりに、拡大率を範囲内に収めて返すメソッドを作る
    /*
    /// <summary>
    /// 新しい拡大率のバリデートと更新をする
    /// </summary>
    private void SetNewScale(float new_scale)
    {

        // min < 新しい拡大率 < max に設定する
        new_scale = Mathf.Min(new_scale, RangeScale.max);
        new_scale = Mathf.Max(new_scale, RangeScale.min);

        scale = new_scale;

    }
    */
    /// <summary>
    /// 拡大率を範囲内に収める
    /// </summary>
    /// <returns>クランプされた拡大率。</returns>
    /// <param name="unclampedScale">未クランプの拡大率。</param>
    private float ClampScale(float unclampedScale)
    {
        return Mathf.Clamp(unclampedScale, RangeScale.min, RangeScale.max);
    }

    /// <summary>
    /// 収束させる拡大率を求め、コルーチンを開始する
    /// </summary>
    private void StartTweenCoroutine()
    {
        // min < 収束させる拡大率 < max に設定する
        float limited_scale = scale;
        limited_scale = Mathf.Min(limited_scale, RangeLimitedScale.max);
        limited_scale = Mathf.Max(limited_scale, RangeLimitedScale.min);

        StartCoroutine(TweenLimitedScale(limited_scale));
    }

    /// <summary>
    /// 拡大率を設定された値に収束させる
    /// </summary>
    IEnumerator TweenLimitedScale(float limited_scale)
    {
        if (scale == limited_scale)
            yield break;

        float timer = 0;
        float def_scale = scale - limited_scale;

        // scaleをTweenSecond秒以内にlimited_rateにする
        while (timer < TweenSecond)
        {
            timer += Time.deltaTime;
            UpdateScaling(ClampScale(scale - def_scale * Time.deltaTime * (1f / TweenSecond))); // 変更...新形式に書き換え
            yield return 0;
        }
    }

    // 変更...「scaleに新しい拡大率をセットしてUpdateScalingを実行」から「新しい拡大率を引数にしてUpdateScalingを実行」に動作を変え、中心調整の方法も変更する
    /// <summary>
    /// 新しい拡大率に基づいてオブジェクトの大きさを更新する
    /// </summary>
    /// <param name="newScale">新しい拡大率。</param>
    private void UpdateScaling(float newScale)
    {
        // 拡縮後の拡縮中心のズレを調べ、スクロール量に換算する
        var centerOnContent = (Vector2)contentRect.InverseTransformPoint(scalingCenter);
        var nextScaledCenterOnContent = (centerOnContent - marginTopLeft) * (newScale / scale) + marginTopLeft;
        var nextScaledCenterOnScreen = (Vector2)contentRect.TransformPoint(nextScaledCenterOnContent);
        var delta = nextScaledCenterOnScreen - scalingCenter;

        content.cellSize = defaultCellSize * newScale; // 想定するコンテンツの大きさを更新する
        wrapper.localScale = new Vector3(newScale, newScale, 1); // 全体を拡大縮小する
        contentRect.anchoredPosition -= delta; // コンテントをずらして位置を合わせる

        scale = newScale;
    }
}


iOSシミュレーターで動かしてみたところ一応それらしく見えたものの、やはり実機を使わないと動作確認にはいまいちですね...
結果

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/02/05 14:20

    細かく分かりやすいご回答ありがとう御座います。
    教えていただいたように修正したところ
    実機でも期待していた動作となりました。
    ありがとう御座いました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.95%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る