UniRxのReactivePropertyについて
こんにちは、Nobollelエンジニアの古屋です。
前回の投稿ではUniRxのコルーチンについて紹介しました。今回はReactivePropertyについて、Rxの細かい部分には触れずに、大まかにどんなことができるかについて書きたいと思います。
ReactivePropertyとは
ReactivePropertyは、ざっくり言うと、値の変更を通知してくれる変数のようなものです。new ReactiveProperty<T>()
で作成し、Value
から値を取得・設定します。
var property = new ReactiveProperty<int>(); property.Value = 100; // 値の設定 Debug.Log(property.Value); // 値の取得
Subscribe
を使えば、変更の通知をコールバックで受け取ることができます。もし解除したければ、戻り値のIDisposableをDisposeすれば良いです。
IDisposable subscription = property.Subscribe(x => { Debug.Log(x); });
値を変更を常に通知してくれるので、用途は色々ありますが、たとえばデータをUI上の表示に直結させたりするのに使えます。
Rxのオペレータと組み合わせる
ReactivePropertyにRxのオペレータを適用することで、さらに複雑なことができます。個人的によく使うパターンをいくつか紹介します。
Select
Selectを使って、ReactivePropertyを別のReactivePropertyに変換できます。
var level = new ReactiveProperty<int>(1); ReadOnlyReactiveProperty<bool> isLevelMax = level.Select(lv => (lv >= 100)).ToReadOnlyReactiveProperty(); Debug.Log(isLevelMax); // false level.Value = 100; Debug.Log(isLevelMax); // true
この例では、level.Value
が100以上になったら、自動的にisLevelMax.Value
がtrueになります。
CombineLatest
CombineLatestを使って、2つ以上のReactivePropertyを合成して別のReactivePropertyにできます。
var a = new ReactiveProperty<bool>(false); var b = new ReactiveProperty<bool>(false); var c = Observable.CombineLatest(a, b, (x, y) => x && y).ToReadOnlyReactiveProperty(); Debug.Log(c); // false a.Value = true; Debug.Log(c); // false b.Value = true; Debug.Log(c); // true
この例では、c.Value
の値は常にa.Value && b.Value
に等しくなります。CombineLatest
がaとbのどちらかが変更される度に(x, y) => x && y
が実行され、その戻り値がc
の値になります。
Select+Switch
Select+Switchは、Selectのパターンを拡張したようなものです。ちょっとややこしいので、まずは例を見てください:
ReactiveProperty<Character> currentCharacter = new ReactiveProperty<Character>(); ReadOnlyReactiveProperty<float> currentAttack = currentCharacter.Select(c => (IObservable<float>)c.Attack).Switch().ToReadOnlyReactiveProperty();
操作するキャラクターが途中で切り替わるゲームがあったとして、currentCharacter
は「現在のキャラクターオブジェクト」です。このとき、「現在のキャラクターの攻撃力」を返すcurrentAttack
を作るためにSelect+Switchを使っています。
c.Attack
がただのfloatならSelectだけで実現できますが、c.Attack
がReactivePropertyの場合は、SelectでまずIObservable<IObservable<float>>
に変換した後、SwitchでIObservable<float>
に変換します。Switchは最後に流れてきたIObservable<float>
の値を流すので、currentAttackからは、最後に設定されたキャラクターのAttackの値が取得できるようになります。
character1.Attack.Value = 100; character2.Attack.Value = 200; currentCharacter.Value = character1; Debug.Log(currentAttack.Value); // 100 // キャラが変わったらそのキャラの攻撃力になる currentCharacter.Value = character2; Debug.Log(currentAttack.Value); // 200 // 攻撃力が後から変化しても適用される character2.Attack.Value = 220; Debug.Log(currentAttack.Value); // 220