はじめに
どうも、土鍋です。
最近、Message Pipeを使用したプロジェクトに出会ったのですが、Message Pipeミリしらだったので、今回は勉強がてらUIでよく使用されるMVPパターンをMessage PipeとVContainerを利用してできないかやってみました。
MessagePipeとは?
Pub/SubパターンをUnityに提供するライブラリです。Pub/Subパターンの個人的な理解としては、Observerパターンを依存性の逆転とすると、Pub/Subパターンはそもそもお互いを知らなくても通知が飛ぶ的なイメージです。
お互いを知らなくても良いようにするためにDIによる依存性の注入が必要になってきます。
インポート
VContainer
Package Managerを開き、左上の+から「Add package from git URL」で
「https://github.com/hadashiA/VContainer.git?path=VContainer/Assets/VContainer」

Message Pipe
Message Pipe本体
同様にして、「Add package from git URL」で以下を追加。
「https://github.com/Cysharp/MessagePipe.git?path=src/MessagePipe.Unity/Assets/Plugins/MessagePipe」
UniTaskも必要なので追加
「https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask」
お好きなDIも追加しよう
MVPパターンで構築してみる
MVPパターンとは
Model View Presenterの略。UnityでGUIを設計する際に用いられる設計思想で、UnityのUI処理部分と値を持つ部分を切り分けてそれらをPresenterで繋ぐといった形の設計。
(界隈によってはMVCと言った方が伝わる気がする)
今回の設計
今回は例として、ボタンを押すと数字が増えるものを作る。
ViewやModelはデータや状態の変更をPresenterにPublishし、PresenterがSubscribeしている。これによってViewとModelはPresenterを知らなくても良くなっている。
View
ViewのみがMonoBehaviourを持つ形で作ってみる。 ボタンの押されたか否かの監視とテキストの更新のみを担っている。
public class UIView : MonoBehaviour { [SerializeField] private Button button; [SerializeField] private Text text; [Inject] private readonly IPublisher<Unit> _publisher; private void Start() { var onPush = button.onClick.AsObservable(); onPush.Subscribe(_publisher.Publish); } public void ChangeText(int num) { text.text = num.ToString(); } }
Presenter
DIによってsubscriberがInjectされる。
値変更やボタンが押されるとそれをViewやModelに伝える。
public class UIPresenter : IInitializable { [Inject] private UIView _view; [Inject] private UIModel _model; [Inject] private readonly ISubscriber<Unit> _unitSubscriber; [Inject] private readonly ISubscriber<int> _intSubscriber; public UIPresenter(ISubscriber<Unit> unitSubscriber, ISubscriber<int> intSubscriber) { _unitSubscriber = unitSubscriber; _intSubscriber = intSubscriber; } public void Initialize() { _unitSubscriber.Subscribe((_) => { _model.CountNumber(); }); _intSubscriber.Subscribe(num => { _view.ChangeText(num); }); } }
Model
現在の数字のデータの保持とカウントアップ用メソッドを持つ。こちらも数字が変更されると、Publishされるようになっている。
public class UIModel { private int _number; [Inject] private readonly IPublisher<int> _publisher; public UIModel() { _number = 0; } public void CountNumber() { _number++; _publisher.Publish(_number); } }
LifetimeScope
ModelViewPresenterの登録をここで行っている。
また、MessagePipeもここで登録する必要がある。
public sealed class UILifetimeScope : LifetimeScope { [SerializeField] private UIView uiView; protected override void Configure(IContainerBuilder builder) { var options = builder.RegisterMessagePipe(); builder.Register<UIPresenter>(Lifetime.Singleton); builder.Register<UIModel>(Lifetime.Singleton); builder.RegisterEntryPoint<UIPresenter>(Lifetime.Singleton); builder.RegisterEntryPoint<UIModel>(Lifetime.Singleton); builder.RegisterComponent(uiView); } }
完成
ボタン押したら数が増えるよ

まとめ
ひとまず動きはしましたが、PresenterがViewもModelも持っているので、だったらeventなり、ObservableなりでSubscribeすればええやんという話ではある。
といった感じで設計として微妙な気がするので、Message Pipe使ったもっといいユースケースがあるはず…
そこら辺は思いついたらまた記事書きます。