(:3[kanのメモ帳]

個人ゲーム開発者kan.kikuchiのメモ的技術ブログ。月木更新でUnity関連がメイン。

(:3[kanのメモ帳]


本ブログの運営者kan.kikuchiが個人で開発しているVRゲームです!


List(配列)やDictionary用のReactiveProperty、ReactiveCollectionとReactiveDictionary【Unity】【UniRx】


このエントリーをはてなブックマークに追加


この記事でのバージョン
Unity 2019.4.17f1
UniRx - Reactive Extensions for Unity 7.1.0


はじめに

UniRxではReactivePropertyを使うことで簡単に値の変更を監視する事が出来ます。

/*SampleA*/
//変更を監視する値
private ReactiveProperty<int> _valueReactiveProperty = new ReactiveProperty<int>(0);

//ReactivePropertyのうち、IObservableだけを公開し、処理を登録できるように
public IObservable<int> Observable => _valueReactiveProperty;
    
/// <summary>
/// 値を設定する
/// </summary>
public void SetValue(int value){
  _valueReactiveProperty.Value = value;
}
/*SampleAを使う別のクラス(MonoBehaviour)*/

private void Awake(){
  //サンプル生成
  var sample = new SampleA();

  //値が変更されたらログを出すように
  sample.Observable.Subscribe(count => Debug.Log(count));

  //てきとうに値を変更
  SetValue(1);
  SetValue(1); 
  SetValue(2); 
  SetValue(2); 
  SetValue(1); 
}
f:id:kan_kikuchi:20190316142911j:plain


しかしReactivePropertyはListやDictionaryといった物では使えません

ということで今回は、ListやDictionaryの値の変化をUniRxで監視する方法の紹介です!


そもそも「UniRxやReactivePropertyがよく分からん」という方は以下の記事を参照の事。





ReactiveCollection

まずList(というより配列)の値の変化をUniRxで監視する時はReactiveCollectionという物を使います。


具体的な使い方は以下のような感じです。

/*ReactiveCollectionSample*/

using System;
using System.Collections.Generic;
using System.Linq;
using UniRx;
using UnityEngine;

/// <summary>
/// ReactiveCollection確認用サンプル
/// </summary>
public class ReactiveCollectionSample {
    
  //変更を監視する値(new ReactiveCollection<int>(new List<int>(){1, 2, 3})のようにListや配列でも初期化可能)
  private readonly ReactiveCollection<int> _reactiveCollection = new ReactiveCollection<int>{1, 2, 3};

  //中身の値だけを公開するためのList(このListの値を変えてもReactiveCollection側は変わらない)
  public List<int> ValueList => _reactiveCollection.ToList();
  
  //ReactiveCollectionのうちIObservableだけを公開し、処理を登録できるように
  public IObservable<CollectionReplaceEvent<int>> ReplaceObservable => _reactiveCollection.ObserveReplace();

  /// <summary>
  /// 値を変更する
  /// </summary>
  public void ChangeValue(int index, int value) {
    _reactiveCollection[index] = value;
  }
    
}
/*ReactiveCollectionSampleを使う別のクラス(MonoBehaviour)*/

private void Awake(){
  //サンプル生成
  var sample = new ReactiveCollectionSample();
    
  //最初の値を確認
  var valueList = sample.ValueList;
  for (var i = 0; i < valueList.Count; i++) {
    Debug.Log($"{i}番目の値は{valueList[i]}");
  }

  //値が変更されたらログを出すように
  sample.ReplaceObservable.Subscribe(OnReplace).AddTo(gameObject);

  //試しにListの中身を直接書き換えてみる(OnReplaceは実行されない)
  sample.ValueList[0] = 2;
    
  //値をテキトウに変更
  sample.ChangeValue(0, 7);
  sample.ChangeValue(1, 58);
  sample.ChangeValue(2, -12);
}

//値が変更された時の処理
private void OnReplace(CollectionReplaceEvent<int> replaceEvent) {
  Debug.Log($"{replaceEvent.Index}番目の値が{replaceEvent.OldValue}→{replaceEvent.NewValue}に変更");
}
f:id:kan_kikuchi:20210107065303j:plain


基本的にはReactivePropertyと同様に任意の型を指定して作成し、

Subscribeを使って変更を購読するだけですが、

IObservableを取得するのにメソッドが必要(例だとObserveReplace)という違いがあります。


また例のように値の変更の監視にはObserveReplaceを使いますが、

要素数の変更の監視にはObserveCountChanged、リセットの監視にはObserveReset

と言ったように監視したい内容に合わせてメソッドを使い分ける必要もあります。


さらにReactivePropertyはSubscribe時に登録された処理が実行されていましたが、

ReactiveCollectionはSubscribe時に処理が実行されないという点にも注意が必要です。


ReactiveDictionary

次にDictionaryの値の変化をUniRxで監視する時はReactiveDictionaryという物を使います。


具体的な使い方は以下のような感じです。

/*ReactiveDictionarySample*/

using System;
using System.Collections.Generic;
using System.Linq;
using UniRx;
using UnityEngine;

/// <summary>
/// ReactiveDictionary確認用サンプル
/// </summary>
public class ReactiveDictionarySample {
    
  //変更を監視する値
  private readonly ReactiveDictionary<int, string> _reactiveDictionary = new ReactiveDictionary<int, string> {
    {0, "a"}, {1, "あいう"}, {2, "(`・ω・´)"}
  };

  //中身の値だけを公開するためのDictionary(このDictionaryの値を変えてもReactiveDictionary側は変わらない)
  public Dictionary<int, string> ValueDictionary => _reactiveDictionary.ToDictionary(pair => pair.Key, pair => pair.Value);
  
  //ReactiveDictionaryのうちIObservableだけを公開し、処理を登録できるように
  public IObservable<DictionaryReplaceEvent<int, string>> ReplaceObservable => _reactiveDictionary.ObserveReplace();

  /// <summary>
  /// 値を変更する
  /// </summary>
  public void ChangeValue(int key, string value) {
    _reactiveDictionary[key] = value;
  }
    
}
/*ReactiveCollectionSampleを使う別のクラス(MonoBehaviour)*/

private void Awake(){
  //サンプル生成
  var sample = new ReactiveDictionarySample();
    
  //最初の値を確認
  var valueDictionary = sample.ValueDictionary;
  foreach (var pair in valueDictionary) {
    Debug.Log($"Key : {pair.Key}, Value : {pair.Value}");
  }

  //値が変更されたらログを出すように
  sample.ReplaceObservable.Subscribe(OnReplace).AddTo(gameObject);

  //試しにDictionaryの中身を直接書き換えてみる(OnReplaceは実行されない)
  sample.ValueDictionary[0] = "試しに";
    
  //値をテキトウに変更
  sample.ChangeValue(0, "Test");
  sample.ChangeValue(1, "変わった");
  sample.ChangeValue(2, "(; ・`д・´)");
}

//値が変更された時の処理
private void OnReplace(DictionaryReplaceEvent<int, string> replaceEvent) {
  Debug.Log($"Key : {replaceEvent.Key}の値が{replaceEvent.OldValue}→{replaceEvent.NewValue}に変更");
}
f:id:kan_kikuchi:20210108070529j:plain


これも基本的にはReactivePropertyと同様に任意の型を指定して作成し、

Subscribeを使って変更を購読するだけですが、

IObservableを取得するのにメソッドが必要(例だとObserveReplace)という違いがあります。


またReactiveCollectionと同様に値の変更の監視にはObserveReplaceを使いますが、

要素数の変更の監視にはObserveCountChanged、リセットの監視にはObserveReset

と言ったように監視したい内容に合わせてメソッドを使い分ける必要もあります。


さらにReactiveCollectionと同様、Subscribe時に処理が実行されないという点にも注意が必要です。