0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自分用R3辞書

0
Last updated at Posted at 2026-06-25

自分用R3辞書

基本形

Observable<T>
    .Where(...)
    .Select(...)
    .Subscribe(...);

生成 → 加工 → 購読、という順序で読むと理解しやすい。
R3は通常のRxと似ているが、エラーでストリームが自動終了しないなど、UniRxやSystem.Reactiveとは重要な違いがある。

索引

基本

メソッド 説明
Subscribe 流れてきた値を受け取り、実際の処理を実行する。
AddTo 購読をGameObjectやComponentの破棄に連動させる。
Where 条件に一致する値だけを通す。
Select 値を別の値に変換する。
DistinctUntilChanged 前回と同じ値なら通知しない。
Take 先頭から指定個数だけ受け取って終了する。
Skip 先頭から指定個数を無視する。
TakeUntil 別のObservableが値を発行するか完了するまで、またはCancellationTokenがキャンセルされるまで値を受け取る。

組み合わせる系

メソッド 説明
Merge 複数のObservableを一つにまとめる。どれかから値が来たら、その値を流す。
CombineLatest 複数のObservableの最新値を組み合わせる。一方が更新されるたびに、それぞれの最新値から新しい結果を作る。
Zip 複数のObservableを、到着順に一つずつ組み合わせる。

ストリーム制御系

メソッド 説明
Debounce 値が連続して来たとき、一定時間入力が途切れてから最後の値を流す。
ThrottleFirst 一定期間内の最初の値だけを通す。
ThrottleLast 最初の値が到着した時点から一定期間の区間を開始し、その区間内で最後に到着した値を、期間終了時に一度流す。

タイミング系

メソッド 説明
Delay / DelayFrame 通知を遅らせる。
Observable.Timer 指定時間後に一度だけ通知する。
Observable.Interval 一定時間ごとに通知する。
Observable.EveryUpdate 毎フレーム通知する。Update() に近い。
Observable.NextFrame 次のフレームで一度通知する。

通知送る側

メソッド 説明
ReactiveProperty<T> 値を保持しつつ、変更をObservableとして通知する。
Subject<T> 任意のタイミングでイベントを発行する。

Unity拡張

メソッド 説明
OnClickAsObservable などのUnity UI変換 uGUIのイベントをObservableに変換する。
SubscribeToText / SubscribeToInteractable UIへの反映を簡潔に書くUnity用拡張。
OnCollisionEnterAsObservable などのCollision・Trigger系 MonoBehaviourのイベントをObservableとして扱う。

非同期処理系

メソッド 説明
SelectAwait 各値に対して非同期処理を実行し、その結果を次へ流す。
FirstAsync 最初の値を Task<T> として待つ。
LastAsync 最後の値を Task<T> として待つ。

実戦的な組み合わせ

Subscribe

流れてきた値を受け取り、実際の処理を実行する。ほぼすべてのストリームの終点になる。

playerHp
    .Subscribe(hp =>
    {
        Debug.Log($"HP: {hp}");
    });

Subscribe の戻り値は IDisposable。これを破棄すると購読が解除される。

IDisposable subscription = playerHp.Subscribe(Debug.Log);

// 購読解除
subscription.Dispose();

R3を使ううえで最も重要なのは、Subscribe したら必ずライフサイクルを管理することだ。放置すると、そのGameObjectが不要になっても処理が残る可能性がある。

索引へ

AddTo

購読をGameObjectやComponentの破棄に連動させる。

button.OnClickAsObservable()
    .Subscribe(_ =>
    {
        Debug.Log("Clicked");
    })
    .AddTo(this);

このMonoBehaviourが破棄されると、購読も自動的に破棄される。

Unityでは基本的に、

.Subscribe(...)
.AddTo(this);

までを一組として考えるとよい。R3の AddTo(Component/GameObject) は、非アクティブのまま破棄されたGameObjectも考慮した専用の破棄監視を行う。購読数が非常に多い場合は、Disposable.CreateBuilder()destroyCancellationToken を使う方式のほうが効率的だ。

var builder = Disposable.CreateBuilder();

stream1.Subscribe(...).AddTo(ref builder);
stream2.Subscribe(...).AddTo(ref builder);
stream3.Subscribe(...).AddTo(ref builder);

builder.RegisterTo(destroyCancellationToken);

普通のゲームコードなら、まずは .AddTo(this) で十分。

索引へ

Where

