【Unity】MessagePipeのScopeについて2
前回はDIContainerごとに異なるスコープを持つことがわかりました。(Zenject使用時
今回は実際にMessagePipeのBroker, Scope, Lifetimeについて調べながら書いていこうと思います
(Readmeが丁寧に書かれているのでここを読むのが一番早いですが..!)
- Unity
- Zenject
- MessagPipe v1.6.1
Broker
Publisher, Subscriber はDI毎に管理され、スコープごとに異なるBrokerを持てる。
そして、スコープが破棄されるときにすべての購読も解除されるためリーク防止となる。 (Readmeから)
イベントは BindMessageBroker
メソッドで登録をします。
そこでDIContainer毎に異なるBrokerが生成され、DIContainerが破棄されるときにすべての購読も解除されます。
ちなみに同じDIContainerに対して同じ型は一度だけしか登録できないので以下のようにするとエラーが出ます
var option = _container.BindMessagePipe();
// 同じ型の二重登録
_container.BindMessageBroker<MyEvent>(option);
_container.BindMessageBroker<MyEvent>(option);
BindMessageBrokerの引数に指定するoptionの中には
生存範囲を決める 'InstanceLifetime' というものを決めれました。
InstanceLifetime
Singleton
Scope
しかしZenjectをDIContainerに指定している場合はZenjectの実装上Singletonは選択できない。そうです(コードを見るとSingletonを指定していても必ずScopeになるようになっていました)
つまりZenjectで使用する場合はZenjectで言うところの AsSingle 的な使い方はできず、イベントは常にScopeとともに生き死をともにする。
ということかな? と
ゲーム全体へのイベント通知
MessagePipeを調べていくうちに 「ゲーム全体にイベントを投げたいときはどうするの?」
という疑問がわきました。
例えば、何かしら通知を行うクラスを考えてみます。
// 通知用 public class NoticeEvent { public string Message; }
そしてManager的なところでNoticeEventを受け取ってごにょごにょするような以下のSubscriberを想定
// NoticeEventを受け取る subscriber.Subscribe(ev => { var message = ev.Message; // UIに表示したり // デバッグでコンソールに送ったり // 通信でログ送ったり .... };
複数のDIContainerが存在している場合(Zenject的に言うとSceneContextが複数)、あるシーンから別のシーンに通知イベントを投げたい。
しかし、前回テストしたように通知を受け取るためには同じDIContainerを使用しなければならない。
そこでこのときに使用できるのが GlobalMessagePipe
その名の通りグローバルな使い方ができるMessagePipeであると。
とりあえず使ってみます。
使用する前に SetProvider メソッドでDIContainerから取得したServiceProviderを設定する必要があります。
GlobalMessagePipe.SetProvider(_container.AsServiceProvider());
一度 GrobalMessagePipe に登録すると、あとはこのクラスの静的メソッドを利用して Publisher と Subscriber を取得できます。
// どこかで受ける var subscriber = GlobalMessagePipe.GetSubscriber<NoticeEvent>(); subscriber.Subscribe(ev => { var message = ev.Message; // UIに表示したり // デバッグでコンソールに送ったり // 通信でログ送ったり .... };
そして送る方
// どこかで送る var publisher = GlobalMessagePipe.GetPublisher<NoticeEvent>(); publisher.Publish(new NoticeEvent { Message = "テストログ" });
静的メソッドからイベントの受信と発行ができるので場所を問うことはなくなりました。
GlobalMessagePipe.SetProvider
SetProvder に渡している _container.AsServiceProvider()
こちらですが、どのContainerを渡すべきか
GlobalMessagePipe.SetProvider(_container.AsServiceProvider());
マルチシーン開発であれば削除されないManager的なシーンについているSceneContextのContinerから取得してもいいと思います。
一般的には ProjectContext を使用するのが良いかなと。
ProjectContextはゲーム全体に常駐するContextであり、Globalに設定するには適切なものであると考えました
つまり、ProjectContext.prefab を作成したあと以下のMonoInstallerスクリプトを作成してそのPrefabにアタッチ
using Zenject; namespace XXX { public class ProjectContextInstaller : MonoInstaller { public override void InstallBindings() { var option = Container.BindMessagePipe(); // Option設定変更するならここ GlobalMessagePipe.SetProvider(Container.AsServiceProvider()); // NoticeEventをBind Container.BindMessageBroker<NoticeEvent>(option); ... あとその他 } } }
あとはGlobalMessagePipeから Publish, Subscribe を取得してゲーム全体でイベント発行!!
// Manager的な人がゲーム全体で発行されたNoticeEventを管理する public class Manager : Monobehaviour { public void Awake() { var subscriber = GlobalMessagePipe.GetSubscriber<NoticeEvent>(); subscriber.Subscribe(ev => { UnityEngine.Debug.Log(ev.Message); }); } }
で、送る
// バトルシーン public class BattleScene { public void Damage() { var publisher = GlobalMessagePipe.GetPublisher<NoticeEvent>(); publisher.Publish(new Common.NoticeEvent { Message = "ダメージ発生した" }); } }
これだけでイベント配達ができました。
ものすごく楽
終わりに
今回はDisposeやフィルター機能などは省いています。
MessagePipeはただのイベント発行だけではなく、Disposeやフィルター機能、Asyncによる非同期待機処理などの便利機能の価値が高いものと思います。
むしろその機能を使いこなせたとき更にゲーム開発効率が上がると信じています。
また少しずつ調べた内容を記事化していきMessagePipeをもっと便利に使いこなせるようにしていきたいと思います