この記事でのバージョン
Unity 2018.3.4f1
UpdateAsObservable
UniRxではUpdateAsObservableを使うことで簡単にUpdateをストリーム化する事が出来ます。
(FixedUpdateAsObservableやLateUpdateAsObservableもある)
using UniRx; using UniRx.Triggers; //UpdateAsObservableを使うのに必要 using UnityEngine; public class NewBehaviourScript : MonoBehaviour{ private void Start() { //Updateをストリーム化し、毎フレーム、ログを出すように this.UpdateAsObservable() .Subscribe(_ => { Debug.Log("アップデート中"); } ); } }
ちなみにUpdateAsObservableはGameObjectが破棄された際に
自動的にOnCompletedが発行されるので、AddToは必要ありません。
また、UpdateAsObservableを実行すると、
ObservableUpdateTriggerというコンポーネントが追加されます。(間違って消さないように)
そもそも「Updateをストリーム化出来ると何が嬉しいのか」という話ですが、
例えば「Updateの最初の一回だけ処理したい」みたいな条件を簡単に設定できますし、
this.UpdateAsObservable() .FirstOrDefault() //最初の一回実行したら完了させる .Subscribe(_ => { Debug.Log("最初のアップデート"); } );
Update内だけで使う変数を関数外に書かなくても良かったりもします。
private int _sum = 0; //Update内だけでしか使わないが、Update外に書く必要がある private void Update() { _sum++; Debug.Log(_sum); }
int sum = 0; //UpdateAsObservableを使えば同じ関数内に書ける this.UpdateAsObservable() .Subscribe(_ => { sum++; Debug.Log(sum); } );
他にUpdateAsObservableを複数回実行する事で、異なるUpdate処理を同時に実行出来たり、
好きなタイミングでUpdateを止められたりと、様々な利点があります。
EveryUpdate
UpdateAsObservableはComponentの拡張メソッドなので
MonoBehaviourなどのComponentを継承していないクラスでないと使えません。
と言うことで、次はMonoBehaviourを継承していないクラスでも
Update処理を実行できるEveryUpdateの紹介です。
using UniRx; using UnityEngine; public class NewBehaviourScript : MonoBehaviour{ private void Start() { Sample sample = new Sample(); } } public class Sample { public Sample() { //MonoBehaviourを継承していなくても使える! Observable.EveryUpdate() .Subscribe(_ => { Debug.Log("EveryUpdate"); }); } }
なお、EveryUpdateはUpdateAsObservableと違い自動で破棄されたりしないので、
Update処理をやめたい時はSingleAssignmentDisposableのDisposeを使ったり、
//UpdateをやめるためにSingleAssignmentDisposableを保持 var disposable = new SingleAssignmentDisposable(); disposable.Disposable = Observable.EveryUpdate() .Subscribe(_ => { Debug.Log("EveryUpdate"); }); //Update処理をやめたい時はDisposeメソッドを使う //disposable.Dispose();
他のストリーム同様、AddToでGameObjectを設定したりする必要があります。
public Sample(MonoBehaviour monoBehaviour) { Observable.EveryUpdate() .Subscribe(_ => { Debug.Log("EveryUpdate"); }) .AddTo(monoBehaviour.gameObject); }
もちろん、MonoBehaviourを引数で渡すなら、
そのままUpdateAsObservableを使っても大差はありません。
public Sample(MonoBehaviour monoBehaviour) { monoBehaviour.UpdateAsObservable() .Subscribe(_ => { Debug.Log("アップデート中 : Sample"); } ); }
ただし、UpdateAsObservableとEveryUpdateの仕組み自体は異なっているようです。
Observable.EveryUpdate()はUniRxの機能の1つである「マイクロコルーチン」を利用して動作しており、仕組みはUpdateAsObservableと比較して複雑になっています。すごく簡潔にまとめてしまえば、「Observable.EveryUpdate()は呼び出されるたびにシングルトン上でコルーチンを起動する」という動作になっています。このコルーチンは手動で止めない限りずっと動作し続けてしまうため、ストリームの寿命管理をしっかりしないとその2で触れたような問題を引き起こす可能性があります。
ただしその反面、Observable.EveryUpdate()には次のようなメリットも存在します。
シングルトン上で動作するため、ゲームが進行中ずっと存在するストリームを生成できる
大量にSubscribeしてもパフォーマンスが低下しない(マイクロコルーチンの性質)
ObserveEveryValueChanged
Updateをストリーム化すると、条件を簡単に設定できると書きましたが、
よく使いそうな「前フレームから値が変化した場合にだけ実行されるUpdate」
を実装するためのオペレータ、ObserveEveryValueChangedは最初から用意されていたりします。
//この値が変化した時だけUpdate処理をしたい public int Value = 0; private void Start() { //Valueが変わった時だけそれをログに表示 this .ObserveEveryValueChanged(value => Value) .Subscribe(value => Debug.Log("値変更! : " + value)); }
ちなみにObserveEveryValueChangedには以下のような特徴があります。
任意のクラスオブジェクトに対して使用できる
対象のインスタンスオブジェクトのプロパティ値(プロパティに限らずに値を返すものなら何でも)が前フレームと比較して変化していた際にその値をOnNextに流す
1フレーム内で発生した複数回の変動は検知できず、前フレームと今フレームの変化のみ検知できる