2022年現在におけるUniRxの使いみち

# はじめに

私が「UniRx入門」という記事を書き始めてから5年、最後に投稿してから4年が経過してしまいました。
記事を更新していなかったこの数年間で、UniRxを取り巻く環境が大きく変わってしまいました。
UniRxというライブラリ自体に特に大きな更新はないのですが、UnityのC#バージョンがアップデートされたり、UniRxよりも便利なライブラリが登場したりしました。

今回は2022年現在のUnityにおけるUniRxの立ち位置と、その使い方について解説します。

# UniRxの立ち位置

## 現代のUniRxの状況

2017年頃の古いバージョンのUnityにおいては`.NET 3.5`相当のかなり貧弱なC#しか用いることができませんでした。
非同期処理に`async/await`すら使えず、「コルーチン」で書くか「UniRx」で書くかという状況でした。
そのため、時間が関係した処理はコルーチンかUniRxを使って書かざるを得ませんでした。

ですが、2022年現在においては`C# 8.0`/`.NET Standard 2.0`と、かなり新しいめのC#が利用可能になりました。
このため何か機能を実装するにあたって2017年ごろと比べて取れる選択肢が大幅に増え、一昔前では一般的だった手法も現代では時代遅れとなったものもあります。
UniRxもそのうちのひとつです。
現代においては無理にUniRxを使う必要がなく、`async/await`で書いたり、他のライブラリを併用した方がスマートに終わるというパターンが存在しています。

UniRx自体は確かに便利で強力なライブラリではあり、ユースケースがぴったりハマれば非常に便利です。
ですがUniRx自体がそれなりにややこしく、また記法も特殊であるため、使いこなすようになるまでにかなり時間がかってしまいます。
その上で、現代においては「他にベターな選択肢があったりするのでUniRxを無理に使う必要はない」という状況であったりします。

そのため、 **UniRxはUnity初心者が真っ先に学習するようなライブラリではなくなった（`async/await`から先に学習した方がより実践的である）** という状況です。

とはいえど、UniRxの出番が完全に無くなったわけではなく、用途によってはまだまだ現役で便利に使えるライブラリです。
そういった前提を踏まえて、現代におけるUniRxの用途をまとめてみます。

## 現代のUniRxの用途

UniRx自体は「イベント処理」と「非同期処理」に特化したライブラリです。
このうち「イベント処理」についてはUniRxは現役バリバリで使えるレベルに便利です。
一方の「非同期処理」については`async/await`が使える現代においては、UniRxを使うことはまずない状況でしょう。

また、UniRxの特徴である豊富なオペレータ群も、ぴったりハマるシチュエーションであればまだまだ扱えます。
ただし複雑なオペレータチェインになるくらいなら、素直に`async/await`に書き下したほうがトータルでわかりやすくなる場面も多いです。

**UniRxの用途**

 * イベント処理の機構として使う
   * 複数のオブジェクトに対してメッセージを伝達する場面においてはまだまだ使える
   * MessageBrokerとしての用途もあるが、現代においては「MessagePipe」という選択肢もある
 * 複雑なイベント処理のロジックを組む
   * ぴったりハマるオペレータがあるなら簡潔に書ける
   * 少しでも応用をしようとすると一気に難しくなるので、だったら`async/await`を使ったほうがよい
 * 非同期処理の機構として
   * 単発の非同期処理を扱う場合は、**現代においてはasync/awaitで代替することを推奨**
   * ただし`Retry`や`Switch`などの一部オペレータが便利なのでそれ目的で使うことも無くはない
 * UI周りの実装に使う
   * MV(R)Pパターンが便利

## UniRxとasync/awaitとUniTask

さきほどから何度も`async/await`を取り上げていますが、こちらはイメージとしては「**便利になったコルーチン**」という認識でいったんは問題ありません。
`async/await`自体は非同期処理の待機処理を簡素に書けるようにするための、C#の言語機能です。
（仕組みはぜんぜん異なるが）`async/await`は見た目としてはUnityのコルーチンにとても似ています。

