Loading...

【Unity】MessagePipeの基本的な使い方についてまとめる

Unity
マイケル
マイケル
みなさんこんにちは!
マイケルです!
エレキベア
エレキベア
こんにちクマ〜〜〜
マイケル
マイケル
今日はUnityのメッセージングライブラリである、
MessagePipe の使い方について紹介します!
エレキベア
エレキベア
最近よく聞くやつクマね
マイケル
マイケル
UniRxと結局何が違うのか、といった点も踏まえてみていくので
気になっている方もぜひご参照ください!
エレキベア
エレキベア
やったるクマ〜〜〜
スポンサーリンク

MessagePipeとは

マイケル
マイケル
まずMessagePipeとは何かについてですが、こちらはCysharpさんが公開しているUnity用のメッセージングライブラリになります!

GitHub – Cysharp/MessagePipe

MessagePipe – .NET と Unityのためのハイパフォーマンスメッセージングライブラリ

マイケル
マイケル
これはいわゆる Publish/Sublish(Pub/Sub)パターン と呼ばれるもので、
下記のようにメッセージを送る仲介者の存在を知らずにやり取りを行うことができます。
ScreenShot 2022 10 15 10 22 37↑Pub/Subパターン
エレキベア
エレキベア
ObserverパターンやUniRxだと基本的に発行者への依存が発生してしまうクマから、
お互いを完全に知らずにやり取りできるのは疎結合になってよさそうクマね
マイケル
マイケル
ただその分、どんなに離れていてもやり取りできてしまうから、
むやみに使いすぎるとどこでやり取りしているのかの管理が大変になってしまう点には注意が必要だね!
エレキベア
エレキベア
とりあえずこのPub/SubパターンをUnityで実装できるという点が
MessagePipeの利点なのクマね
マイケル
マイケル
ちなみにUniRxにも同じようにPub/Subパターンを実装できる
MessageBrokerという機能があるんだ。

UniRxのMessageBrokerが便利という話 – Qiita

エレキベア
エレキベア
え、UniRxにもあるクマか・・・
ならわざわざMessagePipeを使う理由って何なのクマ・・・
マイケル
マイケル
MessagePipeはDIコンテナの導入が前提になっているから、
DIコンテナを通すことである程度メッセージできる範囲を制御できるというのが大きなメリットかなと思っているよ。
これでさっき話したどこでも呼び出せるという問題が軽減されるからね。
エレキベア
エレキベア
なるほどクマ
DIコンテナとも連携してPub/Subを使いたい場合に有効なのクマね
マイケル
マイケル
まあそもそもUniRxは多機能すぎるから、シンプルに使いたいというだけでもいいかもね!
それじゃ早速使い方をみていこう!
エレキベア
エレキベア
楽しみクマ〜〜〜

MessagePipeの使い方

導入方法

マイケル
マイケル
それでは早速導入していきます。
導入方法は今後変更される可能性もあるため、基本的には最新のREADMEを参考に進めましょう!

GitHub – Cysharp/MessagePipe#Unity

マイケル
マイケル
まず、MessagePipeを使用するにあたり
・DIライブラリ(VContainer or Zenject)
・UniTask

の導入が前提になっているため、これらをUnityにインストールします。
今回はVContainerを使用して進めました。

インストール | VContainer

GitHub – Cysharp/UniTask

エレキベア
エレキベア
もうもはや使用するのが定番になっているクマからね
マイケル
マイケル
準備ができたら、MessagePipeのライブラリを入れていきます。
こちらはCoreとなるライブラリと使用するDIライブラリに対応したライブラリを入れる必要があります。
今回は下記の2つが対象になりますね。

・Coreライブラリ
https://github.com/Cysharp/MessagePipe.git?path=src/MessagePipe.Unity/Assets/Plugins/MessagePipe

・VContainer用
https://github.com/Cysharp/MessagePipe.git?path=src/MessagePipe.Unity/Assets/Plugins/MessagePipe.VContainer

マイケル
マイケル
これらを導入すれば、MessagePipeを使用する準備は完了です!!
エレキベア
エレキベア
やったるクマ〜〜〜

