ささみのメモ帳

ゲーム会社で働く、無能プログラマーが自分のためのメモ帳として利用しています。

UniRxについてのメモ

UniRxとは

UniRx(Reactive Extensions for Unity)は、マイクロソフトによって開発された.NetFrameworkのReactive Extensions(Rx)機能をCySharp社がUnity用にしたライブラリです。
※UnityはMono(.NetFrameworkとは違う)環境をベースに動作しているため、.NetFrameworkのRxの機能をすべてサポートためです。

Rxとは、Observerパターンを取り入れたライブラリで、非同期 / イベント / 時間に関する処理をLINQ的に記述でき、プログラムをより効果的に管理するための機能を提供します。複雑なイベント処理をシンプルにし、コードの可読性や保守性を向上させることができます。

基本概念/機能

オブザーバブル(Observables): データやイベントのストリームを表し、時間の経過と共に複数の値を非同期的に生成することができます。これにより、イベント駆動型のプログラミングが容易になります。

オブザーバー(Observers): オブザーバーはオブザーバブルから送出されるデータを購読し、データが発行されるたびに特定のアクションを実行します。オブザーバーはオブザーバブルと接続され、イベントやデータの変更を監視します。

サブスクリプション(Subscriptions): オブザーバーがオブザーバブルを購読するとき、サブスクリプションが生成されます。このサブスクリプションを通じて、イベントの購読を管理し、必要ない場合には購読を解除することができます。

オペレーター(Operators): UniRxには多くのオペレーターが用意されており、これを使ってデータストリームの変換、フィルタリング、結合などが行えます。これにより、複雑なデータ処理や条件分岐を簡単に記述することが可能です。

利点

コードの簡素化: 非同期処理や複数のイベントソースを扱うコードが簡潔に書けるようになります。 エラー処理: エラーハンドリングが一元化され、エラー処理をストリーム内で簡単に行うことができます。 メモリリークの防止: イベントの購読解除を自動的に行うことで、メモリリークのリスクを減少させます。 時間操作: デバウンス、スロットル、タイムアウトなどの時間に基づいた操作を簡単に実装できます。

導入方法

以下のドキュメントより導入を行ってください。
github.com

基本的な利用方法

Obserbableの作成/設定
Obserbableすなわち、Observer(購読者)が読むもの/対象の作成をする必要があります。 Observableクラスのメソッドを利用してIObservable(インターフェース)として作成/設定します。
時間とイベントによるベースの設定があります。

以下はその種類です。

名前/記述 ディレクティブ 内容
EveryUpdate(void) UniRx 毎フレームごと(Updateタイミング)に通知
※GameObjectの状態に依存しない全体サイクルをもとにします
EveryLateUpdate(void) UniRx 毎フレームごと(LateUpdateタイミング)に通知
※GameObjectの状態に依存しない全体サイクルをもとにします
EveryFixedUpdate(void) UniRx Unity設定で指定したフレームごと(FixedUpdateタイミング)に通知
※通常と同様に物理計算系の使用に推奨
※GameObjectの状態に依存しない全体サイクルをもとにします
EveryGameObjectUpdate(void) UniRx |毎フレームごと(Updateタイミング)に通知
MainThreadDispatcherのSubjectに積まれて、メインスレッド実行になる
EveryEndOfFrame(void) UniRx 毎フレームの最後に通知
EveryApplicationFocus(void) UniRx アプリのフォーカス状態が変更されるたびに通知
戻り値がboolでフォーカス状態が判る
true : フォーカス中 false : フォーカス外
EveryApplicationPause(void) UniRx アプリが一時停止時に通知
戻り値がboolで停止状態が判る
true : 停止 false : 再開
Range(int, int) UniRx 指定した範囲の整数で順に通知する
第一引数が開始値、第二引数が回数
例 (3,4) = {3, 4, 5, 6}
Start(Func <T> or Action) UniRx 指定した処理を非同期実行し、結果を通知
Interval(TimeSpan) UniRx, System 指定した時間間隔で通知
Timer(TimeSpan) UniRx, System 指定した時間経過後に通知
引数によって単発や一定間隔での通知にできる
Timer(TimeSpan) UniRx, System 指定したフレーム経過後に通知
引数によって単発や一定間隔での通知にできる
UpdateAsObservable() UniRx.Triggers MonoBehaviourのUpdate()の呼び出しをObservableストリームとして扱い、通知する
LateUpdateAsObservable() UniRx.Triggers MonoBehaviourのLateUpdate()の呼び出しをObservableストリームとして扱い、通知する
FixedUpdateAsObservable() UniRx.Triggers MonoBehaviourのFixedUpdate()の呼び出しをObservableストリームとして扱い、通知する
OnDestroyAsObservable() UniRx.Triggers MonoBehaviourのOnDestroy()の呼び出しをObservableストリームとして扱い、通知する
ObserveEveryValueChanged
(TSource, Func<TSource, TProperty>)
UniRx 指定したプロパティが変更時に通知
Empty<Unit>() UniRx 指定した型で即座に通知
Return(Unit.Default) UniRx 指定した値で即座に通知
Throw<Unit>(new Exception()) UniRx 指定した型でエラーハンドルを発行する
Repeat(Unit.Default) UniRx ループして任意の値で通知
※終わり次第即座に次の通知を行います
Create<Unit>(observer => ...) UniRx 独自の通知条件の作成

