🧩

SelectableScroll の主な機能について

に公開

前回の記事(Unityアセットをリリースしたけど、半額セール中に売れたのは3本だけでした)では、SelectableScroll をリリースした経緯や、売上が「3本」だったことについてご紹介いたしました。
お読みくださった方、コメントをお寄せくださった方、本当にありがとうございました。

今回は、SelectableScroll がどのようなアセットなのか、どのような機能を持っているのかについて、もう少し具体的にご紹介させていただきます。

🎉 Asset Store で SelectableScroll を見る
※ ゲームパッド対応のスクロールリストコンポーネントです!


Unity の Selectable とスクロールリスト

Unity の UI には Selectable という重要なコンポーネントが存在します。
ButtonToggleInputField など、ユーザーが操作できる UI 要素の多くはこれを基盤としており、ゲームパッドやキーボードでのフォーカス移動に利用されます。

たとえば、ゲームパッドの十字キーを押すことでボタンが順番に選択されていく仕組みは、Selectable によって実現されています。
(※ Gizmo の矢印は少々見えづらいかもしれませんが、Navigation の接続が表示されています)

しかし、スクロールリストとこの Selectable を組み合わせようとすると、いくつかの課題に直面します。

  • リストの中の要素が動的に生成・削除されると、ナビゲーションの接続が崩れてしまう
  • それを防ぐには、ナビゲーションを動的に構築する処理を実装する必要がある
  • グリッドなどレイアウトが複雑になると設定が非常に煩雑になる

さらに、世の中の多くのスクロールリスト向けアセットは、スマホのタッチ操作をメインに作られているため、
ゲームパッドやキーボード操作にはあまり対応できていない場合が多いのが実情です。


主な機能と特長

1. Selectable の自動ナビゲーション構築

SelectableScroll の最大の特徴は、Selectable 間のナビゲーションを自動で構築してくれることです。

表示中のリストアイテム(Selectable を含む UI)同士を、上下左右に自動で接続し、ゲームパッドやキーボードによる直感的な操作を可能にします。

さらに、非表示のアイテム(オブジェクトプールに戻されたもの)は自動的にナビゲーション対象から除外されるため、見た目どおりのフォーカス移動が維持されます。


2. グリッド表示にも対応

一部のスクロールアセットでは、正式なグリッド対応がされておらず、
「1行に複数アイテムを並べて、見た目だけグリッドに見せる」といった実装を行う場合があります。

(※このように、3つ並べた UI を1アイテムとして扱う必要があるアセットも存在します)
この場合でも動作は可能ですが、上下方向のナビゲーションを成立させるには手動の接続処理が必要となり、コードが複雑になったり、メンテナンスの手間が増すこともあります。
SelectableScroll であれば、構造的に正しいグリッドとしてリストアイテムを扱うことができ、上下左右すべてのナビゲーション接続も自動化されます。


3. セパレーター表示

スクロールリストの中に、タイトルや区切り線などの装飾的な要素(セパレーター)を挿入することが可能です。

セパレーターは Selectable ではないためフォーカスの対象にはなりませんが、
前後のアイテムとのナビゲーションが途切れないよう、自動的に接続処理が行われます

カテゴリー分けされたリストや、見出しのあるリストを作りたいときに便利です。

セパレーターの活用例

  • 設定画面で「サウンド」「グラフィック」「操作」などのカテゴリー見出しを表示したいとき
  • アイテムリストで「武器」「防具」「道具」などの区切りを挿入したいとき

4. 無限スクロール(Infinite Scroll)

リストをスクロールし続けると、データを循環させて表示し続ける「無限スクロール」にも対応しています。

とくにアイテム数が多い場合や、ループする UI(スロット、時間帯選択など)で活躍します。

表示領域に合わせてアイテム内容を動的に入れ替えるため、パフォーマンスにも配慮された設計となっています。


5. 選択フォーカスの循環(Wrap Around)

SelectableScroll は、上下左右の入力によるフォーカスのラップアラウンド(循環)移動 に対応しています。

たとえば、縦方向のリストで一番下のアイテムにフォーカスがある状態で下キーを押すと、
自動的に先頭のアイテムへフォーカスが移動します。
同様に、先頭で上キーを押すと、末尾のアイテムにジャンプします。

一時的に循環を停止させる
キーリピートが有効な環境では、
いったん端で止まり、次の入力で循環するような挙動を実現したいことがあります。

SelectableScroll にはこの挙動をサポートするための API が用意されています:
DisconnectWrapAroundNavigation() で一時停止し、
入力が止まったタイミングで ConnectWrapAroundNavigation() を呼び出すことで再開できます。

👇以下はその使用例(Sample05)のコードです。
SelectableScroll を用いることで、この程度のコードで柔軟な挙動を実現可能です。

