UniRx入門　~ データバインディングとUnityイベント関数の購読 ~

[UniRx（github）](https://github.com/neuecc/UniRx)
[UniRx（アセットストア）](https://www.assetstore.unity3d.com/jp/#!/content/17276)

# はじめに
UniRxはRxをUnity用に移植した素晴らしいライブラリ（アセット）ですが、Rx自体の学習コストの高さから導入が見送られるケースが多い気がします。

この記事では学習コストが高く理解が必要な機能については省き、使い方を覚えるだけで簡単かつ便利に使える強力な機能について紹介します。


# `ReactiveProperty<T>`でデータバインディング
`ReactiveProperty<T>`を使うと値の変化が通知されてきます。これを使うと例えば「HPが減ったらビューを自動更新」みたいなことができます。

`Text[] _texts`にはInspectorから適当にuGUIのTextコンポーネントをセットしてください。

```DataBinding1.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class DataBindingSample1 : MonoBehaviour
{
    // NOTE: newするときに初期値を与えることが可能
    public ReactiveProperty<int> _intProperty = new ReactiveProperty<int>(1);
    public ReactiveProperty<float> _floatProperty = new ReactiveProperty<float>(0.1f);
    public ReactiveProperty<bool> _boolProperty = new ReactiveProperty<bool>(true);
    public ReactiveProperty<string> _stringProperty = new ReactiveProperty<string>("ABC");

    [SerializeField]
    private Text[] _texts;

    void Start()
    {
        // バインド（購読）する
        _intProperty.SubscribeToText(_texts[0]);
        _floatProperty.SubscribeToText(_texts[1]);
        _boolProperty.SubscribeToText(_texts[2]);
        _stringProperty.SubscribeToText(_texts[3]);
    }

    void Update()
    {
        // それぞれの値を適当に更新してみる
        _intProperty.Value++;
        _floatProperty.Value += 0.1f;
        _boolProperty.Value = !_boolProperty.Value;
        _stringProperty.Value += "a";
    }
}
```

使い方は簡単で、`ReactiveProperty<T>`をnewしてValueに値をセットすることで実際の値を更新、値が更新されると`SubscribeToText(Text text)`で与えたtextが更新されます。

`SubscribeToText(Text text)`は`IDisposable`を返すので、好きなタイミングで`Dispose()`を呼び出してバインドを解除します。

## 番外編：`SubscribeToText(TextMeshProUGUI text)`ないの？
* [TextMesh Pro](https://www.assetstore.unity3d.com/jp/#!/content/84126)

「UnityのTextコンポーネントなら`SubscribeToText(text)`が使えるけどTextMeshProUGUIだと`Subscribe(value => t.text)`って書くのめんどくさい」という場合は自分で実装しましょう。

以下コード。

```UnityUIComponentExtensions.cs
using System;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

namespace UniRx
{
    public static partial class UnityUIComponentExtensions
    {
        public static IDisposable SubscribeToText(this IObservable<string> source, TextMeshProUGUI text)
        {
            return source.SubscribeWithState(text, (x, t) => t.text = x);
        }

        public static IDisposable SubscribeToText<T>(this IObservable<T> source, TextMeshProUGUI text)
        {
            return source.SubscribeWithState(text, (x, t) => t.text = x.ToString());
        }

        public static IDisposable SubscribeToText<T>(this IObservable<T> source, TextMeshProUGUI text, Func<T, string> selector)
        {
            return source.SubscribeWithState2(text, selector, (x, t, s) => t.text = s(x));
        }
    }
}
```

TextMeshProUGUI以外で使っているTextがあればクラスの記述を置き換えてもらえればOKです。

余談ですが自分は無料になる1週間前くらいに知らずにTextMeshPro買って悔しい思いしました。。。


## Inspectorから値をセット可能な`IntReactiveProperty`など
`ReactiveProperty<T>`だとジェネリックが使われているのでInspectorで値をセットしておけないのですが、int,float,bool,stringのそれぞれに特殊化されたReactiveProperty `IntReactiveProperty`,`FloatReactiveProperty`,`BoolReactiveProperty`,`StringReactiveProperty`を使えばInspectorで値をセットしておくことができます。

```DataBindingSample2.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class DataBindingSample2 : MonoBehaviour
{
    // NOTE: ジェネリックを使っていないので値をInspectorからセットできる
    public IntReactiveProperty _intProperty = new IntReactiveProperty();
    public FloatReactiveProperty _floatProperty = new FloatReactiveProperty();
    public BoolReactiveProperty _boolProperty = new BoolReactiveProperty();
    public StringReactiveProperty _stringProperty = new StringReactiveProperty();

    [SerializeField]
    private Text[] _texts;

    [SerializeField]
    private Button _button;

    void Start()
    {
        // バインド（購読）する
        _intProperty.Subscribe(value => _texts[0].text = value.ToString("D5"));
        _floatProperty.Subscribe(value => _texts[1].text = (value * 10).ToString("F1"));
        // NOTE: ButtonのInteractableにもバインドできる！
        _boolProperty.SubscribeToInteractable(_button);
        _stringProperty.Subscribe(value => _texts[3].text = value.Replace('a', 'z'));
    }

    void Update()
    {
        // それぞれの値を適当に更新してみる
        _intProperty.Value++;
        _floatProperty.Value += 0.1f;
        _boolProperty.Value = !_boolProperty.Value;
        _stringProperty.Value += "a";
    }
}
```

今度は`SubscribeToText(Text text)`ではなく`Subscribe(Action<T> OnNext)`を使ってtextにセットする前に値を加工してみました。引数がTextでないのでText以外にも値をバインドできます。

コード中にコメントで書いてありますが、`SubscribeToInteractable(Selectable selectable)`を使えばボタンのInteractableにバインドしたりできます。

## `IReadOnlyReactiveProperty<T>`インターフェース（2017/6/15修正）
これまでのコードだとReactiveProperty自体がpublicで公開されていてValueが外から更新される可能性がある状態でした。
今度は`IReadOnlyReactiveProperty`インターフェースを使って外からは値を更新できないようにしてみます。`ReactiveProperty<T>`は`IReadOnlyReactiveProperty<T>`を実装しているのでキャストして公開するだけです。

```DataBindingSample3.cs
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UniRx;

public class DataBindingSample3 : MonoBehaviour
{
    #region private ReactiveProperty

    [SerializeField]
    private IntReactiveProperty _intProperty = new IntReactiveProperty();

    [SerializeField]
    private FloatReactiveProperty _floatProperty = new FloatReactiveProperty();

    [SerializeField]
    private BoolReactiveProperty _boolProperty = new BoolReactiveProperty();

    [SerializeField]
    private StringReactiveProperty _stringProperty = new StringReactiveProperty();

    #endregion

    #region public IReadOnlyReactiveProperty

    public IReadOnlyReactiveProperty<int> IntProperty {
        get { return _intProperty; }
    }

    public IReadOnlyReactiveProperty<float> FloatProperty {
        get { return _floatProperty; }
    }

    public IReadOnlyReactiveProperty<bool> BoolProperty {
        get { return _boolProperty; }
    }

    public IReadOnlyReactiveProperty<string> StringProperty {
        get { return _stringProperty; }
    }

    #endregion

    [SerializeField]
    private Text[] _texts;

    [SerializeField]
    private Button _button;

    void Update()
    {
        // それぞれの値を適当に更新してみる
        _intProperty.Value++;
        _floatProperty.Value += 0.1f;
        _boolProperty.Value = !_boolProperty.Value;
        _stringProperty.Value += "a";
    }
}
```

`IReadOnlyReactiveProperty<T>`は以下のようにValueのセッターを持っていないのでpublicにしても外部から値を更新されません。

```csharp
public interface IReadOnlyReactiveProperty<T> : IObservable<T>
{
    T Value { get; }
    bool HasValue { get; }
}

public interface IReactiveProperty<T> : IReadOnlyReactiveProperty<T>
{
    new T Value { get; set; }
}

public class ReactiveProperty<T> : IReactiveProperty<T>, IDisposable, IOptimizedObservable<T>
{
    // 実装
}

```

基本的にはモデルがこんな風に`IReadOnlyReactiveProperty<T>`を公開して、ビュー側で`Subscribe()`する設計になると思います。


<font color="red">（2017/6/15追記）</font>
### `ReadOnlyReactiveProperty<T>`
「外部への公開は`ToReadOnlyReactiveProperty()`を呼んで`ReadOnlyReactiveProperty`として公開する（ｷﾘｯ」と書いてましたが、`ToReadOnlyReactiveProperty()`を呼ぶと内部で`new ReadOnlyReactiveProperty()`が走るので

* 同じものを`Subscribe()`しているつもりが挙動が変になる
* 参照のたびに`new`されるのでメモリとGC的に良くない

という良くないことが起こります（@toRisouP さんにtwitterで教えていただきました）。

<font color="red">（2017/6/15追記 ここまで）</font>


# `ObserveEveryValueChanged`を使ってReactivePropertyでない値を監視
「いまさら全部`ReactiveProperty<T>`に書き換えられないけど値の変更がPushされてくる機構はほしいなぁ」という場合は`ObserveEveryValueChanged`が使えます。

[UniRxの作者さんの解説](http://neue.cc/2015/04/13_510.html)を貼っておきます。

> 最後に、ObserveEveryValueChangedを紹介します。これは、ラムダ式で指定した値を変更のあった時にだけ通知するという、つまり変更通知のない値を変更通知付きに変換するという魔法のような（実際ベンリ！）機能です（実際は毎フレーム監視してるんで、ポーリングによる擬似的なPull→Push変換）
`
// watch position change
this.transform.ObserveEveryValueChanged(x => x.position).Subscribe(x => Debug.Log(x));
`
> これは監視対象がGameObjectの場合はDestroy時にOnCompletedを発行して監視を止めます。通常のC#クラス（POCO）の場合は、GCされた時に、同様にOnCompletedを発行して監視を止めるようになっています（内部的にはWeakReferenceを用いて実装されています）。ただのポーリングなので多用すぎるとアレですが、お手軽でベンリには違いないので適宜どうぞ。


# Unityが自動で呼び出すメソッドのイベント化
MonoBehaviourには決まったタイミングでUnityから呼び出されるイベント関数というものがたくさんあります。

一番よく使われていると思われる`Update()`を例にその使いづらさについて考えます。


```Player.cs
using UnityEngine;

public class Player : MonoBehaviour
{
    public bool isPoisoned;
    public bool isRegenerating;

    public int hp {
        get;
        private set;
    }

    void Start()
    {
        hp = 100;
    }

    void Update()
    {
        if (isPoisoned) {
            hp -= 1;
        }
        if (isRegenerating) {
            hp += 2;
        }
    }
}
```

上記のコードはクラスが肥大化したときにその複雑さに耐えきれなくなります。

というのは、1フレーム毎に呼び出してほしい処理が`Update()`内に何種類も書いてあるため、

* `Update()`が肥大化する
* 変数のスコープが大きくなる

からです。


## `UpdateAsObservable()`で毎フレーム起きるイベントを購読する
`UpdateAsObservable()`を使うと毎フレームイベントが飛んでくるので、購読することで処理をカプセル化してスコープを限定できます。

先の例だとLINQのように`Where()`でフィルタを挟むことでやりたいことが実現できます。`using UniRx.Triggers`を忘れずに。

```Player.cs
using UnityEngine;
using UniRx;
using UniRx.Triggers;

public class Player : MonoBehaviour
{
    public bool isPoisoned;
    public bool isRegenerating;

    public int hp {
        get;
        private set;
    }

    void Start()
    {
        hp = 100;

        this.UpdateAsObservable()
            .Where(_ => isPoisoned)
            .Subscribe(_ => hp -= 1);

        this.UpdateAsObservable()
            .Where(_ => isRegenerating)
            .Subscribe(_ => hp += 2);
    }
}
```

そもそも`Update()`というメソッドは消え去り、処理は個々の無名関数に閉じ込められました。
購読をやめたくなったときも`Subscribe()`の戻り値の`IDisposable`を受け取って`Dispose()`を呼べばOKです。

え？毒によるダメージは毎フレームじゃなくて1秒おきに10回！？しかも死んでたら毒解除してHP減らさない！？

<font color="red"><b>できらぁ！<b></font>

```Player.cs
using System;
using UnityEngine;
using UniRx;
using UniRx.Triggers;

public class Player : MonoBehaviour
{
    public BoolReactiveProperty IsPoisoned = new BoolReactiveProperty();
    public IntReactiveProperty Hp = new IntReactiveProperty();
    public ReadOnlyReactiveProperty<bool> IsDead;

    void Awake()
    {
        Hp.Value = 100;

        // getterを作るような感じで新しいReactivePropertyを作れる
        IsDead = Hp.Select(x => x <= 0).ToReadOnlyReactiveProperty();
    }

    void Start()
    {
        this.IsPoisoned
            .Where(value => value) // trueに変化したときだけ通知
            .Take(1)
            .SelectMany(_ => Observable.Interval(TimeSpan.FromSeconds(1))) // 1秒ごとのタイマーに差し替え
            .Take(10)  // 10個来たら終わり
            .TakeWhile(_ => IsPoisoned.Value && !IsDead.Value)  // どちらかがtrueで終わり
            .DoOnCompleted(() => IsPoisoned.Value = false)
            .RepeatUntilDestroy(this)  // 1回の毒が終わったら最初から購読し直し
            .Subscribe(_ => Hp.Value -= 1);
    }
}
```

だいぶごちゃごちゃしてきましたが、「理解できればいろいろできるんだな」くらいの認識で大丈夫です。

Rxを使いこなせるようになってくるといろんな処理がメソッドチェーンで書けてスコープが小さく保てるのですが、短く書ける分オペレータの挙動を正しく理解していないとハマりやすいです。

最初のうちはとりあえずLINQを知っていれば使える`Select()`と`Where()`以外のオペレータは使わないようにしましょう。

## `MonoBehaviour`の購読可能なイベント
`MonoBehaviour`の自動で呼ばれる関数については全部購読可能になると思ってもらっていいです。

使い方は先の`UpdateAsObservable()`と同じで、`using UniRx.Triggers`することで`MonoBehaviour`のインスタンスにメソッドが生えるので`Subscribe()`で購読します。

* OnAnimatorIKAsObservable
* OnAnimatorMoveAsObservable
* OnCollisionEnter2DAsObservable
* OnCollisionExit2DAsObservable
* OnCollisionStay2DAsObservable
* OnCollisionEnterAsObservable
* OnCollisionExitAsObservable
* OnCollisionStayAsObservable
* OnDestroyAsObservable
* OnEnableAsObservable
* OnDisableAsObservable
* FixedUpdateAsObservable
* LateUpdateAsObservable
* OnMouseDownAsObservable
* OnMouseDragAsObservable
* OnMouseEnterAsObservable
* OnMouseExitAsObservable
* OnMouseOverAsObservable
* OnMouseUpAsObservable
* OnMouseUpAsButtonAsObservable
* OnTriggerEnter2DAsObservable
* OnTriggerExit2DAsObservable
* OnTriggerStay2DAsObservable
* OnTriggerEnterAsObservable
* OnTriggerExitAsObservable
* OnTriggerStayAsObservable
* UpdateAsObservable
* OnBecameInvisibleAsObservable
* OnBecameVisibleAsObservable
* OnBeforeTransformParentChangedAsObservable
* OnTransformParentChangedAsObservable
* OnTransformChildrenChangedAsObservable
* OnCanvasGroupChangedAsObservable
* OnRectTransformDimensionsChangeAsObservable
* OnRectTransformRemovedAsObservable
* OnParticleCollisionAsObservable
* OnParticleTriggerAsObservable

## uGUI系の購読可能なイベント
こちらは`ObservableEventTrigger`コンポーネントを`AddComponent()`して使います。

```csharp
var trigger = gameObject.AddComponent<ObservableEventTrigger>();
trigger.OnPointerClickAsObservable()
       .Subscribe(e => Debug.Log("OnPointerClick"));
```

* OnDeselectAsObservable
* OnMoveAsObservable
* OnPointerDownAsObservable
* OnPointerEnterAsObservable
* OnPointerExitAsObservable
* OnPointerUpAsObservable
* OnSelectAsObservable
* OnPointerClickAsObservable
* OnSubmitAsObservable
* OnDragAsObservable
* OnBeginDragAsObservable
* OnEndDragAsObservable
* OnDropAsObservable
* OnUpdateSelectedAsObservable
* OnInitializePotentialDragAsObservable
* OnCancelAsObservable
* OnScrollAsObservable


## UnityEventの購読可能なイベント
UnityEventは`AsObservable()`を呼ぶことで購読可能になります。

「`AddListener()`でいいじゃん」と思われるかもしれませんが、Rxへの世界への変換を挟むといろいろといいことがあるので覚えておきましょう。

```csharp
using UniRx;

// AsObservable()での変換だと初期値は購読時に送られてこない
button.OnClick.AsObservable()
toggle.OnValueChanged.AsObservable()
scrollbar.OnValueChanged.AsObservable()
scrollRect.OnValueChanged.AsObservable()
slider.OnValueChanged.AsObservable()
inputField.OnEndEdit.AsObservable()
inputField.OnValueChanged.AsObservable()

// 初期値が購読時に送られてくるver.
button.OnClickAsObservable()
toggle.OnValueChangedAsObservable()
scrollbar.OnValueChangedAsObservable()
scrollRect.OnValueChangedAsObservable()
slider.OnValueChangedAsObservable()
inputField.OnEndEditAsObservable()
inputField.OnValueChangedAsObservable()
```

## `OnApplicationXXX`の購読可能なイベント
`Observable`というクラスのstaticメソッドとして生えています。

`MonoBehaviour`が必要ないので通常のクラスでも使えますが、購読の解除には気をつけるようにしましょう。

```csharp
using UniRx;

Observable.EveryApplicationPause()
          .Subscribe(pause => Debug.Log("pause:" + pause))
          .AddTo(this) // MonoBehaviourで使う時はこれを書いておくと良い
```

* EveryApplicationPause
* EveryApplicationFocus


# 最後に
UniRxは確かに学習コストが高いです。

というかできることが多い＆すご過ぎて、だんだん使えるようになってくると「素のUnityってなんて貧弱なんだ！」と思うくらいになります。

とりあえず最初のうちは難しいことは一旦置いといて、「なんでもかんでもイベント（Observable）化してくれるんだな」くらいで使い始めてみてはどうでしょうか。

一人でも多くのUnity使いがUniRxで幸せになることを祈っています。