基本的な使い方

マイケル
マイケル
まずは基本的な使い方をみていきましょう。
LifetimeScopeでのメッセージ登録は、下記のようにoptionを作成してRegisterMessageBrokerで登録します。
今回はint型のメッセージを登録していますが、独自クラスでも指定することができます。
using MessagePipe;
using VContainer;
using VContainer.Unity;
public class TestLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        // int型のメッセージを登録してみる
        var options = builder.RegisterMessagePipe();
        builder.RegisterMessageBroker<int>(options);
        // EntryPoint登録
        builder.RegisterEntryPoint<TestEntryPoint>();
    }
}
↑int型のメッセージを登録
マイケル
マイケル
あとは発行側(Publisher)、購読側(Subscriber)のイベントそれぞれを受け取ってメッセージング処理を実装します。
使い方としては下記のように、Subscriverに受け取った際の処理を記述し、Publisherでイベントを発行する、といったものになります。
using System;
using Cysharp.Threading.Tasks;
using MessagePipe;
using UnityEngine;
using VContainer.Unity;
public class TestEntryPoint : IStartable, IDisposable
{
    private readonly IPublisher<int> _testPublisher;
    private readonly ISubscriber<int> _testSubscriber;
    private IDisposable _disposable;
    public TestEntryPoint(IPublisher<int> testPublisher, ISubscriber<int> testSubscriber)
    {
        _testPublisher = testPublisher;
        _testSubscriber = testSubscriber;
    }
    public void Start()
    {
        // 購読開始
        var d = DisposableBag.CreateBuilder();
        _testSubscriber.Subscribe(x =>
        {
            Debug.Log(x); // とりあえずログ出力
        }).AddTo(d);
        _disposable = d.Build();
        // 試しに発行
        TestPublishAsync();
    }
    public void Dispose()
    {
        // 購読解除
        _disposable?.Dispose();
    }
    private async void TestPublishAsync()
    {
        // 一定間隔で発行する
        _testPublisher.Publish(10);
        await UniTask.Delay(500);
        _testPublisher.Publish(20);
        await UniTask.Delay(500);
        _testPublisher.Publish(30);
    }
}
↑簡単なPub/Sub実装
マイケル
マイケル
Subscriberに購読処理を設定した際にはDisposableも設定して、必ず購読解除の処理も実装するようにしましょう。
エレキベア
エレキベア
非同期処理のお約束クマね
マイケル
マイケル
この状態でLifetimeScopeをアタッチして実行すると、
正常にメッセージング処理が行われることが確認できました!
01 test↑メッセージが送られることを確認
エレキベア
エレキベア
シンプルで使いやすいクマね

攻撃通知を送るサンプル

