🤖

MVPパターンでUIを構築するときは、UI自体のActive状態もModelで管理すると便利

2024/05/25に公開1

はじめに

UnityでUIを構築する際に非常に便利なパターンにMVPパターンがあります。

このパターンでは、主にUniRx / R3を利用しながら、Modelを購読したPresenterがViewを更新する(逆もしかり)という流れでUIを構築します。

今回は、このMVPパターンを使ってUIを構築する際に、UI自体のActive状態もModelで管理すると便利なケースを紹介します。

前提

  • DIちょっとわかる
  • UniRx / R3を使ったことがある
  • MVPパターンちょっとわかる

Active状態もModelで管理する

基本的にはModelで管理するデータは、UIに表示されるデータやUIの状態などが該当します。この中にActive状態も含むと、画面自体のオンオフや切り替えといった面倒な作業が減り、非常にシンプルな記述で済みます。

例えば、以下のようなケースを考えてみます。

  • UIの画面自体(以下スクリーンと呼ぶ)が複数あり、ボタンを押すとその画面に切り替わる
  • すでに表示されている画面に対応したボタンを押すと、その画面が非表示になる

要するに簡単なタブ付きのメニュー画面みたいなものですが、意外と実装すると面倒になることも多いです。

これをModelで管理すると、以下のような感じで実装できます。

Model
// using 省略

public sealed class ScreenStateModel : IDisposable
{
    public ReadOnlyReactiveProperty<ScreenName> CurrentScreen => _currentScreen;
    private readonly ReactiveProperty<ScreenName> _currentScreen = new(ScreenName.None);

    private readonly CompositeDisposable _disposable = new();

    public void SetCurrentScreen(ScreenName screenName)
    {
        // すでに表示されている画面を再度押した場合は、画面を閉じる
        if (_currentScreen.Value == screenName)
        {
            _currentScreen.Value = ScreenName.None;
            return;
        }

        _currentScreen.Value = screenName;
    }

    public void Dispose()
    {
        _disposable.Dispose();
    }
}

public enum ScreenName
{
    None,
    MainOption,
    SubOption,
    Credit,
    VersionInfo,
}

このように実装すると、Presenter, Viewで以下のように画面を切り替えることができます。

Presenter
public sealed class ScreenPresenter : IInitializable, IDisposable
{
    private readonly ScreenStateModel _model;
    private readonly ScreenViews _views;

    private readonly CompositeDisposable _disposable = new();

    public ScreenPresenter(ScreenStateModel model, ScreenViews views)
    {
        _data = model;
        _views = views;
    }

    public void Initialize()
    {
        _data.CurrentScreen
            .Subscribe(screen =>
            {
                // すべての画面を非表示にする
                foreach (var view in _views.Views)
                {
                    view.SetActive(false);
                }

                // 表示する画面がない場合はここで終了
                if (screen == ScreenTitleName.None) return;
                
                // 表示する画面だけをアクティブにする
                _views.Get(screen).SetActive(true);

            }).AddTo(_disposable);
    }

    public void Dispose()
    {
        _disposable.Dispose();
    }
}
View
public sealed class ScreenViews : MonoBehaviour
{
    public IEnumerable<ScreenView> Views => _views;
    [SerializeField] private ScreenView[] _views;

    public ScreenView Get(ScreenName screenName)
    {
        return _views.FirstOrDefault(view => view.ScreenName == screenName);
    }
}

public sealed class ScreenView : MonoBehaviour
{
    public ScreenName ScreenName => _screenName;
    [SerializeField] private ScreenName _screenName;

    public void SetActive(bool active)
    {
        gameObject.SetActive(active);
    }
}

後は、ボタンを押したらModelのSetCurrentScreenを呼び出すだけで、画面の切り替えができます。

簡易的なButton
public sealed class ScreenChangeButton : MonoBehaviour
{
    [Inject] private readonly ScreenStateModel _model; // DI

    [SerializeField] private ScreenName _screenName; // 開きたい(閉じたい)画面の名前

    private Button _button;

    private void Awake()
    {
        _button = GetComponent<Button>();
    }

    private void Start()
    {
        _button.OnClickAsObservable()
            .Subscribe(_ => _model.SetCurrentScreen(_screenName))
            .AddTo(this);
    }
}

最後に

Modelは状態変数を管理する部分であり、その原則を徹底するともっと楽できる部分が増えるかもしれません。
無論、UI以外の部分での濫用はMVPパターンの管轄外なので、そこは注意した方がいい気がします。

Discussion

void2610void2610

Unityの中、上級者向け情報が少ないので本当に助かります...!
いつも発信ありがとうございます!!