条件に一致する値だけを通す。LINQの Where と同じ発想だ。

Observable.EveryUpdate()
    .Where(_ => Input.GetKeyDown(KeyCode.Space))
    .Subscribe(_ =>
    {
        Jump();
    })
    .AddTo(this);

値そのものに条件を付ける例。

hp
    .Where(value => value <= 0)
    .Subscribe(_ => Die())
    .AddTo(this);

ただし、HPが0以下の間に何度も値が更新されれば、Die() も複数回呼ばれる。状態が切り替わった瞬間だけ欲しいなら、後述の DistinctUntilChanged と組み合わせるべきだ。

索引へ

Select

値を別の値に変換する。

hp
    .Select(value => value <= 0)
    .Subscribe(isDead =>
    {
        Debug.Log($"死亡状態: {isDead}");
    })
    .AddTo(this);

int のHPを bool の死亡状態に変換している。

UI表示用の文字列を作ることも多い。

hp
    .Select(value => $"HP: {value}")
    .Subscribe(text => hpText.text = text)
    .AddTo(this);

Where は値を選別し、Select は値を変換する。この違いは覚えておくこと。

索引へ

DistinctUntilChanged

前回と同じ値なら通知しない。

hp
    .Select(value => value <= 0)
    .DistinctUntilChanged()
    .Subscribe(isDead =>
    {
        Debug.Log($"死亡状態が変化: {isDead}");
    })
    .AddTo(this);

たとえばHPが 100 → 90 → 50 と変化しても、死亡状態はずっと false なので通知されない。50 → 0 で初めて true が通知される。

状態監視ではかなり頻繁に使う。

なお、ReactiveProperty<T> 自体も、通常は現在値と同じ値を代入した場合には通知しない。強制通知したい場合は .Value = value ではなく OnNext(value) を使う。

索引へ

Take

先頭から指定個数だけ受け取って終了する。

button.OnClickAsObservable()
    .Take(1)
    .Subscribe(_ =>
    {
        StartGame();
    })
    .AddTo(this);

何度クリックされても、最初の1回だけ実行される。

stream.Take(5);

なら5回受け取った時点で完了する。

「1回だけ待つ」という用途なら、Take(1) は非常によく使う。

索引へ

Skip

先頭から指定個数を無視する。

hp
    .Skip(1)
    .Subscribe(value =>
    {
        Debug.Log($"初期値以外のHP変更: {value}");
    })
    .AddTo(this);

ReactiveProperty<T> は購読時に現在値を通知するため、「初期値は要らず、変更だけ欲しい」という場合に Skip(1) が使われる。

ただし、安易な Skip(1) は設計意図を隠しやすい。初期値も意味のある状態なら、無視せず正しく処理したほうがいい。

索引へ

TakeUntil

別のObservableが値を発行するか完了するまで、またはCancellationTokenがキャンセルされるまで値を受け取る。

Observable.EveryUpdate()
    .TakeUntil(destroyCancellationToken)
    .Subscribe(_ =>
    {
        // Update相当
    });

CancellationTokenがキャンセルされると完了し、購読が解除される。
別のObservableを終了条件にすることもできる。

dragObservable
    .TakeUntil(pointerUpObservable)
    .Subscribe(position =>
    {
        UpdateDrag(position);
    })
    .AddTo(this);

「ボタンを離すまで」「シーンを終了するまで」「通信をキャンセルするまで」という処理に向いている。R3ではCancellationTokenを受け取る生成系・演算子が用意されており、キャンセル時には完了通知を通して購読を終了できる。

索引へ

Merge

複数のObservableを一つにまとめる。どれかから値が来たら、その値を流す。

var keyboardAttack = Observable.EveryUpdate()
    .Where(_ => Input.GetKeyDown(KeyCode.Space))
    .Select(_ => Unit.Default);

var buttonAttack = attackButton.OnClickAsObservable();

keyboardAttack
    .Merge(buttonAttack)
    .Subscribe(_ =>
    {
        Attack();
    })
    .AddTo(this);

キーボードでもUIボタンでも、同じ Attack() を実行できる。

複数の入力経路を一つのゲーム操作に統合するときに便利だ。

索引へ

CombineLatest

複数のObservableの最新値を組み合わせる。一方が更新されるたびに、それぞれの最新値から新しい結果を作る。

ReactiveProperty<int> currentHp = new(100);
ReactiveProperty<int> maxHp = new(100);

