Zenn
🌟

[unity] ScrollRectを強化するTableScrollView

2025/02/25に公開
1

unity の ScrollRect は便利ですが、アイテム数が増えていくとどんどん重くなるのがネックです。
またゲームだと、キーボード(パッド)入力にも対応する必要があります。

この要望を拡張してくれる追加コンポーネント、TableScrollView を紹介します。

GitHub

https://github.com/catsnipe/TableScrollView

特徴

ベースは ScrollRect

ScrollRect のいいところは残しつつ、いくつかの UX 改善を行っています。

  • スクロールバーが自動フェードアウト(機能オンオフ可能)
  • 選択されたアイテムがスクロールストップ時、自動的に適切な位置に補正する

アイテム数が大量に増えても処理落ちしない

最初に表示エリアに必要な最低限のアイテム(Node と呼びます)バッファがヒエラルキーに確保され、以後はそのバッファを書き換えつつスクロールエリアを表現します。
実際に表示する項目が1000になっても、バッファは表示エリア分(多くても20程度でしょう)なので、大幅な速度改善が期待できます。

キーボード / パッド などのコントローラー入力用イベント

入力はイベントとして外に出しているので、各々アプリ側で使っているキー入力システムを当てはめることができます。

unity6 より下でも(多分)使用可能

いくつか unity の都合で書式の変わったコードさえなんとかすれば、使える……(はず)。

使い方

SampleScene2.unity を実行してください。必要な作業は3つです。

1. ノードを作成する

アイテム1つ分の Prefab(表示用)と、それを制御するノードクラスを記述します。

こんな風に表示用の Prefab を作って……。

NodeScene2.cs
using UnityEngine.UI;

public class NodeScene2 : TableNodeElement
{
    [SerializeField]
    TextMeshProUGUI    No = null;
    [SerializeField]
    TextMeshProUGUI    Desc = null;
    [SerializeField]
    Image              Icon = null;
    [SerializeField]
    Image              Focus = null;
    [SerializeField]
    Sprite[]           IconSprites = null;

    /// <summary>
    /// フォーカス ON/OFF の表示をここに記述する
    /// </summary>
    public override void onEffectFocus(bool focus, bool isAnimation)
    {
        Focus.color = new Color(0,0,0, focus == true ? 0.2f : 0.1f);
    }

    /// <summary>
    /// 行の表示更新通知があった場合、ここで表示を更新する
    /// </summary>
    public override void onEffectChange(int itemIndex)
    {
        var row = (TestScene2.Row)table[itemIndex];

        No.SetText("Line: " + row.No.ToString("00"));
        Desc.SetText(row.PlaceName);
        Icon.sprite = IconSprites[row.No % IconSprites.Length];

        this.name = No.text;
    }
}

TableNodeElementをベースクラスとしたノードクラスを作成し、アタッチします。

2. ScrollRect に TableScrollViewer をアタッチする


ScrollRect と同じ GameObject に TableScrollViewer をアタッチし、先ほど作成したノードをドラッグ、スクロール方向が縦なら Vertical、横なら Horizontal を選んでください。

3. ビューに表示するデータリストを登録する

TaseScene2.cs をご覧ください。

TestScene2.cs
using System.Collections.Generic;
using UnityEngine;

public class TestScene2 : MonoBehaviour
{
    [SerializeField]
    TableScrollViewer   viewer;

    public class Row
    {
        public int No;
        public string PlaceName;
    }

