← logs に戻る
2026/07/01 · 4 min read ⚙️ intermediate

UniRx ではじめるリアクティブな Unity

Update ポーリングやイベントの氾濫を、時間軸のストリームとして宣言的に書く UniRx 入門。Observable の基本・主要オペレータ・購読の後始末まで。

「入力を監視して反応する」処理を、Unity では Update のポーリングやコールバックの寄せ集めで書きがちです。UniRx は、それらを 「時間軸に流れるストリーム」 として宣言的に扱えるようにするライブラリです。この記事では、なぜ UniRx なのか・Observable の基本・主要オペレータ・購読の後始末までを押さえます。

⚠️ UniRx は実質メンテ終了で、Cysharp による後継 R3 が新規プロジェクトの推奨。ただし考え方(Rx)は共通なので、まず UniRx で概念を掴むのは有効。本記事の内容は R3 にもほぼそのまま通じる。

なぜ UniRx なのか

Update に処理を書き足していくと、状態フラグと if が増えて追いにくくなります。UniRx は「いつ・何が起きたか」を**イベントの流れ(ストリーム)**として捉え、それに変換・フィルタを重ねて宣言的に書きます。

// 素朴な Update ポーリング
void Update()
{
    if (Input.GetMouseButtonDown(0)) Fire();
}

// UniRx: クリックというストリームを購読する
Observable.EveryUpdate()
    .Where(_ => Input.GetMouseButtonDown(0))
    .Subscribe(_ => Fire())
    .AddTo(this);

🧭 UniRx は .NET の Rx(System.Reactive)を Unity 向けに移植したもの。IObservable<T> / IObserver<T> は元々 BCL の型で、LINQ 風のオペレータもほぼ同じ。C# で Rx を触ったことがあれば知識がそのまま効く。

Observable と Subscribe

中心にあるのは IObservable<T>(値が流れてくる源)と Subscribe(流れを受け取る)です。SubscribeIDisposable を返し、これを捨てると購読が止まります。

IObservable<long> stream = Observable.Interval(TimeSpan.FromSeconds(1)); // 1秒ごとに発行

IDisposable subscription = stream.Subscribe(
    x => Debug.Log($"OnNext: {x}"),   // 値が来るたび
    ex => Debug.LogError(ex),         // エラー時
    () => Debug.Log("OnCompleted"));  // 完了時

イベントやコールバックも Observable に変換できます。uGUI のボタンなどは専用の拡張が用意されています。

button.OnClickAsObservable()
    .Subscribe(_ => Debug.Log("clicked"))
    .AddTo(this);

主要オペレータ

ストリームに変換・フィルタを重ねるのが Rx の肝です。よく使うものだけ挙げます。

オペレータ役割
Where条件に合う値だけ通す
Select値を別の形に変換する
Throttle発行が落ち着くまで待って最後の1つを流す(検索入力など)
ThrottleFirst最初の1つを流し一定時間は無視する(連打防止など)
DistinctUntilChanged前回と同じ値なら流さない
Merge / CombineLatest複数ストリームを合流・組み合わせる
// 入力が 0.5 秒落ち着いてから検索する
inputField.OnValueChangedAsObservable()
    .Throttle(TimeSpan.FromSeconds(0.5))
    .DistinctUntilChanged()
    .Subscribe(text => Search(text))
    .AddTo(this);

購読の後始末(AddTo)

購読しっぱなしはリークになります。破棄されたオブジェクトを参照し続けたり、二重購読で処理が多重に走ったりします。AddTo で MonoBehaviour のライフタイムに結びつけ、破棄時に自動で購読解除します。

Observable.EveryUpdate()
    .Subscribe(_ => Tick())
    .AddTo(this);   // この GameObject/Component 破棄で購読解除

🧭 UniTask で GetCancellationTokenOnDestroy() のトークンを渡すのと同じ発想。非同期・購読は「オブジェクトの寿命に必ず紐づける」のが Unity での鉄則。詳しくは UniTask 入門 を参照。

ReactiveProperty で状態を持つ

値の変化そのものをストリームにするのが ReactiveProperty<T> です。.Value で読み書きし、変化すると購読側に流れます。MVP/MVVM で View と状態を繋ぐのに向きます。

public class Player : MonoBehaviour
{
    public ReactiveProperty<int> Hp = new ReactiveProperty<int>(100);

    void Start()
    {
        // HP が変わるたびに UI を更新(初期値も届く)
        Hp.Subscribe(hp => hpText.text = $"HP: {hp}").AddTo(this);
    }

    public void Damage(int n) => Hp.Value -= n;   // 代入するだけで購読側に伝わる
}

まとめ

  • UniRx はイベントや入力を 時間軸のストリームとして宣言的に扱うライブラリ。Update ポーリングとフラグ地獄を減らせる。
  • 中心は IObservable<T>SubscribeSubscribe が返す IDisposable が購読の寿命。
  • Where / Select / Throttle などのオペレータを重ねて、変換・フィルタを宣言的に書く。
  • AddTo で購読をオブジェクトの寿命に紐づけるのが必須(リーク防止)。
  • ReactiveProperty<T> は「値の変化」自体をストリーム化し、状態と View を繋ぐのに向く。
  • 新規なら後継の R3 も検討する(概念は本記事のまま通用する)。

次にやること: CombineLatest で複数の入力条件をまとめたり、UniRx と UniTask を ToObservable() / ToUniTask() で相互変換して、非同期とストリームを行き来する書き方に進むと応用が利く。

← logs 一覧 tags →