Nobollel開発者ブログ

Nobollelのエンジニアが、UnityやCocos2d-xの旬な情報・技術を紹介します。

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