UniRxにおいてもコルーチンとの組み合わせは便利だったのですが、`async/await`においてもUniRxと組み合わせるとその利便性が高まります。
特に[UniTask](https://github.com/Cysharp/UniTask)というライブラリが強力であり、こちらを導入することでUnityにおける`async/await`の扱いを大幅に強化することができます。
（**async/awaitとUniTaskは必ずセットで扱って欲しいレベルで強力です**）

ここで誤解してほしくないのですが、「`async/await`があるからUniRxは不要になった」と**いうわけではありません**。
UniRxはUniRxで使える場面はまだまだありますし、`async/await`もまた便利に使える場面は多数あります。
**昔はUniRxしか選択肢が無かったが、現代においてはUniRxを使わなくてももっと簡単に実装できる手法が増えた**というわけです。

## UniRxが便利な場所、async/awaitが便利な場所

UniRxは決して万能ではありません。
シチュエーションによっては`async/await`（もしくは、まったく別の機構）を使ったほうがキレイに書ける場合も多々あります。

ではUniRxと`async/await`をどう使い分けるかですが、基本的には次の使い分けで問題ありません。

* UniRxが便利に使える場面
  * 不特定多数に対して**複数回**イベントメッセージを伝達する場合
  * 特定の処理の流れ（シーケンス）を**何回も繰り返し実行する**場合
  * 依存関係を逆転したい場合
  * Push型で駆動する必要がある場合

* `async/await`（および`UniTask`の組み合わせ）が便利に使える場面
  * 「**1回だけ**実行される処理」を待ち受ける場合
  * 手続き的(`if`文や`for`文)で処理を書き下したい場合
  * 処理の動作がPull型で済む場合


UniRxは「**繰り返し実行すること**」に特化しています。
そのため「何度か発行されるイベントを処理する」「`Update()`のループの代わりに使う」といった用途にマッチしています。

一方で`async/await`は「**なにかの処理を1回だけ待つ**」に特化しています。
そのため「初期化処理が終わるのを待つ」「外部との通信を待つ」といった場合は`async/await`で書いたほうがスマートに書けます。
（単発の処理をUniRxで書くことももちろんできるが、冗長になりやすい）


# 2.実際のUniRxの用途

それでは現代において、UniRxをどのような場面で使うのかを紹介します。
中には**かつてはUniRxを使うこともあったが、現代では別の手法で書いたほうがよい**というものもあります。


## A.イベント通知に使う

イベントとは、「何かしらの条件を満たした時に、そのときの情報を通知し別の場所で処理を実行する」という仕組みを指します。
イベントを用いると「条件を判定する部分」と「実際に処理を行う部分」を分離して実装することができるようになります。

```plantuml
@startuml

class EventSender

class Subscriber1
class Subscriber2
class Subscriber3


EventSender ..> Subscriber1 : event
EventSender ..> Subscriber2 : event
EventSender ..> Subscriber3 : event

@enduml
```


イベントの使いみちとしては次のパターンが考えられます。

* 不定期に何回か繰り返す処理を扱いやすくする
* 実際の処理部分が複数あるときに、その条件判定の部分を一箇所に集約する
* コンポーネント間の依存関係を整理する

「不定期に何回か繰り返す処理を扱いやすくする」はUnityにおいては`OnTriggerEnter`などのイベントが例として挙げられます。
「いつ起きるかわからないが、発生したときは即座に対応した処理を実行したい」といったときにイベントの概念が使えます。

```plantuml
@startuml

object UnitySystem.Physics

note top of UnitySystem.Physics: 物理演算はUnity内部で実行され、\n何か起きた時にイベントとして\nMonoBehaviourに通知する


class MonoBehaviour_A
{
    + OnTriggerEnter
}

class MonoBehaviour_B
{
    + OnTriggerEnter
}

UnitySystem.Physics -> UnitySystem.Physics : 物理演算の実行
UnitySystem.Physics ..> MonoBehaviour_A : 衝突イベント
UnitySystem.Physics ..> MonoBehaviour_B


@enduml
```


「実際の処理部分が複数あるときに、その条件判定の部分を一箇所に集約する」は、たとえば「Input」が挙げられます。
ゲームを遊んでいる人間（プレイヤー）からのInputを受け付けてキャラクタを操作することになります。
このときに何も考えずに愚直に実装すると、さまざまなコンポーネントに`if(Input.GetKeyDown("Attack"))`といった処理が散らかってしまいます。
こういった問題もイベントの概念を使うことでスマートに実装することが可能となります。




### UniRxとイベント

UniRxはイベント処理を扱うのに長けたライブラリです。
`Observable`というオブジェクト（および概念）でイベントを扱うことができます。

UniRxを用いたイベント処理の例として、さきほどの「プレイヤーからのInputを受け付けてキャラクタを操作する」を考えてみます。

#### UniRxでInputイベントを扱う

まず「Inputを判定してそれをObservableに変換するコンポーネント」を考えます。


```cs
using UniRx;
using UnityEngine;

namespace Events
{
    public sealed class InputEventProvider : MonoBehaviour
    {
        /// <summary>
        /// 攻撃ボタン入力
        /// </summary>
        public IReadOnlyReactiveProperty<bool> Attack => _attack;

        /// <summary>
        /// 移動方向入力
        /// </summary>
        public IReadOnlyReactiveProperty<Vector3> MoveDirection => _moveDirection;

        /// <summary>
        /// ジャンプ入力
        /// </summary>
        public IReadOnlyReactiveProperty<bool> Jump => _jump;

        // 実装
        private readonly ReactiveProperty<bool> _attack = new BoolReactiveProperty();
        private readonly ReactiveProperty<bool> _jump = new BoolReactiveProperty();
        private readonly ReactiveProperty<Vector3> _moveDirection = new ReactiveProperty<Vector3>();

        private void Start()
        {
            // Destroy時にDispose()する
            _attack.AddTo(this);
            _jump.AddTo(this);
            _moveDirection.AddTo(this);
        }

        private void Update()
        {
            // 各種入力をReactivePropertyに反映
            _jump.Value = Input.GetButton("Jump");
            _attack.Value = Input.GetButton("Attack");
            _moveDirection.Value = new Vector3(
                x:Input.GetAxis("Horizontal"), 
                y:0,
                z:Input.GetAxis("Vertical"));
        }
    }
}
```

このコンポーネントを用意したら、実際に入力イベントを使って処理を行うコンポーネントからこれを参照させます。
これで「UniRxを用いて入力イベントを扱う」ことができました。

```cs
using System;
using UniRx;
using UnityEngine;

namespace Events
{
    /// <summary>
    /// 例：Inputをみて移動する
    /// </summary>
    public class PlayerMove : MonoBehaviour
    {
        [SerializeField] private float _moveSpeed = 1.0f;
        [SerializeField] private InputEventProvider _inputEventProvider;

        private CharacterController _characterController;

        private void Start()
        {
            _characterController = GetComponent<CharacterController>();

            // ジャンプ
            // ジャンプボタン入力イベントを判定
            _inputEventProvider.Jump
                // ボタンが押された時に、
                .Where(x => x)
                // 接地中であり、
                .Where(_ => _characterController.isGrounded)
                // 最後にジャンプしてから1秒以上経過しているなら、
                .ThrottleFirst(TimeSpan.FromSeconds(1))
                .Subscribe(_ =>
                {
                    // ジャンプ処理を実行する
                    Jump();
                });
            
            // 移動処理
            _inputEventProvider
                .MoveDirection
                // 一定値以上入力されたなら
                .Where(x=>x.magnitude > 0.5f)
                .Subscribe(x =>
                {
                    // そっち方向に移動する
                    _characterController.Move(x.normalized * _moveSpeed);
                });
        }

        private void Jump()
        {
            // ジャンプ処理（省略）
        }
    }
}
```

```plantuml
@startuml

class InputEventProvider
{
    + IReadOnlyReactiveProperty<bool> Attack
    + IReadOnlyReactiveProperty<Vector3> MoveDirection 
    + IReadOnlyReactiveProperty<bool> Jump 
}

class PlayerMove<<MonoBehaviour>>

PlayerMove --u> InputEventProvider
note right of PlayerMove: 入力イベント発行は\nInputEventProvider任せ

@enduml
```


### 補足:「Pub/Sub」という概念

イベント処理の一種として、「`Pub/Sub`」というものがあります。

一般的なイベント処理においては、一般的には「誰からメッセージが送られてくるか」を購読側がある程度は意識する必要があります。
それを`Pub/Sub`においては「イベントメッセージそのもの」に注目し、「誰から送られてきているかは気にしない」というモデルとなっています。
（同様に送信側も「誰が受信しているかは気にしない」という形になります）

```plantuml
@startuml
left to right direction

interface IPublisher
class Publisher_A
class Publisher_B

interface ISubscriber

class Subscriver_A
class Subscriver_B
note top of Subscriver_A : 受信者は送信者が誰か知らない



Publisher_A --> IPublisher
Publisher_B --> IPublisher
ISubscriber <-- Subscriver_A
ISubscriber <-- Subscriver_B
note top of Publisher_A : 送信側は受信者が誰か知らない


note "Pub/Subの本体は隠蔽され、\n見えないどこかを通って\nメッセージが伝達される" as AAA

IPublisher .. AAA
AAA .. ISubscriber

IPublisher ..> ISubscriber : Message

@enduml
```

`Pub/Sub`を使うことで、送信側と受信側をより疎結合に近づけることができます。
コンポーネント間の参照関係や依存関係を整理し振り回されることなく、よりデータフローを中心とした実装を行うことが可能となります。
しかし、その一方で正しくメッセージ管理できないとスパゲッティコードが加速するという問題点も`Pub/Sub`は持っています。
初心者にオススメできる機能ではないのですが、こういったものもあると頭の片隅においておくといずれ役に立つときがくるでしょう。

さてこの`Pub/Sub`ですが、実装する方法はいくつかあります。

* UniRxの「`MessageBroker`」という機能を使う
* [MessagePipe](https://github.com/Cysharp/MessagePipe)というライブラリを使う


`Pub/Sub`を軽く試してみたいのであればUniRxの`MessageBroker`が簡単に使えてオススメです。
そこからさらに踏み込んで、「DIと組み合わせて使いたい」「サーバ通信を絡めた`Pub/Sub`を行いたい」といった場合は`MessagePipe`を使ってみるとよいでしょう。


## B.非同期処理に使う

先に結論から述べると、**非同期処理にUniRxを使うことは非推奨です**。
かつて、2017年以前のUnityにおいては非同期処理の選択肢としてまともなものがUniRxくらいしかありませんでした。
しかし現代においては`async/await`や`UniTask`の登場によって、わざわざUniRxを使って非同期処理を扱う必要性がなくなりました。

（そもそも「非同期処理」とは何を指すかですが、こちらは語ると長くなるため別記事で改めて投稿予定です）

#### 追記

「非同期処理にUniRxを使うことは非推奨」ではあるものの、一部のOperatorが非常に便利だったりします。
とくに`OnErrorRetry`はエラー発生時に指定回数までリトライをしてくれるものです。
こういったOperatorを`async/await`と`try-catch`で書くと結構煩雑になったりするので、上手く`async/await`と`Observable`を併用するのも上級者向けではありますがテクニックとして存在します。

ただ、愚直に`ToObservable().OnErrorRetry()`とだけしても上手くは動かず、`Observable.Defer()`と併用する必要があったりします。
このあたりの挙動は結構ややこしいので、なぜ`Observable.Defer()`が必要なのかわからない人は、このテクニックは使わないほうが安全かもしれないです。

```cs
private async UniTaskVoid SampleAsync(string uri, CancellationToken token)
{
    var result = 
        // Observable.Deferで包むことで、Retry発火時にGetAsyncを再度実行するようにする
        await Observable.Defer(() => GetAsync(uri, token).ToObservable())
        // エラー発生時は1秒待ってから、合計3回まで試行する
        .OnErrorRetry((UnityWebRequestException ex) => Debug.LogException(ex), retryCount: 3, TimeSpan.FromSeconds(1));

    Debug.Log(result);
}

private async UniTask<string> GetAsync(string uri, CancellationToken token)
{
    using (var uwr = UnityWebRequest.Get(uri))
    {
        await uwr.SendWebRequest().WithCancellation(token);
        return uwr.downloadHandler.text;
    }
}
```

### 例：AsyncOperation

Unityで登場する非同期処理として扱う必要があるオブジェクトとして`例：AsyncOperation`があります。
こちらはUnityAPI上で何か非同期な処理を呼び出した時に返されるオブジェクトです。

【`AsyncOperation`なオブジェクトを返すUnityのAPIの例】

* `UnityWebRequest.SendWebRequest()`
* `SceneManager.LoadSceneAsync()`
* `AssetBundle.LoadAssetAsync`

`AsyncOperation`は本来であればコルーチンで処理するオブジェクトですが、`UniTask`を導入している場合は`async/await`で記述することが可能です。


```cs:UnityWebRequest
// テクスチャをダウンロードする
public async UniTask<Texture> FetchTextureAsync(string uri, CancellationToken token)
{
    using (var uwr = UnityWebRequestTexture.GetTexture(uri))
    {
        await uwr.SendWebRequest().WithCancellation(token);
        return ((DownloadHandlerTexture) uwr.downloadHandler).texture;
    }
}
```

---
（以下非推奨）

かつて、UniRxくらいしかまともな非同期処理のハンドリング方法がなかった時代は次のような書き方をしていました。
`async/await`と比較してもらえるとわかりますが、**UniRxの方が圧倒的に冗長で煩雑です。**

```cs
/// <summary>
/// テクスチャをダウンロードするObservable
/// </summary>
public IObservable<Texture> FetchTextureObservable(string uri)
{
    // コルーチンをObservableに変換する
    return Observable.FromCoroutine<Texture>(observer => 
        FetchTextureCoroutine(uri, observer));
}

/// <summary>
/// 通信するコルーチン
/// </summary>
private IEnumerator FetchTextureCoroutine(
    string uri,
    IObserver<Texture> observer)
{
    using (var uwr = UnityWebRequestTexture.GetTexture(uri))
    {
        yield return uwr.SendWebRequest();

        if (uwr.result != UnityWebRequest.Result.Success)
        {
            observer.OnError(new Exception(uwr.error));
        }
        else
        {
            var result = ((DownloadHandlerTexture) uwr.downloadHandler).texture;
            observer.OnNext(result);
            observer.OnCompleted();
        }
    }
}
```

## C.Model-View-(Reactive)Presenterパターンに使う

`Model-View-(Reactive)Presenter`、通称`MV(R)P`パターンはUnityにおいて主にUIの実装周りで使われることが多いパターンです。
`View`と`Model`の2つのオブジェクトをUniRxを用いて連結する実装パターンとなります。

こちらは別の記事で解説しています。

- [【Unity】Model-View-(Reactive)Presenterパターンとは何なのか](https://qiita.com/toRisouP/items/5365936fc14c7e7eabf9)


## D.MonoBehaviourのロジックを記述する

UniRxに存在する`UpdateAsObservable()`や`Observable.EveryUpdate()`を愛用している方は多いでしょう。
こちらについては**UniRxをそのまま使ってもよいが、場合によっては`async/await`の方が簡単にかける場合もある**とおぼえておくとよいでしょう。

### 簡単なロジックをUniRxで記述する

例として次のロジックを考えてみます。

* 条件を満たしたら処理を実行し、その後数秒間クールタイムに入る

（攻撃を出したらその後数秒間は再度攻撃ができない、みたいな）

これをUniRxで記述すると次のようになります。

```cs
using System;
using UniRx;
using UniRx.Triggers;
using UnityEngine;

namespace Samples
{
    public sealed class SimpleLogicUniRx : MonoBehaviour
    {
        private void Start()
        {
            // 毎フレーム実行
            this.UpdateAsObservable()
                // 攻撃ボタンが押されたら
                .Where(_ => Input.GetButtonDown("Attack"))
                // 処理を1回走らせたあと、1秒間クールタイム
                .ThrottleFirst(TimeSpan.FromSeconds(1))
                .Subscribe(_ => Action())
                .AddTo(this);
        }

        /// <summary>
        /// 定期的に実行する処理
        /// </summary>
        private void Action()
        {
            // なんかする
            Debug.Log("Action!");
        }
    }
}
```

こういった「オペレータを組み合わせるだけで実装できる処理」についてはUniRxを使って書いても問題ありません。

### UniRxでの記述が難しいやつ

UniRxのメリットは「豊富なオペレータが使える」ですが、逆にデメリットとしてオペレータの範囲外の処理がものすごく書きづらくなってしまいます。

さきほどの例の「条件を満たしたら何か処理を実行しその後数秒間クールタイムに入る」を少し拡張して、次のような処理を考えてみましょう。

* 条件Aを満たしたら処理Xを呼び出し、N秒間クールタイムに入る
* 条件Bを満たしたら処理Yを呼び出し、M秒間クールタイムに入る
* 処理XとYはそれぞれ排他であり、お互いのクールタイム中はお互いの処理がブロックされる

わかりやすく言い換えるなら、「強攻撃を出したらクールタイムが長い。弱攻撃を出したらクールタイムが短い。クールタイム中は攻撃が一切できない」みたいなパターンです。

さて、これをUniRxだけで記述しようとするとどうなるでしょうか。
オペレータの単純な連結だけでは実装できず、かなり入り組んだものを書く必要がでてきそうです。
**このような途中で条件分岐が入ったり、条件によって処理の内容が大幅に変わってしまうものをUniRxは非常に苦手としています。**

こうった場合は**UniRxを使わずに`async/await`（またはコルーチン）で書いてしまったほうが結果としてキレイに実装できます。**

```cs:コルーチン版
using System.Collections;
using UnityEngine;

namespace Samples
{
    public sealed class ComplexLogicCoroutine : MonoBehaviour
    {
        private void Start()
        {
            StartCoroutine(LogicLoop());
        }

        private IEnumerator LogicLoop()
        {
            // Destroyされるまで無限ループ
            while (true)
            {
                if (Input.GetButtonDown("AttackA"))
                {
                    // 入力Aが実行されたら処理Xを呼んで1秒待機
                    ActionX();
                    yield return new WaitForSeconds(1);
                }
                else if (Input.GetButtonDown("AttackB"))
                {
                    // 入力Bが実行されたら処理Yを呼んで2秒待機
                    ActionY();
                    yield return new WaitForSeconds(2);
                }
                else
                {
                    // 入力がないなら1フレーム待機
                    yield return null;
                }
            }
        }

        private void ActionX()
        {
            Debug.Log("do X!");
        }

        private void ActionY()
        {
            Debug.Log("do Y!");
        }
    }
}
```

```cs:async/await+UniTask版
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace Samples
{
    public sealed class ComplexLogicUniTask : MonoBehaviour
    {
        private void Start()
        {
            // CancellationToken生成
            var token = this.GetCancellationTokenOnDestroy();

            // ループ起動
            LogicLoopAsync(token).Forget();
        }

        private async UniTaskVoid LogicLoopAsync(CancellationToken ct)
        {
            // Destroyされるまで無限ループ
            while (!ct.IsCancellationRequested)
            {
                if (Input.GetButtonDown("AttackA"))
                {
                    // 入力Aが実行されたら処理Xを呼んで1秒待機
                    ActionX();
                    await UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: ct);
                }
                else if (Input.GetButtonDown("AttackB"))
                {
                    // 入力Bが実行されたら処理Yを呼んで2秒待機
                    ActionY();
                    await UniTask.Delay(TimeSpan.FromSeconds(2), cancellationToken: ct);
                }
                else
                {
                    // 入力がないなら1フレーム待機
                    await UniTask.Yield();
                }
            }
        }

        private void ActionX()
        {
            Debug.Log("do X!");
        }

        private void ActionY()
        {
            Debug.Log("do Y!");
        }
    }
}
```

### MonoBehaviourのロジックを記述する まとめ

* `Update()`や`FixedUpdate()`に関係したロジックはUniRxで記述ができる
* ただしUniRxを使う場合は既存のオペレータで実装できる範囲内に留めておく
* **少しでもUniRxで書けないと感じたらさっさと諦めてコルーチンやasync/awaitに書き直したほうが最終的に読みやすくなる**

## E.Update()などのスコープを分離する

UniRxの使いみちとして「`Update()`などのスコープを分離する」があります。

 * `Update()`や`FixedUpdate()`などにベタ書きされた処理をコンテキストごとに分離する
 * `Update()`や`FixedUpdate()`の実行開始タイミングを調整する

 このような場合にUniRxを用いることができます。

### 例:Update()をコンテキストごとに分離する

 たとえば次のような、`Update()`に複数の処理がまとめて記述されてるコードがあったとします。

```cs
using UnityEngine;

namespace Samples
{
    public sealed class SamplePlayer : MonoBehaviour
    {

        // ここにたくさんのフィールド変数が定義されていたとして

        private void Update()
        {
            // 攻撃処理のチェックが走る
            CheckAttack();

            // Playerの位置に応じた処理が走る
            CheckPosition();

            // Playerの体力に応じた処理が走る
            CheckHealth();

            // 移動処理
            Move();
        }

        // ↓ にメソッドが並んでたとする
        
        /*
         * 省略
         */
    }
}
```

さて、この`Update()`をみて感じることは無いでしょうか。
「**これは処理の順番は入れ替えても大丈夫なのか**」「**どの処理とどの処理がセットなんだ**」
といった疑問は出てこないでしょうか。

これらの処理は順不同で並べ替えても正しく動作するかもしれませんし、実際は裏でつながっておりこの順序が重要かもしれません。
このような旨がコメントに書いてあるかもしれませんし、まったくコメントが無いコードかもしれません。
`Update()`という1つのメソッドに並べて書いている以上は、常にこういったことを意識してコードを書く/読む必要が生まれてしまいます。

こういった問題はUniRxを使うことで**多少はマシにすることができます**。

```cs
using System;
using UniRx;
using UniRx.Triggers;
using UnityEngine;

namespace Samples
{
    public sealed class SamplePlayer : MonoBehaviour
    {
        private void Start()
        {
            // Playerの位置に応じた処理
            this.UpdateAsObservable()
                .Subscribe(_ => CheckPosition())
                .AddTo(this);

            // Playerの体力に応じた処理
            this.UpdateAsObservable()
                .Subscribe(_ => CheckHealth())
                .AddTo(this);

            // 攻撃処理のチェックと移動処理
            this.UpdateAsObservable()
                .Subscribe(_ =>
                {
                    CheckAttack();
                    Move();
                })
                .AddTo(this);
        }
        
        // ↓ にメソッドが並んでたとする

        /*
         * 省略
         */
    }
}
```

このように、各処理ごとに別々の`Observable`に格納することで処理同士のスコープを明確に区切ることができました。
また処理ごとに`Observable`が分離しているため、一部処理だけオペレータを追加したり調整もしやすくなります。

### 例:一部処理だけ実行開始タイミングをずらす

`Update()`を各処理ごとの`Observable`に分けてしまう利点として、「実行開始タイミングを調整しやすい」というものがあります。

たとえば特定のメソッドが呼び出されるまでは`Update()`の処理の一部をスキップしておきたい、といった場合です。
（敵キャラが画面内に映るまで移動処理を止めておきたい、など）

```cs
using UniRx;
using UniRx.Triggers;
using UnityEngine;

namespace Samples
{
    public sealed class SampleEnemy : MonoBehaviour
    {
        private void Start()
        {
            // 画面に映ったら初期化処理を呼び出す
            this.OnBecameVisibleAsObservable()
                .Take(1)
                .Subscribe(_ => Initialize())
                .AddTo(this);
        }

        /// <summary>
        /// 初期化処理
        /// </summary>
        private void Initialize()
        {
            // 毎フレームMove()を呼び出すようにする
            this.UpdateAsObservable()
                .Subscribe(_ => Move())
                .AddTo(this);
        }

        /// <summary>
        /// 移動処理
        /// </summary>
        private void Move()
        {
            // 移動処理
        }
    }
}
```

### Update()などのスコープを分離する まとめ

これはUniRxが処理をすべて`Observable`というオブジェクトに包む性質を利用した、ちょっとしたテクニックです。
覚えておくと地味ながら便利なので、個人的にはたまに使う小技だったりします。

なお、このテクニックを`async/await + UniTask`で記述することも可能ではあります。
ただしUniRxと比べこちらは記述量が増え冗長になってしまいます。
そのためこの用途においてはUniRxに軍配が上がります。

```cs:同じようなことをasync/awaitで書けなくもないが、冗長
private void Start()
{
    var token = this.GetCancellationTokenOnDestroy();

    UniTask.Void(async () =>
    {
        while (!token.IsCancellationRequested)
        {
            CheckPosition();
            await UniTask.Yield();
        }
    });

    UniTask.Void(async () =>
    {
        while (!token.IsCancellationRequested)
        {
            CheckHealth();
            await UniTask.Yield();
        }
    });

    UniTask.Void(async () =>
    {
        while (!token.IsCancellationRequested)
        {
            CheckAttack();
            Move();
            await UniTask.Yield();
        }
    });
}

```

## F.依存関係の整理に使う

UniRxは依存関係を整理するのに用いることができます。
（UniRxの機能のというよりかは、`Observer`パターンの性質そのもの）

さきほど説明した「イベント処理」と同じ話ではあります。
（視点が依存関係の整理になっているだけで、やってることはイベント処理そのもの）

### 例：PlayerとPlayerManager

たとえば、次のようなケースを考えてみます。

* `PlayerManager` : `Player`を生成し、ライフサイクルを管理する
 * `Player` : 自分が死んだことを`PlayerManager`に伝える
 

 さて、これを愚直にそのまま実装すると、次のようなコードになるでしょう。

```cs:PlayerManager
using UnityEngine;

namespace Samples2
{
    public class PlayerManager : MonoBehaviour
    {
        // PlayerのPrefab
        [SerializeField] private Player _playerPrefab;

        // 今存在しているPlayerの実体
        private Player _currentPlayer;

        private void Start()
        {
            CreatePlayer();
        }

        public void OnPlayerDead()
        {
            // Playerが死んだ時の処理がここに
            _currentPlayer = null;

            // 新しいPlayerを生成する
            CreatePlayer();
        }

        private void CreatePlayer()
        {
            // Playerを生成
            _currentPlayer = Instantiate(_playerPrefab);

            // PlayerにManagerを教える
            _currentPlayer.Initialize(this);
        }
    }
}
```

```cs
using UnityEngine;

namespace Samples2
{
    public class Player : MonoBehaviour
    {
        private PlayerManager _playerManager;

        public void Initialize(PlayerManager playerManager)
        {
            // 初期化時にManagerを保持する
            _playerManager = playerManager;
        }
        
        private void OnDestroy()
        {
            // 今回はOnDestroyされたら「死亡」という扱いにする
            _playerManager.OnPlayerDead();
        }
    }
}
```

このコードにはひとつ大きな問題が存在します。
それは`Player`と`PlayerManager`が相互参照していることです。

```plantuml
@startuml
class PlayerManager
{
  + void OnPlayerDead()
}

class Player


Player -> PlayerManager : "Player死亡時に\nOnPlayerDead()を呼び出す"
PlayerManager --> Player : "Playerを生成する"
@enduml
```

相互参照は後にスパゲッティコードへと発展するリスクが非常に高いです。
そのためできる限り相互参照は除去すべきです。

そこでこれをUniRx（またはUniTask）を用いて整理してみます。

### UniRxを用いて依存関係を整理する

UniRxの`Observable`を利用することで、参照関係を一方通行に整理することが可能となります。

```cs
using UniRx;
using UnityEngine;

namespace Samples2
{
    public class PlayerManager : MonoBehaviour
    {
        // PlayerのPrefab
        [SerializeField] private Player _playerPrefab;

        // 今存在しているPlayerの実体
        private Player _currentPlayer;

        private void Start()
        {
            CreatePlayer();
        }

        private void OnPlayerDead()
        {
            // Playerが死んだ時の処理がここに
            _currentPlayer = null;

            // 新しいPlayerを生成する
            CreatePlayer();
        }

        private void CreatePlayer()
        {
            // Playerを生成
            _currentPlayer = Instantiate(_playerPrefab);

            // 生成したPlayerを監視して
            // 死亡イベントが来たらOnPlayerDeadを実行
            _currentPlayer
                .PlayerDeadAsync
                .Subscribe(_ => OnPlayerDead())
                .AddTo(this);
        }
    }
}
```

```cs
using System;
using UniRx;
using UnityEngine;

namespace Samples2
{
    public class Player : MonoBehaviour
    {
        /// <summary>
        /// Playerが死亡した通知を発行するObservable
        /// </summary>
        public IObservable<Unit> PlayerDeadAsync => _playerDeadSubject;

        // 1回だけ通知を発行するケースにおいてはAsyncSubjectが便利
        private readonly AsyncSubject<Unit> _playerDeadSubject = new AsyncSubject<Unit>();

        private void OnDestroy()
        {
            // Playerが死亡した通知を発行する
            _playerDeadSubject.OnNext(Unit.Default);
            _playerDeadSubject.OnCompleted();

            _playerDeadSubject.Dispose();
        }
    }
}
```

```plantuml
@startuml
class PlayerManager

class Player
{
 + IObservable<Unit> PlayerDeadAsync
}

PlayerManager -> Player : "Playerを生成する"
PlayerManager --> Player : "PlayerDeadAsync\nを監視する"
@enduml
```

UniRxの`Observable`を用いることで通知フローを整理し、参照関係を一方通行にすることができました。
そもそも、`Observable`は「不特定多数に自分の状態を監視させる」という用途のための機能です。
そのためこのような「何か状態が変化したとき、それを相手に伝える」という場面においては`Observable`は強力に作用します。

#### 応用:状態の通知

`Observable`の「不特定多数に自分の状態を監視させる」という用途をもう少し考えてみます。

たとえばアクションゲームにおいて「何かイベントが起きたときそれに応じて複数の処理が同時に走る」というパターンはよく存在します。
そのような処理を実装する場合、イベント発行側が通知先をすべて把握するようなコードは大変扱いにくいものになります。

例として次のような実装があったとします。

* Playerがダメージを受けた時、次の処理が走る
  * Healthが低下する
  * ノックバックする
  * ダメージアニメーションを再生する
  * パーティクルエフェクトが出る
  * 効果音を再生する
  * デバッグ時だけUIに数値を出したい

これを「ダメージを受けたときにひとつひとつ通知先のメソッドを呼び出していく」という実装にするとこうなります。

```plantuml
@startuml
namespace PlayerComponents{

class PlayerCore
note  top of PlayerCore: Playerのダメージを管理するやつ

class PlayerHealth
{
 + OnPlayerDamaged(int value)
}
note  bottom of PlayerHealth : 体力管理コンポーネント

class PlayerMove 
{
 + OnPlayerDamaged(int value)
}
note  bottom of PlayerMove : 移動制御コンポーネント

class PlayerAnimation
{
 + OnPlayerDamaged(int value)
}
note  bottom of PlayerAnimation : アニメーション管理コンポーネント

class PlayerEffectEmmiter 
{
 + OnPlayerDamaged(int value)
}
note  bottom of PlayerEffectEmmiter: パーティクルエフェクト管理コンポーネント

class PlayerSfx 
{
 + OnPlayerDamaged(int value)
}
note  bottom of PlayerSfx : 効果音コンポーネント


PlayerCore --> PlayerHealth
PlayerCore --> PlayerMove 
PlayerCore --> PlayerAnimation 
PlayerCore --> PlayerEffectEmmiter 
PlayerCore --> PlayerSfx 

namespace Debug{
 class DebugGUI
 {
 + OnPlayerDamaged(int value)
}
 note  bottom of DebugGUI: Debug時のみここに通知したい

}

PlayerComponents.PlayerCore ---r> PlayerComponents.Debug.DebugGUI

}
@enduml
```

この実装の弱点は「`PlayerCore`がすべてを管理しなくてはいけない」という点にあります。
通知先をすべて`PlayerCore`が知っている状態にし、なおかつ状況に応じて通知する/しないの判断を`PlayerCore`が行わなくてはいけません。
そのため`PlayerCore`の責務が膨れ上がり、どんどん複雑なコードへと成長してしまいます。

---

こういった問題は`Observable`を使うことで解決することができます。

`PlayerCore`に`Observable`を定義し各コンポーネントが必要に応じて`Subscribe`する形式にしてあげます。
こうすることで`PlayerCore`の責務が大幅に減り、「イベント通知をどう扱うか」の責務が各コンポーネントに分散されます。

```plantuml
@startuml
namespace PlayerComponents{

class PlayerCore
{
 + IObservable<int> OnDamagedAsObservable()
}
note  top of PlayerCore: Observableでダメージイベントをブロードキャストする

class PlayerHealth

note  bottom of PlayerHealth : 体力管理コンポーネント

class PlayerMove 

note  bottom of PlayerMove : 移動制御コンポーネント

class PlayerAnimation

note  bottom of PlayerAnimation : アニメーション管理コンポーネント

class PlayerEffectEmmiter 
note  bottom of PlayerEffectEmmiter: パーティクルエフェクト管理コンポーネント

class PlayerSfx 

note  bottom of PlayerSfx : 効果音コンポーネント


PlayerCore <-- PlayerHealth
PlayerCore <-- PlayerMove 
PlayerCore <-- PlayerAnimation 
PlayerCore <-- PlayerEffectEmmiter 
PlayerCore <-- PlayerSfx 

namespace Debug{
 class DebugGUI
 note  bottom of DebugGUI: Debug時のみSubscribe()する

}

PlayerComponents.PlayerCore <---r PlayerComponents.Debug.DebugGUI

}
@enduml
```


### UniTaskを用いて依存関係を整理する

`UniTask`と`async/await`を使って依存関係を整理することができます。
ただしUniRxと違い、`UniTask`は「イベント通知回数が1回に限る」場合においてのみ利用可能です。

```cs:Player
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace Samples2
{
    public class Player : MonoBehaviour
    {
        /// <summary>
        /// Playerが死亡したら完了するUniTask
        /// </summary>
        public UniTask PlayerDeadAsync => _playerDeadUtc.Task;

        private readonly UniTaskCompletionSource _playerDeadUtc = new UniTaskCompletionSource();

        private void OnDestroy()
        {
            // Playerが死亡したUniTaskを完了させる
            _playerDeadUtc.TrySetResult();
        }
    }
}
```

```cs:PlayerManager
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace Samples2
{
    public class PlayerManager : MonoBehaviour
    {
        // PlayerのPrefab
        [SerializeField] private Player _playerPrefab;

        // 今存在しているPlayerの実体
        private Player _currentPlayer;

        private void Start()
        {
            SetupPlayerAsync(this.GetCancellationTokenOnDestroy()).Forget();
        }

        private void OnPlayerDead()
        {
            // Playerが死んだ時の処理がここに
            _currentPlayer = null;

            // 新しいPlayerを生成する
            SetupPlayerAsync(this.GetCancellationTokenOnDestroy()).Forget();
        }

        private async UniTaskVoid SetupPlayerAsync(CancellationToken token)
        {
            // Playerを生成
            _currentPlayer = Instantiate(_playerPrefab);

            // 生成したPlayerを監視して
            // 死亡したらOnPlayerDeadを実行
            await _currentPlayer.PlayerDeadAsync;
            token.ThrowIfCancellationRequested();

            OnPlayerDead();
        }
    }
}
```

```plantuml
@startuml
class PlayerManager

class Player
{
 + UniTask PlayerDeadAsync
}

PlayerManager -> Player : "Playerを生成する"
PlayerManager --> Player : "PlayerDeadAsync\nをawaitする"
@enduml
```

### 依存関係の整理に使う まとめ

UniRxを用いることで依存関係を整理し、イベントフローを改善することができます。
また通知回数が1回に限り場合においては「UniRx」か「`UniTask`+`async/await`」のどちらでも記述することも可能です。

なお、個人的には、通知回数が1回だけなら`UniTask`+`async/await`、何度も通知するなら`Observable`と自分は使い分けをしています。
というのも、`Observable`という型だけを見たときに、「この`Observable`は何度イベントを発行するんだろうか」がわかりません。
だったら「`UniTask`なら絶対に多くても1回しか発行されない」「`Observable`なら複数回発行されるだろう」と割り切れた方が楽だからです。
（相手側の実装内容を想像して書く、みたいな余計なことしなくて済むようになる）

## G.スクリプトの実行順の調整に使う

Unityには[Script Execution Order](https://docs.unity3d.com/ja/2022.1/Manual/class-MonoManager.html)という、スクリプトの実行順序を調整するための機構があります。
この`Script Execution Order`ですが、これは最後の砦として残しておくべきものであり**安易に触るべきではない機能です**。

では実行順序を管理したいとなったときにどうするかというと、UniRxを用いてイベントで動作するようにしてしまうことです。
つまり、「前のコンポーネントが終わったら次のコンポーネントが連鎖して実行される」という仕組みにします。
こうすることでコンポーネント間の`Update()`や`FixedUpdate()`の呼び出し順を考えなくてよくなります。

### 例:UniRxでコンポーネント間のタイミングを調整する

たとえば、次のような実装を考えてみます。

 * `PlayerInput`でボタン入力を受け付ける
 * ボタン入力をチェックして、`PlayerMoveController`がジャンプ処理を行う
 * ジャンプ状態になったら`PlayerAnimation`がジャンプアニメーションを再生する


「各コンポーネントが`Update()`を実行し、条件を満たしたら各処理を実行する」といった愚直な実装も可能ではあります。
ですがこの書き方の場合、実行順序がずれて処理が1F遅れたり、状態チェックを取りこぼすなどの不具合が起きかねません。

そのためそのような`Update()`を多用するコードを止め、**UniRxを用いて処理が連鎖する実装**にしてみます。

```cs
using UniRx;
using UnityEngine;

namespace Samples3
{
    /// <summary>
    /// 入力イベントを管理する
    /// </summary>
    public sealed class PlayerInput : MonoBehaviour
    {
        /// <summary>
        /// ジャンプボタンの入力状態を表すReactiveProperty
        /// </summary>
        public IReadOnlyReactiveProperty<bool> JumpButton => _jumpButton;

        private readonly ReactiveProperty<bool> _jumpButton = new ReactiveProperty<bool>(false);

        /*
         * 他にもいろいろイベント並んでるだろうけど省略
         */

        private void Start()
        {
            // Destroy時にDisposeされるようにする
            _jumpButton.AddTo(this);
        }

        private void Update()
        {
            // ボタンの状態を反映して通知する
            _jumpButton.Value = Input.GetButton("Jump");
        }
    }
}
```

```cs
using System;
using UniRx;
using UniRx.Triggers;
using UnityEngine;

namespace Samples3
{
    public sealed class PlayerMoveController : MonoBehaviour
    {
        /// <summary>
        /// 接地しているか
        /// </summary>
        public IReadOnlyReactiveProperty<bool> IsGrounded => _isGrounded;

        private readonly ReactiveProperty<bool> _isGrounded = new ReactiveProperty<bool>(false);

        /// <summary>
        /// ジャンプイベント
        /// </summary>
        public IObservable<Unit> OnJump => _jumpSubject;

        private readonly Subject<Unit> _jumpSubject = new Subject<Unit>();

        [SerializeField] private LayerMask _groundLayerMask;
        [SerializeField] private PlayerInput _playerInput;
        private Rigidbody _rigidbody;

        private void Start()
        {
            _isGrounded.AddTo(this);
            _jumpSubject.AddTo(this);
            _rigidbody = GetComponent<Rigidbody>();

            // 接地状態のチェック
            this.FixedUpdateAsObservable()
                .Subscribe(_ =>
                {
                    // Rayを足元に飛ばす
                    _isGrounded.Value = Physics.SphereCast(origin: transform.position + Vector3.up * 0.04f,
                        radius: 0.02f,
                        direction: Vector3.down, hitInfo: out var _, maxDistance: 0.05f, _groundLayerMask);
                })
                .AddTo(this);


            // 入力イベント処理
            _playerInput.JumpButton
                // ジャンプボタンが押された瞬間に接地してたら実行
                .Where(x => x && _isGrounded.Value)
                // InputイベントはUpdate()タイミングなので、
                // 次のFixedUpdateタイミングにタイミングを調整する
                .ObserveOnMainThread(MainThreadDispatchType.FixedUpdate)
                .Subscribe(_ =>
                {
                    // ジャンプ実行
                    Jump();
                })
                .AddTo(this);
        }

        private void Jump()
        {
            _rigidbody.AddForce(Vector3.up * 10.0f, ForceMode.VelocityChange);

            // ジャンプイベント発行
            _jumpSubject.OnNext(Unit.Default);
        }
    }
}
```

```cs
using UniRx;
using UnityEngine;

namespace Samples3
{
    public sealed class PlayerAnimation : MonoBehaviour
    {
        [SerializeField] private Animator _animator;
        [SerializeField] private PlayerMoveController _moveController;

        /// <summary>
        /// ジャンプアニメーション再生
        /// </summary>
        private bool IsJumping
        {
            set => _animator.SetBool("Jumping", value);
        }

        private void Start()
        {
            // ジャンプイベントが来たらジャンプアニメーション開始
            _moveController.OnJump.Subscribe(_ => IsJumping = true).AddTo(this);

            // 接地したらジャンプアニメーション解除
            _moveController.IsGrounded.Where(x => x).Subscribe(_ => IsJumping = false).AddTo(this);
        }
    }
}
```

```plantuml
@startuml

namespace Player{
    class PlayerInput
    {
        + IReadOnlyReactiveProperty<bool> JumpButton
    }

    class PlayerMoveController
    {
        + IReadOnlyReactiveProperty<bool> IsGrounded
        + IObservable<Unit> OnJump
    }

    class PlayerAnimation
    note right of PlayerAnimation
        PlayerAnimationはPlayerMoveControllerの
        イベント通知を受けたときのみ動作する
    end note

    PlayerInput -> PlayerInput : Update()はここのみで走る
    PlayerInput ..d> PlayerMoveController : ジャンプ入力\nイベントの通知
    PlayerMoveController ..d> PlayerAnimation :ジャンプ開始イベント/\n接地状態の通知

    PlayerMoveController -> PlayerMoveController : FixedUpdate()（物理演算を使うため）

    PlayerMoveController --u> PlayerInput : Subscribe()
    PlayerAnimation --u> PlayerMoveController : Subscribe()

}

@enduml
```

UniRxを用いることで、3つのコンポーネントが競合することなく順序立てて動作させることができるようになりました。
このように各コンポーネントから`Update()`や`FixedUpdate()`を極力排除することで、動作順序を完全に制御できるようになります。
（まさにリアクティブ）


### 例:初期化順の調整（async/await）

実行順序の調整は`Update()`や`FixedUpdate()`のみに限らず、初期化（`Start()`や`Awake()`）の順序も重要になることがあったりします。
そのようなシチュエーションにおいてもUniRxは利用ができます。
ですが、**「初期化」といった基本的に1回しか実行されない**処理については「`async/await`+`UniTask`」で書いたほうがキレイになります。


```cs:EnemyManager
using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace Samples3
{
    public sealed class EnemyManager : MonoBehaviour
    {
        [SerializeField] private Enemy _enemyPrefab;


        /// <summary>
        /// 敵を複数体生成して初期化する
        /// </summary>
        public async UniTask InitializeAsync(CancellationToken token)
        {
            // 10個生成してすべての初期化が終わるまで待つ
            // (UniTask.WhenAllを暗黙的に呼び出している)
            await Enumerable.Range(0, 10).Select(x => CreateEnemyAsync(token));
        }

        /// <summary>
        /// 敵を1体生成する
        /// </summary>
        private async UniTask CreateEnemyAsync(CancellationToken token)
        {
            var enemy = Instantiate(_enemyPrefab);
            // 初期化が終わるのを待つ
            await enemy.InitializedAsync;
            // 初期化中にキャンセルされてたら止める
            token.ThrowIfCancellationRequested();
        }
    }
}
```

```cs:Enemy
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

namespace Samples3
{
    /// <summary>
    /// 敵
    /// </summary>
    public sealed class Enemy : MonoBehaviour
    {
        /// <summary>
        /// 初期化が完了しているかどうか
        /// </summary>
        public UniTask InitializedAsync => _initialTaskCompletionSource.Task;
        private readonly UniTaskCompletionSource _initialTaskCompletionSource = new UniTaskCompletionSource();

        private AsyncOperationHandle<GameObject> _operationHandle;
        
        /// <summary>
        /// この敵オブジェクトの子要素（非同期で初期化される）
        /// </summary>
        private GameObject _child;
        
        /// <summary>
        /// Startで初期化処理が実行される
        /// </summary>
        private void Start()
        {
            var ct = this.GetCancellationTokenOnDestroy();
            InitializeAsync(ct).Forget();
        }

        /// <summary>
        /// 初期化処理
        /// </summary>
        private async UniTaskVoid InitializeAsync(CancellationToken token)
        {
            _operationHandle = Addressables.InstantiateAsync("Child");
            await _operationHandle;
            _child = _operationHandle.Result;
        }

        private void OnDestroy()
        {
            Addressables.Release(_operationHandle);

            // もしDestroyが先に走った場合はキャンセルする
            _initialTaskCompletionSource.TrySetCanceled();
        }
    }
}
```

## H.コレクションの変動を通知する

コレクションとは`Array`や`Dictionary`のような複数個のデータを扱うオブジェクトを指します。

### UniRxでコレクションの変動を監視

UniRxには`ReactiveCollection`や`ReactiveDictionary`といったオブジェクトが用意されています。

* [ReactiveCollection](https://qiita.com/toRisouP/items/86fea641982e6e16dac6#reactivecollection)
* [ReactiveDictionary](https://qiita.com/toRisouP/items/86fea641982e6e16dac6#reactivedictionary)

これらのオブジェクトを用いることで、「配列や辞書の中身が変動した」ということを即時に検知してイベント処理を実行することが可能となります。

### 補足:ObservableCollections

UniRxの`ReactiveCollection`に近い機能を提供するライブラリとして、`ObservableCollections`があります。

* [ObservableCollections](https://github.com/Cysharp/ObservableCollections)

こちらはUniRxに依存すること無く、さまざまなコレクションの監視ができるようになるライブラリとなっています。
（UniRxには存在しない、`ObservableHashSet`や`ObservableRingBuffer`などもあります）

「UniRxを導入したくない」「UniRxが提供する`ReactiveCollection`よりも高度なことがしたい」といった場合はこちらの導入も検討するとよいでしょう。

## I. (以下、思いついたら加筆します)

# まとめ

 * 「UniRxのみ」を用いていろいろ実装する時代ではなくなった
     * 現代はUniRx以上に便利なライブラリや言語機能が揃っている
     * とくに`async/await`+`UniTask`はすごく強力で、UniRxの代わりに利用できるパターンが多い
 * しかし一方でUniRxの用途が完全になくなったわけではない
    * 単なる`Observer`パターンの実装ライブラリとして使っても便利
    * `ReactiveProperty`はとくに利便性が高く、現役で全然使える
 * **UniRxを過信しすぎない**
    * 使用箇所の見極めは大事
    * `async/await`+`UniTask`の方がキレイに書けるパターンもある
    * (`async/await`がわからないならコルーチンを使ってもいい)

# 関連資料

* [UniRx入門シリーズ](https://qiita.com/toRisouP/items/00b8a5bb8e7b68e0686c)
* [UniRx&UniTask とは何なのか](https://qiita.com/toRisouP/items/3ced60a755ab297eb463)
* [UniTask Ver2 、UniTaskAsyncEnumerableまとめ
](https://qiita.com/toRisouP/items/8f66fd952eaffeaf3107)
* [Unity2020.2 で 非同期ストリーム（IAsyncEnumerable）が使いたい！](https://qiita.com/toRisouP/items/ce5809ce6f3efc172b98)
* [UniRx/UniTask完全理解](https://asciidwango.jp/post/631460628113014785/unirxunitask%E5%AE%8C%E5%85%A8%E7%90%86%E8%A7%A3)