Sample05.cs のコード全文を見る
    public class Sample05 : MonoBehaviour
    {
        [SerializeField]
        private Sample05Scroller scroller;

        private bool disconnected;

        public void Start()
        {
            var data = Enumerable.Range('A', 26)
                .Select(c => new Sample05Data(((char)c).ToString()))
                .ToArray();

            this.scroller.OnScrollerEvent += OnScrollerEvent;
            this.scroller.UpdateData(data);

            var itemIndex = Random.Range(0, data.Length);
            this.scroller.JumpTo(itemIndex);
            this.scroller.Select(itemIndex);
        }

        private void OnScrollerEvent(ISelectableScroller scroller, ScrollerEventType eventType, int pageIndex, int itemIndex, object message)
        {
            // Only apply logic when WrapAround is enabled
            if (this.scroller.LoopMode != LoopMode.WrapAround)
            {
                return;
            }

            // Only trigger on item selection events
            if (eventType != ScrollerEventType.ItemSelected)
            {
                return;
            }

            // If the first or last item is selected, disconnect wraparound navigation
            if (itemIndex == 0 || itemIndex == this.scroller.ItemCount - 1)
            {
                this.scroller.DisconnectWrapAroundNavigation();
                this.disconnected = true;
            }
        }

        public void Update()
        {
            // Do nothing if wraparound is still connected
            if (!this.disconnected)
            {
                return;
            }

#if ENABLE_INPUT_SYSTEM
            // Check for vertical input from keyboard or gamepad (new Input System)
            var hasKeyboardInput = Keyboard.current != null
                && (Keyboard.current.upArrowKey.isPressed || Keyboard.current.downArrowKey.isPressed);

            var hasGamepadInput = Gamepad.current != null
                && Mathf.Abs(Gamepad.current.leftStick.ReadValue().y) > 0.1f;

            var hasVerticalInput = hasKeyboardInput || hasGamepadInput;
#else
            // Check for vertical input from keyboard or gamepad (old Input Manager)
            var hasVerticalInput = Mathf.Abs(Input.GetAxisRaw("Vertical")) > 0.0f;
#endif
            // Reconnect wraparound navigation only when there's no vertical input
            if (!hasVerticalInput)
            {
                this.scroller.ConnectWrapAroundNavigation();
                this.disconnected = false;
            }
        }
    }

6. スクロールイベントの購読

前のセクション(Wrap Around)の Sample05 でも OnScrollerEvent を使っていましたが、
この OnScrollerEvent は、SelectableScroll における統一的なイベントフックであり、
次のようなさまざまなイベントを1つのインターフェースから受け取ることができます:

  • データが更新されたとき
  • アイテムがフォーカス(選択)されたとき
  • スクロール位置が変更されたとき
  • 独自に定義したカスタムイベントが呼ばれたとき

イベントは OnScrollerEvent を通じて受け取ることができ、処理を柔軟に追加できます。

this.scroller.OnScrollerEvent += (_, eventType, _, itemIndex, message) =>
{
    if (eventType != ScrollerEventType.Custom)
    {
        return;
    }

    Debug.Log($"CustomEvent: itemIndex={itemIndex}, message={message}");
    var selectedData = data[itemIndex];
    this.clickedItemText.text = selectedData.Text;
};

この例では、itemIndex 番目のデータに対してカスタムイベントが発火した際に、
ログを出力し、対応するテキスト UI を更新しています。
SelectableScroll では、リストアイテムが必ずしも Button とは限らないため、ユーザー操作に反応する処理(クリックなど)はカスタムイベントとして実装するのが基本です。
以下のように書くことで、特定のアイテムがクリックされた際の処理を定義できます:

// Add a click event listener to the button
this.button.onClick.AddListener(() =>
{
    // Invoke the OnEvent action with the data index and a message
    this.InvokeCustomEvent("click");
});

また、アイテム側から任意のタイミングでカスタムイベントを発火させることで、
独自のトリガー処理を自由に組み込むことも可能です。。


7. カスタムスクロールコントローラーの定義

SelectableScroll では、「スクロールの挙動」を ScrollController という仕組みで分離しています。
この仕組みにより、自分だけの独自スクロール挙動を簡単に実装・差し替えできます。

実際に実装されているコントローラー例としては:

  • タッチ操作:ゲームパッドやキーボードではなくタッチ操作も可能に
  • スナップ:アイテムが吸着するスナップ挙動
  • カルーセル:一定時間ごとにアイテムが自動で切り替わるUIです

これらはすべて SelectableScroll 独自の仕組みで実現されています。
そのため、操作感を自由にカスタマイズしたい場面でも柔軟に対応可能です。


まとめ

SelectableScroll は、以下のような方に特におすすめです:

  • ゲームパッドやキーボードで操作可能な UI を構築されたい方
  • オブジェクトプールで効率よくリストを表示されたい方
  • UI 要素を 上下左右に自然につながるナビゲーション で繋ぎたい方
  • セパレーターや複数列、入れ子構造を含む、より複雑なリスト表示に対応されたい方
  • 独自のスクロール挙動やタッチ対応など、柔軟なカスタマイズを行いたい方

ダウンロードはこちらから

SelectableScroll は Unity Asset Store にて販売中です!

👉 Asset Store で SelectableScroll をチェックする

ご不明な点やご意見、ご感想などございましたら、お気軽にお問い合わせやコメントをお寄せください。
最後までお読みいただき、誠にありがとうございました。

Discussion