マイケル
マイケル
ここからもう一歩踏み込んで、攻撃通知を送るサンプルを作成してみます。
下記のような攻撃データをやり取りする例になります。
/// <summary>
/// 攻撃データ
/// </summary>
public class AttackData
{
    /// <summary>
    /// ダメージ量
    /// </summary>
    public int Damage;
}
↑送信する攻撃データ
エレキベア
エレキベア
ゲームではよくある実装クマね
マイケル
マイケル
プレイヤーを発行者(Publisher)、エネミーを購読者(Subscriber)として、エネミー全体へ攻撃通知するといった内容で実装します。
using MessagePipe;
/// <summary>
/// プレイヤークラス
/// </summary>
public class Player
{
    private readonly IPublisher<AttackData> _onDoAttackEvent;
    public Player(IPublisher<AttackData> onDoAttackEvent)
    {
        _onDoAttackEvent = onDoAttackEvent;
    }
    /// <summary>
    /// 攻撃実行
    /// </summary>
    public void OnAttack(int damage)
    {
        _onDoAttackEvent.Publish(new AttackData()
        {
            Damage = damage,
        });
    }
}
↑プレイヤーから攻撃イベントを発行する
using System;
using MessagePipe;
using UnityEngine;
/// <summary>
/// エネミークラス
/// </summary>
public class Enemy : IDisposable
{
    private readonly ISubscriber<AttackData> _onReceiveAttackEvent;
    private IDisposable _disposable;
    private string _name;
    private int _hp;
    private bool _isDead;
    public Enemy(ISubscriber<AttackData> onReceiveAttackEvent, string name, int hp)
    {
        _onReceiveAttackEvent = onReceiveAttackEvent;
        _name = name;
        _hp = hp;
        _isDead = false;
        OnInitialize();
    }
    /// <summary>
    /// 初期化処理
    /// </summary>
    private void OnInitialize()
    {
        // 攻撃通知イベントの購読開始
        var d = DisposableBag.CreateBuilder();
        _onReceiveAttackEvent.Subscribe(attackData =>
        {
            if (_isDead) return;
            // ダメージを受ける
            _hp -= attackData.Damage;
            Debug.Log($"{_name} は {attackData.Damage} のダメージを受けた");
            // 死亡判定
            if (_hp <= 0)
            {
                _hp = 0;
                _isDead = true;
                Debug.Log($"{_name} は死亡した...");
            }
        }).AddTo(d);
        _disposable = d.Build();
    }
    public void Dispose()
    {
        _disposable?.Dispose();
    }
}
↑エネミーは攻撃イベントを受け取ったらダメージを受ける
エレキベア
エレキベア
使い方としてはさっきの例と変わらないクマね
マイケル
マイケル
あとはLifetimeScopeにメッセージを登録して、プレイヤーやエネミーの生成処理を書けば完了です!
今回は下記のようにしてみました。
using MessagePipe;
using VContainer;
using VContainer.Unity;
public class BattleLifeTimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        // 攻撃通知のメッセージを登録
        var options = builder.RegisterMessagePipe();
        builder.RegisterMessageBroker<AttackData>(options);
        // EntryPoint登録
        builder.RegisterEntryPoint<BattleEntryPoint>();
    }
}
↑メッセージの登録
using System.Collections.Generic;
using MessagePipe;
using UnityEngine;
using VContainer.Unity;
public class BattleEntryPoint : IStartable, ITickable
{
    private readonly IPublisher<AttackData> _onDoAttackEvent;
    private readonly ISubscriber<AttackData> _onReceiveAttackEvent;
    public BattleEntryPoint(IPublisher<AttackData> onDoAttackEvent, ISubscriber<AttackData> onReceiveAttackEvent)
    {
        _onDoAttackEvent = onDoAttackEvent;
        _onReceiveAttackEvent = onReceiveAttackEvent;
    }
    private Player _player;
    private List<Enemy> _enemies;
    public void Start()
    {
        // プレイヤー生成
        _player = new Player(_onDoAttackEvent);
        // エネミー生成
        _enemies = new List<Enemy>();
        _enemies.Add(new Enemy(_onReceiveAttackEvent, "エレキベア", 200));
        _enemies.Add(new Enemy(_onReceiveAttackEvent, "ゴロヤン", 500));
        _enemies.Add(new Enemy(_onReceiveAttackEvent, "パインバード", 100));
    }
    public void Tick()
    {
        // スペースキー押下で攻撃
        if (Input.GetKeyDown(KeyCode.Space))
        {
            _player.OnAttack(100);
        }
    }
}
↑プレイヤー、エネミーの生成処理
マイケル
マイケル
LifetimeScopeをアタッチして何回か攻撃してみます。
正常に攻撃通知できていることが確認できますね!
02 battle↑攻撃通知も上手くいっている
エレキベア
エレキベア
これでバッチリクマ〜〜〜

おわりに

マイケル
マイケル
というわけでMessagePipeの使い方でした!
どうだったかな??
エレキベア
エレキベア
シンプルで使いやすくていいライブラリだと思ったクマ〜〜〜
マイケル
マイケル
Pub/Sub実装は手軽に使えるから今後使っていきたいね!
それでは今日はこの辺で!アデューー!!!
エレキベア
エレキベア
クマ〜〜〜〜〜

【Unity】MessagePipeの基本的な使い方についてまとめる 〜完〜

コメント