UnitはUniRxで通知の際の値や型がない場合に使用する構造体です。
Unitの部分は任意の型、Unit.Defaultは具体的な任意な型の値を設定できます。

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

public class UniRxTest : MonoBehaviour
{
    void Start()
    {
        Observable.Interval(TimeSpan.FromSeconds(1));
        Observable.Timer(TimeSpan.FromSeconds(1));
        Observable.TimerFrame(1);
        Observable.EveryUpdate();
        Observable.EveryLateUpdate();
        Observable.EveryFixedUpdate();
        Observable.EveryGameObjectUpdate();
        Observable.EveryEndOfFrame();
        Observable.EveryApplicationFocus();
        Observable.EveryApplicationPause();;
        Observable.Range(0, 10);
        Observable.Start(() => { });

        this.UpdateAsObservable();
        this.LateUpdateAsObservable();
        this.FixedUpdateAsObservable();
        this.OnDestroyAsObservable();
        this.ObserveEveryValueChanged(self => self.transform.position);

        Observable.Empty<Unit>();
        Observable.Return(Unit.Default);
        Observable.Throw<Unit>(new Exception());
        Observable.Repeat(Unit.Default);
        Observable.Create<Unit>(observer =>
        {
             return Disposable.Empty;
         });
    }
}

Obserbableの処理をフィルタリング設定のオペレーターがあります。
以下はその種類です。

名前/記述 内容
Where 条件式に一致したもののみ
Distinct 重複した二つ目以降は通さない
DistinctUntilChanged 最新のものが同じだった場合通さない
Throttle/ThrtottleFrame 指定した時間(ms)/フレーム内に来たOnNextの最後だけ通す
ThrottleFirst/ThrottleFirstFrame 指定した時間(ms)/フレーム内に来たOnNextの最初だけ通す
First/FirstOrDefault 一番最初のみを通してObservableを完了
※一つも発行されなかったとき通常はエラー、Defaultはデフォルト値で通知
Single/SingleOrDefault OnNextが2つ以上発行されたらエラー
※一つも発行されなかったとき通常はエラー、Defaultはデフォルト値で通知
Last/LastOrDefault 最後の値だけ通したい
※一つも発行されなかったとき通常はエラー、Defaultはデフォルト値で通知
Take 指定した個数だけ通す
TakeWhile 条件不一致になるまで通す
TakeUntil 指定したObservableにOnNextが来るまで通す
Skip 指定した個数無視
SkipWhile 条件一致する間は無視
SkipUntil 指定したObservableにOnNextが来るまで無視
OfType 型が一致するもののみ通す※型変換も行われます
IgnoreElements OnErrorまたはOnCompletedのみを通す
using System;
using UniRx;
using UniRx.Triggers;
using UnityEngine;

public class EventBase { }
public class Event : EventBase { }
public class UniRxTest : MonoBehaviour
{
    void Start()
    {
        this.UpdateAsObservable()
            .Where(_ => Input.GetKeyDown(KeyCode.Space))
            .Distinct()
            .DistinctUntilChanged()
            .Throttle(TimeSpan.FromSeconds(1))
            .ThrottleFrame(1)
            .ThrottleFirst(TimeSpan.FromSeconds(1))
            .ThrottleFirstFrame(1)
            .TimeInterval()
            .Timeout(TimeSpan.FromSeconds(1))
            .TimeoutFrame(1)
            .First()
            .FirstOrDefault()
            .Single()
            .SingleOrDefault()
            .Last()
            .LastOrDefault()
            .Take(1)
            .TakeWhile(_ => Input.GetKey(KeyCode.Space))
            .TakeUntil(this.LateUpdateAsObservable())
            .Skip(1)
            .SkipWhile(_ => Input.GetKey(KeyCode.Space))
            .SkipUntil(this.LateUpdateAsObservable())
            .IgnoreElements();
        //型を利用して発行する場合のみ使用可能
        Subject<EventBase> subject = new Subject<EventBase>();
        subject.OfType<EventBase, Event>();
    }
}

参考サイト

qiita.com

これ書きかけです。 とりまここまで自分用に公開