currentHp
    .CombineLatest(maxHp, (current, max) => (float)current / max)
    .Subscribe(rate =>
    {
        hpSlider.value = rate;
    })
    .AddTo(this);

currentHp または maxHp が変わるたび、最新の両方を使って割合を再計算する。

フォームの入力可否にも使える。

nameInput
    .CombineLatest(passwordInput,
        (name, password) =>
            !string.IsNullOrEmpty(name) &&
            password.Length >= 8)
    .Subscribe(canLogin =>
    {
        loginButton.interactable = canLogin;
    })
    .AddTo(this);

「複数の状態から派生状態を作る」場合の中心的な演算子だ。

索引へ

Zip

複数のObservableを、到着順に一つずつ組み合わせる。

streamA
    .Zip(streamB, (a, b) => (a, b))
    .Subscribe(pair =>
    {
        Debug.Log($"{pair.a}, {pair.b}");
    });

CombineLatest とは挙動が違う。

CombineLatest は常に最新値を再利用する。一方、Zip は未使用の値同士を一対一で組み合わせる。ゲームの状態監視では CombineLatest のほうが登場頻度が高く、イベントの対応付けでは Zip が向いている。

索引へ

Debounce

値が連続して来たとき、一定時間入力が途切れてから最後の値を流す。

searchInput.OnValueChangedAsObservable()
    .Debounce(TimeSpan.FromMilliseconds(300))
    .Subscribe(keyword =>
    {
        Search(keyword);
    })
    .AddTo(this);

文字を入力するたびに検索するのではなく、入力が300ミリ秒止まったところで検索する。

検索欄、設定値の保存、連続的に変化するスライダーなどで有効だ。

UniRxや一部のRxライブラリで Throttle と呼ばれていた「最後の入力を待つ処理」は、R3では主に Debounce と考えたほうが混乱しない。R3の公式APIでも時間ベース・フレームベースの Debounce が提供されている。

索引へ

ThrottleFirst

一定期間内の最初の値だけを通す。

attackButton.OnClickAsObservable()
    .ThrottleFirst(TimeSpan.FromSeconds(1))
    .Subscribe(_ =>
    {
        Attack();
    })
    .AddTo(this);

ボタンを連打されても、1秒間に最初の1回しか通さない。

誤連打防止やクールダウンの簡易実装に使える。ただし、本格的な戦闘ロジックなら、時間制限をUIストリームだけに埋め込まず、ゲーム側の状態として管理したほうが堅牢だ。

索引へ

ThrottleLast

最初の値が到着した時点から一定期間の区間を開始し、その区間内で最後に到着した値を、期間終了時に一度流す。

positionStream
    .ThrottleLast(TimeSpan.FromMilliseconds(100))
    .Subscribe(position =>
    {
        SendPosition(position);
    })
    .AddTo(this);

大量に発生する位置更新を間引き、各区間の最後の値を送る用途などに向いている。

フレーム単位版もある。

Observable.EveryUpdate()
    .ThrottleLastFrame(10)
    .Subscribe(_ =>
    {
        Debug.Log("10フレーム単位で実行");
    })
    .AddTo(this);

索引へ

Delay / DelayFrame

通知を遅らせる。

damageEvent
    .Delay(TimeSpan.FromSeconds(1))
    .Subscribe(_ =>
    {
        ShowDamageResult();
    })
    .AddTo(this);

フレーム指定も可能。

damageEvent
    .DelayFrame(3)
    .Subscribe(_ =>
    {
        Debug.Log("3フレーム後");
    })
    .AddTo(this);

注意すべきなのは、Delay は「後続の通知を遅らせる」演算子であって、現在のメソッドを同期的に停止するものではないことだ。

索引へ

Observable.Timer

指定時間後に一度だけ通知する。

Observable.Timer(TimeSpan.FromSeconds(2))
    .Subscribe(_ =>
    {
        Debug.Log("2秒経過");
    })
    .AddTo(this);

一定間隔で繰り返すオーバーロードもある。

Observable.Timer(
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(3))
    .Subscribe(_ =>
    {
        Debug.Log("最初は1秒後、その後は3秒ごと");
    })
    .AddTo(this);

索引へ

Observable.Interval

一定時間ごとに通知する。

Observable.Interval(TimeSpan.FromSeconds(1))
    .Subscribe(count =>
    {
        Debug.Log($"{count} 秒相当");
    })
    .AddTo(this);