    List<Row> rows = new List<Row>()
    {
        new Row() { No = 1, PlaceName = "Amsterdam" },
        new Row() { No = 2, PlaceName = "Bangkok" },
        new Row() { No = 3, PlaceName = "Chicago" },
        new Row() { No = 4, PlaceName = "Dubai" },
        new Row() { No = 5, PlaceName = "Edinburgh" },
        new Row() { No = 6, PlaceName = "Frankfurt" },
        new Row() { No = 7, PlaceName = "Geneva" },
        new Row() { No = 8, PlaceName = "Hong Kong" },
        new Row() { No = 9, PlaceName = "Istanbul" },
        new Row() { No = 10, PlaceName = "Washington, D.C." },
    };

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        viewer?.Initialize();
        viewer?.SetTable(rows);
        viewer?.OnSelect.AddListener(OnSelectVertical);
        viewer?.OnKeyDown.AddListener(OnKeyDown);
    }

    public void OnSelectVertical(List<object> table, int itemIndex, int subIndex, bool isCancel)
    {
        Row row = (Row)table[itemIndex];

        if (subIndex < 0)
        {
            Debug.Log($"selected: {row.No} {row.PlaceName}");
        }
    }

    public void OnKeyDown(TableScrollViewer.KeyDownArgs args)
    {
        if (Input.GetKeyDown(KeyCode.Space) == true)
            args.Flag = TableScrollViewer.eKeyMoveFlag.Select;
        else if (Input.GetKeyDown(KeyCode.UpArrow) == true)
            args.Flag = TableScrollViewer.eKeyMoveFlag.Up;
        else if (Input.GetKeyDown(KeyCode.DownArrow) == true)
            args.Flag = TableScrollViewer.eKeyMoveFlag.Down;
    }
}

List<Row> の型に決まりはないので、自分で好きなように設定してください。
Row 1つ分がノード1つ分になります。
ノードの表示側では、このように定義しています。

    /// <summary>
    /// 行の表示更新通知があった場合、ここで表示を更新する
    /// </summary>
    public override void onEffectChange(int itemIndex)
    {
        var row = (TestScene2.Row)table[itemIndex];

        No.SetText("Line: " + row.No.ToString("00"));
        Desc.SetText(row.PlaceName);
        Icon.sprite = IconSprites[row.No % IconSprites.Length];

        this.name = No.text;
    }

項目の説明

TableScrollViewer のインスペクターに表示される項目について説明します。

設定

  • Source Node:
    作成したノードをここにドラッグします。
  • Orientation:
    縦方向であれば Vertical、横方向であれば Horizontal を指定します。
  • Alignment:
    この項目は実装が適当なので「スクロールしないビューのみ機能」します。
    TableScrollViewer といいつつ通常のボタンメニューでも使えるので、そういう場合には有用な項目です。
  • Padding Top / Bottom:
    表示エリア上(左)と下(右)の余白です。
  • Spacing:
    ノードとノードの間の余白です。
  • Scroll Time:
    スクロール速度。小さいほど速い。
  • Skip Index By Page Scroll:
    キーやパッドでページスクロール要求(eKeyMoveFlag.Page???)された時、一度に移動するアイテムの数。
  • Scrollbar Auto Fadeout:
    リストがスクロールすると表示されるスクロールバーを自動的に消します。
  • Absorption Target:
    説明が難しい……オンにしておくと、とりあえず UX が上がります。
    (スクロールのストップ位置を使いやすいよう補正する、というニュアンス)
  • Select After Focus:
    チェックを入れると「1度めフォーカス > 2度目実際に項目選択」の2ステップになります。
  • Easy Focus For Mouse:
    マウス使用時、クリック選択しなくても自動的にフォーカスされるようになります。
  • Disabled After Select:
    選択後、入力を受け付けなくなりますが、InputEnabled(true) で戻す必要があるので、使い勝手が悪いです……。

イベント

  • OnSelect():
    項目が選択されると発生します。引数でどれを選択されたかが分かります。
  • OnKeyDown():
    キー入力に対し、View がどう動くかを設定します。
    Input でも、InputSystem でも、お好みの入力クラスをお使いください。
  • OnCursorMove():
    フォーカスが移動した時に発生するイベントです。
    フォーカス移動時の描画はノードクラスで定義しますが、カーソル音が必要であればこちらで定義してください。
  • OnCheckSelectable():
    選択されようとしている項目が、選択可能かどうかを返すイベントです。
    たとえば選択項目が0-1-2-3-4とあり、1だけ選択できない場合、Index が1の項目では result.Enabled = false を返すようにしてください。

その他

ノード内にサブノードを作ることもできるため、m列xn行のようなメニューも作ることが可能です。SampleScene.unity を参考にしてください。

1

Discussion

ログインするとコメントできます