UniRx を使っていて、複数の ReactiveProperty をまとめて監視して何か処理を行いたいときがたまにあります。
例えば、「デバッグモードが ON」かつ「ミュート設定が ON」になったときのみ音声をミュートする、とか。
ベタ書き
デバッグモードとミュート設定をそれぞれ BoolReactiveProperty で持ち、どちらかの値が変わったらミュート状態を切り替える、という実装を考えます。
とりあえず、何も考えずそのままベタ書きしたのが以下のコードです。
public BoolReactiveProperty debugMode; public BoolReactiveProperty muteAllSounds; public AudioSource audioSource; void Start() { debugMode.Subscribe(x => ToggleMute()); muteAllSounds.Subscribe(x => ToggleMute()); } void ToggleMute() { audioSource = debugMode.Value && muteAllSounds.Value; }
そのままだ。そのままですが、だいぶ頭の悪いコードですね……。
せっかく ReactiveProperty を使っているのに、流れてきた値 (x
) を使っていないあたりに頭の悪さが滲み出ています。
ストリームを合成する
「ストリームは合成できる」ということを知っていれば、先程のコードは以下のように書き換えることができます。
public BoolReactiveProperty debugMode; public BoolReactiveProperty muteAllSounds; public AudioSource audioSource; void Start() { Observable.Merge(debugMode, muteAllSounds) .Subscribe(x => ToggleMute()); } void ToggleMute() { audioSource = debugMode.Value && muteAllSounds.Value; }
10 点だったのが、20 点くらいにはなったかな……。
しかしこのやり方だと、流れてくるx
の値がdebugMode
のものなのかmuteAllSounds
のものなのか判らないので、やっぱり使えないままです。
両方の値が流れてくるようにする
ストリームを Merge で合成すると、debugMode
の値が変わった時にはdebugMode
の値のみが、muteAllSounds
の値が変わった時にはmuteAllSounds
の値のみが流れて来るので、良くなかった、と。
なら、debugMode
の値が変わろうとmuteAllSounds
の値が変わろうと、両方の値が流れてくるようになれば良いわけです。
そのようにしたのがこちら。
public BoolReactiveProperty debugMode; public BoolReactiveProperty muteAllSounds; public AudioSource audioSource; void Start() { Observable.CombineLatest(debugMode, muteAllSounds) .Subscribe(list => { audioSource = list.All(x => x); }); }
だいぶ賢いコードになりましたね!
ストリームを CombineLatest で合成すると、どれかの値が変わったら全部の値が List にまとめられて流れてくるので、「list 内の全要素が true だったら*1ミュート」とすれば、やりたいことが実現できるというわけ。
補足
先程、CombineLatest の説明で「どれかの値が変わったら全部の値が List にまとめられて流れてくる」と書きましたが、これは微妙に違くて、すべてのストリームに一回以上値が流れていないと List は流れてきません。
それなのになぜ上記のコードが valid なのかというと、ReactiveProperty は値が変化した時だけでなく Subscribe した瞬間にもその時点での値が一回流れるので、自動的に「一回以上値が流れている」が満たされるからですね。
参考:
neue cc - Unityにおけるコルーチンの省メモリと高速化について、或いはUniRx 5.3.0でのその反映
(UniRxの)ReactivePropertyはSubscribe時に必ず値をプッシュするようになってる
*1:list.All(x => x) の部分。