悲哀
例の「年収4万で廃業」の話題を知りインディゲーム開発者としての悲哀を感じるより先にSNSでちょっとした話題になったことを羨ましく感じた狭量な皆さんこんにちは。例の方はゲームリリース時も一部SNSで話題になり、有名なゲーム実況者にも取り上げられていたので「実力不足」云々を嘆く前にSNSでバズる力があることを誇った方が良いと思います。それから開発者でもないただのゲームレビュアーがこの件に関して動画のネタにしているは非常に不快です。
Observerパターン
Observerパターンとはデザインパターンの一種で、プログラム内で発生したイベント(事象)を他のオブジェクトへ通知する形で処理が行われます。
Observerパターン
近年では通知者(Subject)と受信者(Observer)の1対1もしくは1対多といった直接的な関係を利用するのではなく、両者の間に仲介者(Broker)を置くことで通知者(Subject)と受信者(Observer)がより疎結合の状態となるよう工夫されたPub/Subパターンと呼ばれる手法が多く利用されているようです。
Pub/Subパターン
この場合、各々のモジュールは以下の通りとなります。
- 「Publisher」は、発生した事象をBrokerに通知する
- 「Broker」は、Publisherから受けた通知をSubscriberへ通知
- 「Subscriber」は、Brokerから通知を受信して、それに応じた処理を行う
これによりPublisher、Subscriber双方の仕様相違や更改による影響などは全てBrokerの中で吸収することができるため、より柔軟なメッセージ伝達が可能となります。
MessagePipe
このようなパターンの処理をシンプルな記述で実装するには今回紹介する「MessagePipe」を利用する事が最適です。
MessagePipeはUnityで使えるハイパフォーマンスのPub/Subパターンの実装を提供するライブラリで、GitHubにて公開されています。
ただ、MessagePipeの導入はPackageManagerよりUPM経由で導入すると手早く導入することが出来ます。UnityのメニューからProjectSettingsを開いて、次のようにOpenUPMに対するURLを追加します。
- Name: OpenUPM
- URL: https://package.openupm.com
- Scopes:
com.cysharp
jp.hadashikick.vcontainer
するとPackageManagerから「My Registries」からインストールが利用可能となるので「MessagePipe」を選択します。
注意する点として、MessagePipeはDIライブラリ前提で組まれている為、導入に当たってはVContainerが必要です。また、UniRxも必須なのでそれらが未導入の場合は、PackageManagerの中から「MessagePipe.VContainer」「VContainer」「UniTask」を選び、事前に導入しておいてください。
基本的な使い方
簡単な使用例として、キーボードの矢印キーが押下された回数をそれぞれカウントする処理を考えます。
この場合、「Publisher」はキーの入力を受け付けて、そのキーコードをBroker経由で「Subscriber」へ送信します。
「Subscriber」は受信したキーコードを判定してキー押下の回数を更新し、画面へ表示します。
■Publisher側の処理
Publisherは入力されたキーコードをBrokerに向けて通知するので、そのインターフェースを最初に定義します。
VContainerを利用するのでコンストラクタでそのインターフェースをVContainer側から受け取り、キーコードの送信処理でこのインターフェースを利用します。
using UnityEngine; using MessagePipe; using VContainer.Unity; using System; public sealed class TestInputEventProvider : ITickable { /// <summary> /// MessagePipeへのメッセージ送信用インタフェース /// </summary> private readonly IPublisher<KeyCode> _inputPublisher; public TestInputEventProvider(IPublisher<KeyCode> argPublisher) { _inputPublisher = argPublisher; } /// <summary> /// 毎フレーム実行 /// </summary> public void Tick() { if (Input.anyKeyDown) { // 押下キーコードを取得する foreach (KeyCode code in Enum.GetValues(typeof(KeyCode))) { if (Input.GetKeyDown(code)) { // メッセージを送信 _inputPublisher.Publish(code); break; } } } } }
■Subscriber側の処理
SubscriberはBrokerから受信する通知のインターフェースを定義します。SubscriberはMonoBehaviourを継承しますがコンストラクタでの注入を可能にするため[Inject]アトリビュートを付与しておきます。
ISubscriberのSubscribeで受け取り先の関数を定義しています。
using UnityEngine; using VContainer; using MessagePipe; using Cysharp.Threading.Tasks; public class TestSubscribeCounter : MonoBehaviour { /// <summary> /// MessagePipeからのメッセージ受信用インターフェース /// </summary> [Inject] private readonly ISubscriber<KeyCode> _inputSubscriber; // Start is called before the first frame update void Start() { // 入力イベントの受信を開始する _inputSubscriber.Subscribe(OnInputEventReceived) .AddTo(this.GetCancellationTokenOnDestroy()); } /// <summary> /// 入力イベント処理 /// </summary> /// <param name="code"></param> private void OnInputEventReceived(KeyCode code) { // キーコードを判定し、カウンタを更新する // 長くなるので割愛 } }
■DIの設定
Publisher,SubscriberともにVContainerにて依存性の注入を行う為、VContainerのLifetimeScopeを定義し、VContainerにPublisher,Subscriberのクラスと「Broker」役となるMessagePipeの登録を行います。
using VContainer; using VContainer.Unity; using MessagePipe; using UnityEngine; public class TestCountLifetimeScope : LifetimeScope { protected override void Configure(IContainerBuilder builder) { // MessagePipeの設定 var options = builder.RegisterMessagePipe(); builder.RegisterMessageBroker<KeyCode>(options); // InputEventProviderを起動 builder.RegisterEntryPoint<TestInputEventProvider>(Lifetime.Singleton); // ヒエラルキー上でTestSubscribeCounterがアタッチされているものを登録 builder.RegisterComponentInHierarchy<TestSubscribeCounter>(); } }
Unityのシーン内に空のオブジェクトを作り、前述したLifetimeScopeのコンポーネントをアタッチしておきます。このアタッチされたGameObjectが起点となって「Publisher」「Subscriber」の初期処理が実行されます
これらを動作させた結果が以下の動画となります。
キー押下のイベント通知が行われ、UIテキストが更新されているのが分かります。
まとめ
MessagePipeを使用するとシンプルな処理で疎結合なObserverパターンが実装できるのが分かりました。
今回は紹介してませんがメッセージのハンドリングは「非同期」にできたり、「多対多」も実現できるようです。
Observerパターンはゲーム製作では結構な頻度で使用するので、MessagePipeの導入と使い方を覚えておいても損はないと思います。