Unity
uGUI
ScrollView

[Unity][uGUI] ScrollRectを特定の子要素までスクロールさせる

概要

スクロールビューを特定のコンテンツ位置までスクロールさせたいってことありますよね。Unity の Scroll View でそれをやる必要が出てきて、誰かがやってるんじゃないかと色々ググってみたんですが、これだ!ってのが見つからなくて、結局自作しました。
折角なので公開してみます。

コードと解説

ScrollRectに対する拡張メソッドとして作成しました。
縦スクロール画面専用です。チャットやLINEみたいな画面で子要素のサイズが均一ではない場合にも対応してます。

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// ScrollRectの拡張機能を提供します
/// </summary>
public static class ScrollRectExtension
{
    /// <summary>
    /// ScrollRectの上端にGameObjectをあわせる
    /// </summary>
    /// <param name="scrollRect"></param>
    /// <param name="go"></param>
    public static float ScrollToBeBottom(this ScrollRect scrollRect, GameObject go)
    {
        return ScrollToCore(scrollRect, go, 0f);
    }

    /// <summary>
    /// ScrollRectの下端にGameObjectをあわせる
    /// </summary>
    /// <param name="scrollRect"></param>
    /// <param name="go"></param>
    public static float ScrollToBeTop(this ScrollRect scrollRect, GameObject go)
    {
        return ScrollToCore(scrollRect, go, 1f);
    }

    /// <summary>
    /// ScrollRectの縦中央にGameObjectをあわせる
    /// </summary>
    /// <param name="scrollRect"></param>
    /// <param name="go"></param>
    public static float ScrollToCentering(this ScrollRect scrollRect, GameObject go)
    {
        return ScrollToCore(scrollRect, go, 0.5f);
    }

    /// <summary>
    /// ScrollRectのスクロール位置をGameObjectにあわせる
    /// </summary>
    /// <param name="scrollRect"></param>
    /// <param name="go"></param>
    /// <param name="align">0:下、0.5:中央、1:上</param>
    /// <returns></returns>
    static private float ScrollToCore(ScrollRect scrollRect, GameObject go, float align)
    {
        var targetRect = go.transform.GetComponent<RectTransform>();
        var contentHeight = scrollRect.content.rect.height;
        var viewportHeight = scrollRect.viewport.rect.height;
        // スクロール不要
        if (contentHeight < viewportHeight) return 0f;

        // ローカル座標が contentHeight の上辺を0として負の値で格納されてる
        // これは現在のレイアウト特有なのかもしれないので、要確認
        var targetPos = contentHeight + GetPosY(targetRect) + targetRect.rect.height * align;
        var gap = viewportHeight * align; // 上端〜下端あわせのための調整量
        var normalizedPos = (targetPos - gap) / (contentHeight - viewportHeight);

        normalizedPos = Mathf.Clamp01(normalizedPos);
        scrollRect.verticalNormalizedPosition = normalizedPos;
        return normalizedPos;
    }

    static private float GetPosY(RectTransform transform)
    {
        return transform.localPosition.y + transform.rect.y; //pivotによるズレをrect.yで補正
    }
}

使い方は、 ScrollRect のインスタンスとスクロール先に指定したいゲームオブジェクトを取得してscrollRect.ScrollToBeTop(targetObjecct);のように呼び出すだけ。
なお、スクロール先として指定するオブジェクトは、当然かもしれませんがContentの直下にありRectTransformが付いている必要があります。

完成度高めるなら、縦だけでなく横スクロールにも対応するとか、スクロール先として指定するオブジェクトがちゃんと子要素として存在するかチェックするのも必要だと思いますが、そこまでやってる時間がなかったし、最低限の目的は達成できたので一旦公開します。
それにサンプルとしては単純なほうが読みやすいかもしれないし。

今までuGUIとか本格的に弄ったことなくて、自動レイアウトの細かい挙動とかわかってないので、もしかしたらローカル座標計算が汎用的でないかもしれません。その辺も詳しい方に突っ込み貰えるかなという期待込めて投稿しました。

補足:prefab構成

もしかしたら、Component の設定が違ったりすると計算が狂うかもしれないという心配もあるので、実際に使ってる prefab の Inspector 上の設定をキャプチャして晒しておきます。

ScrollView

メニューからScroll Viewを追加すると出来るやつ。
scrollview.jpg

ViewPort

メニューからScroll Viewを追加するとScroll Viewに自動的についてくるやつ1。
viewport.jpg

Content

メニューからScroll Viewを追加するとScroll Viewに自動的についてくるやつ2。
この下にスクロール対象の子要素を追加していきます。
content.jpg

Panel(子要素)

こういうのを複数付けます。各パネルはさらに子要素を持っていて、内容によって高さは不定です(高さは自動レイアウトで決まります)。
children.jpg