原カバンは鞄のお店ではありません。

Unityを使ったゲーム制作のあれこれを綴っていきます。

【Unity】Observerパターンの構築に役立つMessagePipe

悲哀

例の「年収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にて公開されています。

github.com

ただ、MessagePipeの導入はPackageManagerよりUPM経由で導入すると手早く導入することが出来ます。UnityのメニューからProjectSettingsを開いて、次のようにOpenUPMに対するURLを追加します。



すると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の導入と使い方を覚えておいても損はないと思います。

◇プライバシーポリシー

●個人情報の利用目的

当ブログでは、メールでのお問い合わせ、メールマガジンへの登録などの際に、名前(ハンドルネーム)、メールアドレス等の個人情報をご登録いただく場合がございます。

これらの個人情報は質問に対する回答や必要な情報を電子メールなどをでご連絡する場合に利用させていただくものであり、個人情報をご提供いただく際の目的以外では利用いたしません。

●個人情報の第三者への開示

当サイトでは、個人情報は適切に管理し、以下に該当する場合を除いて第三者に開示することはありません。

・本人のご了解がある場合
・法令等への協力のため、開示が必要となる場合

個人情報の開示、訂正、追加、削除、利用停止
ご本人からの個人データの開示、訂正、追加、削除、利用停止のご希望の場合には、ご本人であることを確認させていただいた上、速やかに対応させていただきます。

アクセス解析ツールについて

当サイトでは、Googleによるアクセス解析ツール「Googleアナリティクス」を利用しています。

このGoogleアナリティクスはトラフィックデータの収集のためにCookieを使用しています。このトラフィックデータは匿名で収集されており、個人を特定するものではありません。
この機能はCookieを無効にすることで収集を拒否することが出来ますので、お使いのブラウザの設定をご確認ください。

●免責事項

当サイトからリンクやバナーなどによって他のサイトに移動された場合、移動先サイトで提供される情報、サービス等について一切の責任を負いません。

当サイトのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、誤情報が入り込んだり、情報が古くなっていることもございます。

当サイトに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。

●プライバシーポリシーの変更について

当サイトは、個人情報に関して適用される日本の法令を遵守するとともに、本ポリシーの内容を適宜見直しその改善に努めます。

修正された最新のプライバシーポリシーは常に本ページにて開示されます。