建設とUnity

Unityを使って、建設業界向けのソフトを開発するノウハウを紹介します。

【無限】Unityで大量のオブジェクトでも軽量にスクロールできるスクロールビューをつくろう(その1)

UnityでScroll Viewを使う際は、Contentの配下に大量のオブジェクト(たとえばButton)を配置してしまうと描画負荷が高くなります。

私の環境では、1000個程度なら問題がありませんでした(それでも負荷はかかっていました)が、1万個程度ならかなりの負荷になりました。

そのときのプロファイラーの結果は以下の通りです。

f:id:sayadoki:20220128103138p:plain

前半のスパイク部分がマウスでスクロールしたとき、幅広くピークがたっている部分はスクロールバーで移動しているときです。

特にスクロールバーを移動しているときは常時FPSが15を切ってしまっており、かなりの高負荷になっています。

そもそもオブジェクトを1万個も配置しない…といった話もあるかとは思いますが、 建築現場の部品単位で表示したい場合、部品点数が1万点以上になることもあり、何かしらの対処が必要でした。

今回は、大量のオブジェクトが対象でも、軽量にスクロールできるスクロールビューのつくり方を紹介します。

スクロールビューのつくり方

f:id:sayadoki:20220124204327p:plain

上の画像にスクロールビューのサンプルを示します。大雑把なつくり方は以下の通りです。

  1. Unityのヒエラルキーで右クリック>UI>スクロールビューの作成を行います。

  2. Scroll View/Scrollbar Horizontalを削除します(今回は不要)。

  3. Scroll View/Scrollbar VerticalのRectTransformの下の値を0にします。Scrollbarを範囲いっぱいにするためです。

  4. ScrollView/Viewport/Contentの下にヒエラルキーで右クリック>UI>ボタンで生成したButtonを配置します。

  5. ScrollView/Viewport/ContentにVertical Layout Groupをアタッチします。これがあることで配下のButtonの位置を自動で調整してもらえます。

  6. ScrollView/Viewport/ContentのVertical Layout Groupの子のサイズを制御で幅にチェックをいれて、チェックを外します。Buttonの幅を整えるためです。

  7. ScrollView/Viewport/ContentにContent Size Filterを追加して、垂直フィットをPreffered Sizeにします。

  8. ScrollView/Viewport/Content/Buttonを必要な個数だけ複製します。

どうやって軽くするか

先ほどの画像をよくみてみると、Buttonは0~6までの7つのみ表示されています。

Buttonで表現したい対象がいくら大量にあっても、画面に表示されるのは7つのみということです。

つまり、表示に必要な最低個数を使いまわすことで軽量に動作するスクロールビューが実現できます。

動作イメージ

こちらの動画のように、Buttonの数は増やさずにButtonの数字を増減させています。 (動画では数字の切り替わりが飛ばし飛ばしになっていますが、以下のコードでは順番になるよう修正しています)

サンプルコード

以下のサンプルコードを任意のオブジェクトにアタッチして、ContentのTransformとScrollbarのスクリプトを参照すると動作します。

using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class SimpleScrollViewConroller : MonoBehaviour
{
    [SerializeField] private Transform contentTr;
    [SerializeField] private Scrollbar scrollbar;

    private Text[] texts;

    private int current;      // 現時点のButton群の先頭の番号
    private int final;        // 最後方のButton群の先頭の番号
    private int length;       // 表示するButtonの数
    private int size = 10000; // Buttonの最大数

    private void Start()
    {
        texts = contentTr.GetComponentsInChildren<Text>();
        length = texts.Length;
        for (int i = 0; i < length; i++)
        {
            texts[i].text = i.ToString();
        }
        final = size - length + 1;

        // 次に進む
        scrollbar.ObserveEveryValueChanged(x => x.value)
            .Where(x => x < 0f)
            .Where(_ => current != final)
            .Subscribe(x =>
            {
                scrollbar.value = 1;
                current++;
                SetNumber();
            })
            .AddTo(this);

        // 前に戻る
        scrollbar.ObserveEveryValueChanged(x => x.value)
            .Where(x => x > 1f)
            .Where(_ => current != 0)
            .Subscribe(x =>
            {
                scrollbar.value = 0;
                current--;
                SetNumber();
            })
            .AddTo(this);
    }

    private void SetNumber()
    {
        for (int i = current; i < current + length; i++)
        {
            var j = i - current;
            texts[j].text = i.ToString();
        }
    }
}

注意事項

実際に利用する際には、 SetNumber()でButtonのTextを変更するだけではなく、押したときの動作を切り替えるようにつくりこむ必要があります。

また、今回の方法ではスクロールバーの位置がContent配下にあるButtonに対する相対的な位置を表してしまっているため、 全体の範囲に対して、スクロールバーをドラッグして移動することができない状態です。

この問題に関して、独自のスクロールバーを実装することで解決したので、以下の記事で紹介しました。

【無限】Unityで大量のオブジェクトでも軽量にスクロールできるスクロールビューをつくろう(その2) - 建設とUnity