®️

[Unity] UniRxをR3に移行する

2024/02/28に公開
4

はじめに

UnityのRxライブラリとしてUniRxを使っている方は多いと思います。後継ライブラリであるR3が登場し、UniRxは今後更新されないようです。今回はUniRxを使っていたプロジェクトをR3に移行させた手順について解説します。

参考記事

https://qiita.com/toRisouP/items/4344fbcba7b7e8d8ce16
https://zenn.dev/shiena/articles/unity-install-r3

書き換えもとになるUniRxのコード

UniRxを使ったコードでまず挙動を確認します

using System;
using UniRx;
using UniRx.Triggers;
using UnityEngine;

public class UniRxSample : MonoBehaviour
{
    //値が変更されたらイベントが発火するReactiveProperty
    ReactiveProperty<int> _reactiveProperty = new ReactiveProperty<int>(0);

    //外部のクラスにはReadOnlyReactivePropertyで公開
    public IReadOnlyReactiveProperty<int> ReadOnlyReactiveProperty => _reactiveProperty;
    //またはIObservableで公開
    public IObservable<int> OnValueChanged => _reactiveProperty;

    void Start()
    {
        //値が更新されたらDebug出力
        _reactiveProperty.Subscribe(x => Debug.Log(x)).AddTo(this);

        _reactiveProperty.Value = 1;
        _reactiveProperty.Value = 2;
        //これは出力されない
        _reactiveProperty.Value = 2;
        _reactiveProperty.Value = 3;
        //強制的に通知
        _reactiveProperty.SetValueAndForceNotify(3);

        //マウスを押下したらDebug出力
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Subscribe(_ => Debug.Log("GetMouseButtonDown"))
            .AddTo(this);

        //Spaceキーを押下したらDebug出力(UniRx.Triggers)
        this.UpdateAsObservable()
            .Where(_ => Input.GetKeyDown(KeyCode.Space))
            .Subscribe(_ => Debug.Log("GetKeyDown Space"))
            .AddTo(this);

        //TransformのPositionが変更されたらDebug出力
        this.transform.ObserveEveryValueChanged(t => t.position)
            .Subscribe(x => Debug.Log($"PositionChanged:{x}"))
            .AddTo(this);

        //ReactiveCollectionでAdd/Remove/Replaceを監視
        var collection = new ReactiveCollection<int>();
        collection.ObserveAdd().Subscribe(x => Debug.Log($"Add:{x}")).AddTo(this);
        collection.ObserveRemove().Subscribe(x => Debug.Log($"Remove:{x}")).AddTo(this);
        collection.ObserveReplace().Subscribe(x => Debug.Log($"Replace:{x}")).AddTo(this);

        collection.Add(1);
        collection.Add(2);
        collection[0] = 3;
        collection.Remove(2);
    }
}

出力

0
1
2
3
3
PositionChanged:(0.00, 1.00, -10.00)

Add:Index:0 Value:1
Add:Index:1 Value:2
Replace:Index:0 OldValue:1 NewValue:3
Remove:Index:1 Value:2

#マウスをクリック
GetMouseButtonDown
#スペースキーを押下
GetKeyDown Space
#アタッチしたオブジェクトのPositionを変更
PositionChanged:(0.00, 1.00, 0.00)

R3のインストール

Package Manager経由でR3をインストールします。まずEdit->Project Settings->Package Managerを開きます。ここでScoped Registriesに2つのURLを登録します

Name: Unity NuGet
URL: https://unitynuget-registry.azurewebsites.net
Scope(s): org.nuget
Name: OpenUPM
URL: https://package.openupm.com
Scope(s): com.cysharp

次にWindow->Package Managerを開き、Packages:をMy Registriesに切り替えます。ここで検索ボックスにR3と入力すると2つのリポジトリが出てくるので両方ともインストールします

これでR3のインストールは完了です。もしプロジェクト内でReactiveCollectionまたは ReactiveDictionaryを使っている場合は、以下に示す手順でObservableCollectionsのインストールも行ってください。

PackageManagerを開きObservableCollectionsで検索を行いObservableCollections(NuGet)ObservableCollections.R3(NuGet)をインストールします。下方に出ているものはバージョンが古いものなのでインストールしないようにご注意ください。

UniRxからR3へ書き換えを行う

UniRxとR3が共存している状態は良くないのでUniRxを削除します。Plugins/UniRxのフォルダごと削除すればUniRxの削除は完了です。

削除した直後はコンパイルエラーが出るので書き換えていきます。
修正したコードが下記のものになります。

using R3;   //UniRxから書き換え
using R3.Triggers;  //UniRx.Triggersから書き換え
using ObservableCollections; //ObservableListを使うために追加
using UnityEngine;
//using System; //Systemをusingするのは不要になった

public class UniRxSample : MonoBehaviour
{
    ReactiveProperty<int> _reactiveProperty = new ReactiveProperty<int>(0);

    //外部のクラスにはReadOnlyReactivePropertyで公開
    public ReadOnlyReactiveProperty<int> ReadOnlyReactiveProperty => _reactiveProperty;
    //またはObservableで公開
    public Observable<int> OnValueChanged => _reactiveProperty;