Unityではデフォルトの時間処理がPlayerLoopのUpdateに連動するよう設定される。タイムスケールを無視する場合は、Unity用の TimeProvider を明示できる。

Observable.Interval(
        TimeSpan.FromSeconds(1),
        UnityTimeProvider.UpdateIgnoreTimeScale)
    .Subscribe(_ =>
    {
        Debug.Log("Time.timeScaleに影響されない");
    })
    .AddTo(this);

索引へ

Observable.EveryUpdate

毎フレーム通知する。Update() に近い。

Observable.EveryUpdate()
    .Subscribe(_ =>
    {
        transform.Rotate(0f, 90f * Time.deltaTime, 0f);
    })
    .AddTo(this);

ただし、何でも EveryUpdate() に置き換えるのは賢くない。普通の Update() のほうが単純な処理までObservableにすると、コードの追跡が難しくなる。

EveryUpdate() が有効なのは、フレーム更新に WhereTakeUntilThrottleLastFrame などを組み合わせたい場合だ。

実行タイミングも指定できる。

Observable.EveryUpdate(UnityFrameProvider.FixedUpdate)
    .Subscribe(_ =>
    {
        // FixedUpdate相当
    })
    .AddTo(this);

Unity用R3には、Update、FixedUpdate、PostLateUpdateなどに対応する UnityFrameProvider が用意されている。

索引へ

Observable.NextFrame

次のフレームで一度通知する。

Observable.NextFrame()
    .Subscribe(_ =>
    {
        Debug.Log("次のフレーム");
    })
    .AddTo(this);

UIレイアウトの更新を1フレーム待つ、生成直後の初期化を次フレームにずらす、といった用途で使う。

索引へ

ReactiveProperty<T>

値を保持しつつ、変更をObservableとして通知する。

private readonly ReactiveProperty<int> hp = new(100);

private void Start()
{
    hp.Subscribe(value =>
    {
        Debug.Log($"HP: {value}");
    }).AddTo(this);

    hp.Value -= 10;
}

Subject<T> が単なるイベント発行器なのに対し、ReactiveProperty<T> は現在値を保持する。

外部には読み取り専用で公開する設計が扱いやすい。

private readonly ReactiveProperty<int> hp = new(100);

public ReadOnlyReactiveProperty<int> Hp => hp;

変更できる場所をクラス内部に限定できるため、無秩序な代入を防げる。

派生プロパティも作れる。

public ReadOnlyReactiveProperty<bool> IsDead { get; }

public Player()
{
    IsDead = hp
        .Select(value => value <= 0)
        .ToReadOnlyReactiveProperty();
}

private void Dispose()
{
    IsDead.Dispose();
    hp.Dispose();
}

R3の ReactiveProperty は値を保持し、重複値を通常は除外する。読み取り専用型へ公開する設計も公式READMEで推奨されている。

索引へ

Subject<T>

任意のタイミングでイベントを発行する。

private readonly Subject<int> damaged = new();

public Observable<int> OnDamaged => damaged;

public void Damage(int amount)
{
    damaged.OnNext(amount);
}

購読側。

player.OnDamaged
    .Subscribe(amount =>
    {
        ShowDamageEffect(amount);
    })
    .AddTo(this);

Subject<T> はイベントとして使い、ReactiveProperty<T> は状態として使う、と区別すると設計しやすい。

また、外部には Subject<T> 自体を公開しないほうがよい。公開すると外部から OnNext() を呼べてしまう。

// 良い
public Observable<int> OnDamaged => damaged;

// 避けたい
public Subject<int> OnDamaged => damaged;

索引へ

OnClickAsObservable などのUnity UI変換

uGUIのイベントをObservableに変換する。

button.OnClickAsObservable()
    .Subscribe(_ => StartGame())
    .AddTo(this);

よく使うものには以下がある。

button.OnClickAsObservable();

toggle.OnValueChangedAsObservable();

slider.OnValueChangedAsObservable();

inputField.OnValueChangedAsObservable();

inputField.OnEndEditAsObservable();

dropdown.OnValueChangedAsObservable();

値変更系は購読開始時に現在値を流すものがあるため、「変更時だけ」と思い込むと初期化処理が二重になることがある。公式READMEでは、uGUIの OnValueChangedAsObservable は購読開始時に最新値を発行すると説明されている。

索引へ

SubscribeToText / SubscribeToInteractable

UIへの反映を簡潔に書くUnity用拡張。

