Unity
UniRx

UniRxのMessageBrokerが便利という話

More than 1 year has passed since last update.

概要

UniRxのMessageBrokerが個人的に結構便利だなと思っていて使っています。という話。
自分で使ってみてのScriptも添えて。

MessageBrokerとは

UniRx ( https://www.assetstore.unity3d.com/jp/#!/content/17276 )で使えるRxベースのインメモリPubSubです

作者のneueccさんのページにサンプルのコードがありますね。
http://neue.cc/2016/08/03_536.html

使い方

(作者さんのサンプルコードをお借りします)
1000という値を任意のタイミングで送信する時のMessageBrokerの使い方。

// こんな型があるとして
public class TestArgs
{
    public int Value { get; set; }
}


値を送信する側

MessageBroker.Default.Publish(new TestArgs { Value = 1000 });

・任意のタイミングでPublishして良い。キーが押されたらとか。


値を受信する側

MessageBroker.Default.Receive<TestArgs>().Subscribe(x => UnityEngine.Debug.Log(x));

・基本的に、StartやAwakeなどで受信する側のコードを走らせて置くと良いと思います。
・一度ReceiveのSubscribeが始まるとその後はその型の情報がPublishされるたびにSubscribe()で書いた処理が走るので特に何もなければ最初に宣言しておいて良さそう。


注意点
・1000を送信するだけならPublish(1000)で良いのでは?
→その通りで、それでも良い。ただ、Receive側では型で受け取るものを決めているので、もし他にint型の何かを送る時は誤ってそれもReceiveしてしまう。
→その為上記のようにClassを宣言してそこに値を詰め込んでインスタンス化して投げるというのがそういった間違いがなくて良いと思う。

応用例

AnimationTriggerで与える文字列から、そのトリガーを元に複数のComponentが処理を開始するサンプル。
CropperCapture[428].png

送信側

Scenario.cs
using UnityEngine;
using UniRx;
using System;

public class Scenario
{
    public enum Title { Opening, City, Road ,Castle, Ending }
    public Title title;
    public int index;

    public Scenario(Title title,int index)
    {
        this.title = title;
        this.index = index;
    }
}
public class ScenarioTriggerSender : MonoBehaviour
{
    //"Ending:0"といった文字列を受取り、それをパースしてScenarioクラスのインスタンスをPublish
    public void ScenarioTriggerSend(string scenarioString)
    {
        var q = scenarioString.Split(':');
        var title = (Scenario.Title)Enum.Parse(typeof(Scenario.Title), q[0], true);
        var index = 0;
        if (q.Length > 1) {
            index = int.Parse(q[1]);
        }
        MessageBroker.Default.Publish<Scenario>(new Scenario(title,index));
    }
}

受信側

Example1.cs
        void Start()
        {
            //Openingの場面0では音を鳴らし始め、場面1になったらそれを止める
            MessageBroker.Default.Receive<Scenario>().Where(s => s.title == Scenario.Title.Opening).Subscribe(s =>
            {
                if (s.index == 0)
                {
                    audioSource.Play();
                }
                if (s.index == 1)
                {
                    audioSource.Stop();
                }
            });
        }

このスクリプトの他にも「Openingの場面0で処理を行いたいもの」、「場面2で処理を行いたいもの…」といった感じで各々のGameObjectのComponentがPublishを受け取ってSubscribeし、処理を走らせています。

つまり全体としては、AnimationからTriggerを送ってそれを複数のComponentが受け取ってそのタイミングで処理を走らせているということですね。
例えば少しOpeningの場面1をずらしたいなって時はAnimationTriggerの位置をずらせばそのタイミングでPublishが走るので、後は関連する全ての処理がそこで走ることになります。楽に開発できますね。

終わりに

Unityでは「何かのトリガーを受け取って処理を行う」といったComponentを持つオブジェクトが動的に増えていくときに便利なのではないかと思ってます。
それはランタイムの話でもありますが、開発時に(このトリガーがどの程度の範囲で使われるかわからないなー)ってときにも同じことがいえます。また、使い方によってはComponent同士の依存性なんかもある程度は紐解きやすくなるのではないかなと思っています。


自分が使っている限りでは、MessageBrokerを使っている部分はDelegateで置き換えることができるので個人のお好みで使いわけしていく感じで良いと思います。

自分は、
・Publish,Subscribeという概念を元に構築するのでどれが何を送信するか(受信するか)ということをしっかり意識しながらコードを書けるのが個人的に分かりやすい
・受信者と配信者が互いのこと知らなくていいのが便利。
・Delegateでは開発中に引数や返り値を増やしたりとコードを変更するとそのトリガーを使ってる部分全てに手を入れる必要が出てくる
・Streamとして扱える。(これはあまり活用できてないですが…)
などの理由で、こちらを積極的に使っているという次第です。