    void Start()
    {
        //値が更新されたらDebug出力
        _reactiveProperty.Subscribe(x => Debug.Log(x)).AddTo(this);

        _reactiveProperty.Value = 1;
        _reactiveProperty.Value = 2;
        //これは出力されない
        _reactiveProperty.Value = 2;
        _reactiveProperty.Value = 3;
        //強制的に通知
        _reactiveProperty.OnNext(3);    //SetValueAndForceNotifyの代わりにOnNextを使う

        //マウスを押下したらDebug出力
        Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Subscribe(_ => Debug.Log("GetMouseButtonDown"))
            .AddTo(this);

        //Spaceキーを押下したらDebug出力(R3.Triggers)
        this.UpdateAsObservable()
            .Where(_ => Input.GetKeyDown(KeyCode.Space))
            .Subscribe(_ => Debug.Log("GetKeyDown Space"))
            .AddTo(this);

        //TransformのPositionが変更されたらDebug出力
        //UniRxとは書き方が異なる
        Observable.EveryValueChanged(this.transform, t => t.position)
            .Subscribe(x => Debug.Log($"PositionChanged:{x}"))
            .AddTo(this);

        /*
         * UniRxでの書き方
        this.transform.ObserveEveryValueChanged(t => t.position)
            .Subscribe(x => Debug.Log($"PositionChanged:{x}"))
            .AddTo(this);
        */

        //ObservableListでAdd/Remove/Replaceを監視
        var collection = new ObservableList<int>(); //ReactiveCollectionの代わりにObservableListを使う
        collection.ObserveAdd().Subscribe(x => Debug.Log($"Add:{x}")).AddTo(this);
        collection.ObserveRemove().Subscribe(x => Debug.Log($"Remove:{x}")).AddTo(this);
        collection.ObserveReplace().Subscribe(x => Debug.Log($"Replace:{x}")).AddTo(this);

        collection.Add(1);
        collection.Add(2);
        collection[0] = 3;
        collection.Remove(2);
    }
}

細かく見ていきます

using

using UniRxからusing R3に変わります。Triggersも同じように書き換えます。下記で説明するObservableListを使う際はusing ObservableCollectionsも追加します。また、IObservableのために記述していたusing Systemは不要になります

IReadOnlyReactiveProperty / IObservable

IReadOnlyReactivePropertyが廃止されており、代わりにReadOnlyReactivePropertyを使うように変更されています。また、ObservableがSystemではなくR3で再定義されたものを使うようになった影響によりIObservableObservableに書き換えます。
また、IReadOnlyReactivePropertyで値にアクセスする際は.Valueを使っていましたが、.CurrentValueに変更されています。ちなみにReactiveProperty<T>にはget/setができる.Valueとgetのみの.CurrentValue二つのプロパティが用意されています。

SetValueAndForceNotify

ReactivePropertyの変更が同じ値であっても通知を行うSetValueAndForceNotifyが廃止されました。代わりにOnNextを使うと同じ挙動になります。

ObserveEveryValueChanged

値が変化した際に通知を行うObserveEveryValueChangedは記述が大きく変わりました。オブジェクトからメソッドを生やしていたのが、Observable.EveryValueChangedの第一引数にオブジェクトを入れる形になります。

ReactiveCollection / ReactiveDictionary

ReactiveCollectionの代わりにObservableList<T>, ReactiveDictionaryの代わりにObservableDictionary<TKey, TValue>が用意されています。ほかにも ObservableHashSet<T>, ObservableQueue<T>, ObservableStack<T>, ObservableRingBuffer<T>, ObservableFixedSizeRingBuffer<T>があるようです。

ハマったところ

ObservableListのObserveAdd()などのメソッドが見つからない

実はPackage Managerの方にもObservableCollectionsが存在し、こちらをインストールした場合、拡張メソッドの紐づけがうまくいきません。結果、ObserveAdd()などのメソッドが見つからずコンパイルエラーになってしまいます。

これを防ぐために、ObserveAdd()などのイベントを使いたい場合は、ObservableCollectionsObservableCollections.R3両方ともNuGet経由でインストールしましょう。

まとめ

UniRxからR3へ移行するためには、書き換えなければならない場所が数多くあります。しかし、ObservableTrackerというリークを防ぐ仕組みが追加されていたり、実行速度が上がったりとメリットは大きいです。もし書き換えるチャンスがあるようでしたら書き換えを検討してみてはいかがでしょうか?

Discussion

Mitsuhiro KogaMitsuhiro Koga

UnityNuGetに ObservalbeCollectionObservalbeCollection.R3 を追加してもらいました。
https://github.com/xoofx/UnityNuGet/pull/336
デプロイに数時間かかり月1回くらいしか更新していないので来月あたりにPackage Manager経由でインストールできるようになると思います。

Mitsuhiro KogaMitsuhiro Koga

UnityNuGetのサーバーが更新されてObservableCollectionとObsrevableCollection.R3もPackage Managerからインストールできるようになりました