lime雑記

ゲーム開発、その他雑記。

【Unity】LimeLibraryでUIを実装する : 実装編4

概要

自分用に作成したLimeLibraryを用いたUIの実装例についての続きになります。
前回はこちら。
limegame.hatenablog.com

このライブラリでどのようなことができるかについてはこちらの記事をご覧ください。
limegame.hatenablog.com

今回の内容

アイテムリストを選択可能にして、フローに組み込みます。

実装手順

InputModeUpdaterの追加

ゲームパッドやマウスの操作モード変更イベントを取得するため、シーンの任意の場所にInputModeUpdaterをアタッチしたGameObjectを追加します。

カーソルの用意

選択がわかりやすいようにカーソルを用意し追加します。
(半透明色を設定したImageComponentで大丈夫です)

アイテムにButtonComponentを追加

アイテムを選択可能にするために、ButtonComponentを追加します。
今回はLimeLibrary側のラッパークラスであるUIButtonをアタッチします。
(自動的にButtonもアタッチされます)

TargetGraphic用の透明画像もアタッチする

選択時挙動のセットアップとイベントの追加

ItemUiElementsに以下のコードを追加し、選択時の挙動の追加とイベントの公開を行います。

// ItemUiElements.cs
// 省略

public class ItemUiElements : MonoBehaviour, IInstantiatableUiElements<ItemUiElements>
{
    [SerializeField]
    private UIText _text;
    // 追加
    [SerializeField] 
    private UIButton _button;

    public ItemUiElements Self => this;

    // 追加
    public IObservable<Unit> OnHighlightedObservable => _button.GetEventObservable(UIButtonEventType.PointerEnter).AsUnitObservable();
    public IObservable<Unit> OnSelectObservable => _button.GetEventObservable(UIButtonEventType.Select).AsUnitObservable();
    public IObservable<Unit> OnClickObservable => _button.OnClickObservable;

    // 省略

    // 追加
    public void SetupSelectable(IUIView parentView, SelectCursor selectCursor)
    {
        // 選択時挙動制御クラスをセットアップ
        var selectableExtender = new SelectableExtender(parentView, _button.Button);
        // カーソル移動の選択時挙動をセットアップ
        var appearanceCursor = new CursorMover(gameObject, selectCursor);
        // Gamepad操作時はSelect時にカーソル移動
        selectableExtender.AddSelectableAppearance(ExtendSelectionState.Selected, appearanceCursor, InputMode.Gamepad);
        //  Mouse操作時はHighlighted時にカーソル移動
        selectableExtender.AddSelectableAppearance(ExtendSelectionState.Highlighted, appearanceCursor, InputMode.MouseKeyboard);
    }

    // 省略

SetupSelectableという関数を追加しています。
SelectableExtenderというクラスを使用することで、SelectableAppearanceを継承した挙動を表すインスタンス、その挙動の行うタイミングのステート、その挙動を行う入力タイプを渡すことで自動的にその挙動が行われるようになります。

また、今後の実装のためHighlited、Select、ClickのイベントもIObservableとして追加しています。

ItemListUiElements側から呼び出しとViewへのイベントの追加

先ほど追加した関数をItemListUiElements側から呼び出します。

また、カーソルが必要なためプレハブ側で追加したカーソル画像をGameObjectとしてSerializeしSelectCursorインスタンスを作成します。

最後に、ItemUiElements側で追加したイベント(IObservable)をViewから呼び出せるようにするため、
ItemUiElments→ItemListUIElments(ここでインデックス情報を追加)→ItemListViewまで伝播させます。

// ItemListUiElements.cs
// 省略

public class ItemListUiElements : MonoBehaviour
{
    [SerializeField]
    private ItemUiElements _originalItemUiElements;
    // 追加
    [SerializeField] 
    private GameObject _cursorObject;

    private readonly List<ItemUiElements> _createdItemUiElementsList = new();
    // 追加
    private SelectCursor _selectCursor;

    // 追加
    private readonly Subject<int> _onHighlightedSubject = new();
    private readonly Subject<int> _onSelectSubject = new();
    private readonly Subject<int> _onClickSubject = new();
    public IObservable<int> OnHighlightedObservable => _onHighlightedSubject;
    public IObservable<int> OnSelectObservable => _onSelectSubject;
    public IObservable<int> OnClickObservable => _onClickSubject;

    public void Initialize(IUIView parentView)
    {
        _originalItemUiElements.gameObject.SetActive(false);
        // 追加
        _selectCursor = new SelectCursor(parentView, _cursorObject);
    }
    public void CreateList(IUIView parentView, IReadOnlyList<ItemData> itemDataList)
    {
        // リストを作る前に前回生成したリストを削除
        Clear();
        
        if (itemDataList == null) return;
        
        // リスト分生成しデータを流し込む
        for (var i = 0; i < itemDataList.Count; i++)
        {
            var itemData = itemDataList[i];
            
            var itemUiElements = _originalItemUiElements.Instantiate(parentView);
            _createdItemUiElementsList.Add(itemUiElements);
            itemUiElements.SetItemName(itemData.Name);

            // 追加
            itemUiElements.SetupSelectable(parentView, _selectCursor);
            // Index情報を追加しイベント発行
            int index = i;
            itemUiElements.OnSelectObservable.Subscribe(_ => _onSelectSubject.OnNext(index)).AddTo(itemUiElements);
            itemUiElements.OnClickObservable.Subscribe(_ => _onClickSubject.OnNext(index)).AddTo(itemUiElements);
        }
    }
    
// 省略
}
// ItemListView.cs
// 省略

public class ItemListView : UIView
{
    [SerializeField] 
    private ItemListUiElements _uiElements;

    // 追加
    public IObservable<int> OnHighlightedObservable => _uiElements.OnHighlightedObservable;
    public IObservable<int> OnSelectObservable => _uiElements.OnSelectObservable;
    public IObservable<int> OnClickObservable => _uiElements.OnClickObservable;

    // 省略
}

イベントをトリガーにしてフローに組み込む

クリック時イベントがViewに組み込めたので、InventoryFlowSelectItemを以下のように変更しアイテムのクリックに反応させます。

// InventoryFlowSelectItem.cs

using Cysharp.Threading.Tasks;
using LimeLibrary.UI;

namespace Sample
{
    public class InventoryFlowSelectItem : UIAppFlowState<InventoryFlowState, InventoryFlowContext>
    {
        public override void Initialize() { }

        public override async UniTask<InventoryFlowState> Execute()
        {
            var itemListView = GetView<ItemListView>();
            
            // ItemListの作成
            itemListView.CreateList(Context.ItemDataList);
            
            // ItemListViewの表示
            await itemListView.Show(CancellationToken);
            
            // キャンセルキーが押されるかアイテムがクリックされるまで待機
            var result = await UniTask.WhenAny(
                    Context.CancelInputReceiver.OnInputObservable.ToUniTask(true, cancellationToken: CancellationToken),
                    itemListView.OnClickObservable.ToUniTask(true, cancellationToken: CancellationToken));

            switch (result.winArgumentIndex)
            {
                case 0:
                    // キャンセル
                    RequestEnd();
                    break;
                case 1:
                    // アイテムがクリックされた
                    await UIApp.GetView<ActionListView>().Show(CancellationToken);
                    break;
            }
            
            return InventoryFlowState.SelectItem;
        }
    }
}

アイテムをクリックすることでアクションリストが表示されれば成功です。