hp
    .Select(value => $"HP: {value}")
    .SubscribeToText(hpText)
    .AddTo(this);

ボタンの操作可否。

canSubmit
    .SubscribeToInteractable(submitButton)
    .AddTo(this);

単純なUI反映では便利だが、複雑な表示処理まで無理に SubscribeTo... に押し込む必要はない。通常の Subscribe のほうが意図が明確な場合もある。

索引へ

OnCollisionEnterAsObservable などのCollision・Trigger系

MonoBehaviourのイベントをObservableとして扱う。

using R3;
using R3.Triggers;

this.OnCollisionEnterAsObservable()
    .Subscribe(collision =>
    {
        Debug.Log($"衝突: {collision.gameObject.name}");
    })
    .AddTo(this);

同様に、Update系、Trigger系、Pointer系などがある。R3.Triggersの拡張メソッドは、必要なObservableTriggerコンポーネントを自動的に注入する。

索引へ

SelectAwait

各値に対して非同期処理を実行し、その結果を次へ流す。

button.OnClickAsObservable()
    .SelectAwait(
        async (_, cancellationToken) =>
        {
            await LoadDataAsync(cancellationToken);
            return "完了";
        },
        AwaitOperation.Drop)
    .Subscribe(result =>
    {
        Debug.Log(result);
    })
    .AddTo(this);

AwaitOperation.Drop は、処理中に新しい値が来たら捨てる。ボタンの多重実行防止に便利だ。

主なモードは、順番に処理する Sequential、処理中の入力を捨てる Drop、前の処理をキャンセルして最新を実行する Switch、並列実行する Parallel

たとえば検索処理なら Switch が向いている。

searchInput.OnValueChangedAsObservable()
    .Debounce(TimeSpan.FromMilliseconds(300))
    .SelectAwait(
        async (keyword, ct) =>
        {
            return await SearchAsync(keyword, ct);
        },
        AwaitOperation.Switch)
    .Subscribe(results =>
    {
        UpdateResultView(results);
    })
    .AddTo(this);

新しい検索語が来たら古い検索をキャンセルできる。R3では SelectAwaitWhereAwaitSubscribeAwait に非同期処理の実行方式が明示的に用意されている。

索引へ

FirstAsync

最初の値を Task<T> として待つ。

async Task WaitForClickAsync(CancellationToken ct)
{
    await button.OnClickAsObservable()
        .FirstAsync(ct);

    Debug.Log("クリックされた");
}

Rxチェーンをずっと購読するのではなく、「イベントを一回だけ非同期に待ちたい」場合に向いている。

int value = await hp
    .Where(x => x <= 0)
    .FirstAsync(cancellationToken);

R3では、単一結果を返す処理は FirstAsyncLastAsync のようなTaskベースのAPIになっている。

索引へ

LastAsync

ストリームが完了した時点で、最後に流れた値を Task<T> として返す。

完了しないObservableでは結果が確定しないため、
TakeTakeUntil、CancellationTokenなどで完了条件を用意する必要がある。

索引へ

実戦的な組み合わせ

典型的なUIコードはこうなる。

using R3;
using UnityEngine;
using UnityEngine.UI;

public sealed class PlayerView : MonoBehaviour
{
    [SerializeField] private Button healButton;
    [SerializeField] private Text hpText;

    private readonly ReactiveProperty<int> hp = new(100);

    private void Start()
    {
        hp
            .Select(value => $"HP: {value}")
            .SubscribeToText(hpText)
            .AddTo(this);

        healButton.OnClickAsObservable()
            .Where(_ => hp.Value < 100)
            .ThrottleFirst(TimeSpan.FromMilliseconds(300))
            .Subscribe(_ =>
            {
                hp.Value = Mathf.Min(hp.Value + 10, 100);
            })
            .AddTo(this);
    }
}

このコードで使っている役割は明確だ。

ReactiveProperty が状態を保持し、Select が表示文字列へ変換し、SubscribeToText がUIへ反映する。ボタンクリックは OnClickAsObservable でイベント化し、Where で回復可能な場合だけ通し、ThrottleFirst で連打を抑え、Subscribe で状態を変更する。

最初に覚える優先順位を絞るなら、SubscribeAddToWhereSelectReactivePropertyDistinctUntilChangedTakeCombineLatestOnClickAsObservableSelectAwait。このあたりで、UnityのUIとゲーム状態の大部分は組める。

索引へ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?