ADVENT CALENDAR

UniRx触ってみた話

By Bunny

これは AmusementCreators 2020 アドベントカレンダー の 3日目の記事です。

わからないなりにですが,Unityの結構有名なライブラリであるUniRxに触ってみたので,そこら辺の知識をまとめてみた記事になります.

UniRxとは

UniRxとは,Rx(Reactive Extentions)というC#用のライブラリをUnityでも扱えるようにしたものです.
RxはLINQのデータソースから情報を取得してくる仕組みをイベントや非同期処理に拡張したようなものです,簡単に言うとC#のイベントに便利機能を追加したものみたいなイメージでいいと思います.

どんなことに使えるの?

UniRxを使ってみて,任意のタイミングや条件を満たす場合のみ実行したい処理が組みやすくなるんじゃないかなと思いました.
Unityでは上記のような処理をコルーチンやInvokeを使うことで実現することができますが,コルーチンはMonoBehaviorを継承する必要があったり,Invokeは一定時間待つことに特化していたりと使いにくい場面があると思います.
UniRxでは時間やタイミング,条件等を操作するようなオペレータが予めたくさん揃っているため,上記に書いたような処理を簡潔に書くことができます.

導入方法

アセットストアGitHubからDLしてくることで利用できます.
usingでUniRxを指定して使用します.

using UniRx;

使用例

お試しでUnityのボタン機能を使い,クリックした回数をカウントするボタンを制作してみました.

普通に書くと

	[SerializeField] Button button;
    [SerializeField] Text ClickCountText;
    private int count;

    void Start()
    {
        count = 0;
        button.onClick.AddListener(() => ClickCount(1));
    }

    void ClickCount(int num)
    {
        count += num;
        ClickCountText.text = count.ToString();
    }

こんな感じに書きますが,UniRxを使うと以下のように書くことができます.

	[SerializeField] Button button;
    [SerializeField] Text ClickCountText;

    void Start()
    {
         button.OnClickAsObservable() //ボタンを押したとき
            .Select(i => 1) 
            .Scan((prev,i)=>prev+i) //メッセージの値を前回の結果に加算する
            .Subscribe(i => ClickCountText.text = i.ToString())
            .AddTo(this);
    }

OnClickAsObservableがボタンを押したときに発火するイベントみたいなものです.
SelectやScanがイベントが発火した際に呼ばれるオペレータです,Selectで送るメッセージの値を決定し,Scanで前回までの値に加算することでクリック数のカウントを行っています.
Subscribeは受け取ったメッセージの値を使った処理を記述する部分です,今回だとクリック回数をUIのテキストに流し込む処理を行っています. AddToは例外を防ぐ目的で入れています.

使用例2

さて,ここまでではあまりUniRxを採用する利点があまりないように思います.ほんまか??
確かに見た目はスッキリするけどコード量はあまり変わらないし,普段使っているであろうUnityの文法ともちょっと離れているので学習コスト的にも高いし……みたいなかんじで.

UniRxが真価を発揮するのはこういった機能に仕様変更等のちょっとしたひと手間を加えたいときだと思います.
前述したようにUniRxにはたくさんのオペレータがあるので,あとから仕様変更を加えようとしたときにもオペレータを追加するだけで済むみたいな場面が結構ありそうです.

試しに先程実装したクリックした回数をカウントするボタンを,連続クリックできないように仕様変更したいと思います.
追加する仕様としては,「1回クリックしたらそこから1秒間はクリックしても回数を加算しないようにする」です.

普通に書くと,

	[SerializeField] Button button;
    [SerializeField] Text ClickCountText;
    private int count;
    private bool IsReady;

    void Start()
    {
        IsReady = true;
        count = 0;
        button.onClick.AddListener(() => StartCoroutine(Clickcount(1)));
    }

    IEnumerator ClickCount(int num)
    {
        if (IsReady)
        {
            count += num;
            ClickCountText.text = count.ToString();
            IsReady = false;
            yield return new WaitForSeconds(1.0f);
            IsReady = true;
        }
    }

このように1秒待つという仕様を達成するためにコルーチンを新たに導入したり,フラグを増やす必要があります.

UniRxではどうでしょうか

	[SerializeField] Button button;
    [SerializeField] Text ClickCountText;

    void Start()
    {
         button.OnClickAsObservable() //ボタンを押したとき
         	.ThrottleFirst(TimeSpan.FromSeconds(1.0f)) //値が来たら一定時間メッセージを遮断する
            .Select(i => 1) 
            .Scan((prev,i)=>prev+i)  //メッセージの値を前回の結果に加算する
            .Subscribe(i => ClickCountText.text = i.ToString())
            .AddTo(this);
    }

なんと,「一回メッセージを通したあとに一定時間メッセージを遮断する」というThrottleFirstオペレータがあるので1行追加するだけで済みます.
ぱっと触って見ただけですが,条件を満たすものだけ通すWhereオペレータや最初の一回だけ通すFirstオペレータなど汎用性の高いオペレータがたくさんあるので.あとから機能を追加するとかちょっと凝った処理にしたい場合等に強いと思いました.

まとめ

触ってみてわかりましたが,オペレータの種類がとても豊富で自分で触れられている機能もUniRxのほんの一部に過ぎないみたいです.想像していた以上にできることの幅が広い…….
非同期処理やエラー時の例外処理等もっといろいろなことがUniRxではできるようです.
調べるとたくさん情報が出てくるので,興味があればぜひ触ってみてください.

参考サイト

オペレータ逆引き

SHARE THIS POST