UniRx Documentation

repository·master·Indexed 27 days ago

https://github.com/neuecc/unirx

A Reactive Extensions (Rx) implementation optimized for Unity. UniRx provides tools to handle asynchronous operations, network requests, and Unity-specific events as composable observable streams. Key features include ReactiveProperty for data notification, integration with Unity Coroutines and AsyncOperations, and the UniRx.Triggers namespace for converting MonoBehaviour lifecycle events into observables. It also supports the Model-View-ReactivePresenter (MVRP) pattern and provides specialized schedulers for the Unity main thread.

Tokens
7.7K
Snippets
22
Records
32
Agent score
43%

What's inside UniRx

  1. Overview of UniRx

    master

    UniRx (Reactive Extensions for Unity) is a reimplementation of the .NET Reactive Extensions specifically designed for Unity. It provides a comprehensive reactive programming suite consisting of:

    • Core Library: A port of the standard Rx library.
    • Platform Adaptor: Unity-specific adaptors such as MainThreadScheduler and FromCoroutine.
    • Framework: High-level reactive components like ObservableTriggers and ReactiveProperty.
    • Async/Await Integration: Integration with asynchronous programming via UniRx.Async.
  2. Use UniRx.Triggers for MonoBehaviour events

    master

    Instead of using the legacy ObservableMonoBehaviour, use the UniRx.Triggers namespace to subscribe to Unity events directly via extension methods on Component or GameObject. These methods automatically inject an ObservableTrigger.

    Example of combining mouse events with updates:

    using UniRx;
    using UniRx.Triggers;
    
    public class DragAndDropOnce : MonoBehaviour
    {
        void Start()
        {
            // All events can subscribe by ***AsObservable
            this.OnMouseDownAsObservable()
                .SelectMany(_ => this.UpdateAsObservable())
                .TakeUntil(this.OnMouseUpAsObservable())
                .Select(_ => Input.mousePosition)
                .Subscribe(x => Debug.Log(x));
        }
    }
  3. Integrate UniRx with uGUI UnityEvents

    master

    You can treat Unity UI events as Observables using UnityEvent.AsObservable. This enables declarative UI programming.

    Common helper extension methods include:

    • OnValueChangedAsObservable(): Provides the value on subscribe (e.g., for Toggle, InputField, Slider).
    • SubscribeToInteractable(Button): Sets the interactable property of a button based on the observable stream.
    • SubscribeToText(Text): A helper for updating text components.
    public Button MyButton;
    // Subscribe to a button click
    MyButton.onClick.AsObservable().Subscribe(_ => Debug.Log("clicked"));
    
    // Example of declarative UI logic
    public Toggle MyToggle;
    public Button MyButton;
    void Start()
    {
        // Toggle the button's interactable state based on the toggle value
        MyToggle.OnValueChangedAsObservable().SubscribeToInteractable(MyButton);
    }
  4. Manage subscription lifecycles with AddTo

    master

    Manual management of subscriptions is required for static generators like Observable.Timer or Observable.EveryUpdate. Use IDisposable.AddTo to automate disposal:

    1. With a collection: Use CompositeDisposable to manage multiple subscriptions. Use .Clear() to dispose all and clear the list, or .Dispose() to dispose all and prevent further additions.
    2. With a GameObject/Component: Use .AddTo(this) to automatically dispose the subscription when the GameObject or Component is destroyed.
    3. With pipeline operators: Use TakeUntil, TakeUntilDestroy, or TakeUntilDisable for specific lifecycle-based completion logic.
    // Using CompositeDisposable
    CompositeDisposable disposables = new CompositeDisposable();
    Observable.EveryUpdate().Subscribe(x => Debug.Log(x)).AddTo(disposables);
    
    // Using GameObject/Component
    Observable.IntervalFrame(30).Subscribe(x => Debug.Log(x)).AddTo(this);
    
    // Using TakeUntil operators
    Observable.IntervalFrame(30).TakeUntilDisable(this)
        .Subscribe(x => Debug.Log(x), () => Debug.Log("completed!"));
  5. Use RepeatSafe and RepeatUntilDestroy for safe loops

    master

    The standard .Repeat() method can be dangerous as it may cause infinite loops if the subscription is not detached when a GameObject is destroyed. Use safer alternatives:

    • RepeatSafe: Stops repeating if contiguous OnComplete calls are made.
    • RepeatUntilDestroy(gameObject/component): Stops repeating when the target is destroyed.
    • RepeatUntilDisable(gameObject/component): Stops repeating when the target is disabled.
    this.gameObject.OnMouseDownAsObservable()
        .SelectMany(_ => this.gameObject.UpdateAsObservable())
        .TakeUntil(this.gameObject.OnMouseUpAsObservable())
        .Select(_ => Input.mousePosition)
        .RepeatUntilDestroy(this) // safety way
        .Subscribe(x => Debug.Log(x));
  6. Implement the Model-View-(Reactive)Presenter (MVRP) pattern

    master

    UniRx facilitates the MVRP pattern to decouple game logic from UI.

    • Model: Holds state using ReactiveProperty.
    • View: The Unity hierarchy (Scene/Canvas) containing UI components.
    • Presenter: A MonoBehaviour that subscribes to Model changes and updates the View, and subscribes to View events to update the Model.

    This pattern avoids the complexity of full MVVM binding while providing a reactive flow: View -> ReactiveProperty -> Model -> ReactiveProperty -> View.

    // Presenter for scene(canvas) root.
    public class ReactivePresenter : MonoBehaviour
    {
        public Button MyButton;
        public Toggle MyToggle;
        public Text MyText;
    
        Enemy enemy = new Enemy(1000);
    
        void Start()
        {
            // 1. View -> Model: User events from Views update the Model
            MyButton.OnClickAsObservable().Subscribe(_ => enemy.CurrentHp.Value -= 99);
            MyToggle.OnValueChangedAsObservable().SubscribeToInteractable(MyButton);
    
            // 2. Model -> View: Models notify Presenters via Rx, and Presenters update their views
            enemy.CurrentHp.SubscribeToText(MyText);
            enemy.IsDead.Where(isDead => isDead == true)
                .Subscribe(_ =>
                {
                    MyToggle.interactable = MyButton.interactable = false;
                });
        }
    }
  7. Integrate Coroutines with UniRx

    master

    You can convert Unity Coroutines (IEnumerator) into Observables using Observable.FromCoroutine(AsyncA) or the shorthand AsyncA().ToObservable(). This allows you to orchestrate asynchronous flows using Rx operators like SelectMany.

    Additionally, you can convert an Observable back into a YieldInstruction for use within a Coroutine using .ToYieldInstruction().

  8. Install UniRxAnalyzer for Visual Studio

    master
    The UniRxAnalyzer is a custom analyzer for Visual Studio 2015 that detects common mistakes, such as creating observable streams that are never subscribed to (e.g., ObservableWWW).
  9. Use MicroCoroutines for efficient background tasks

    master

    For high-performance, memory-efficient execution of tasks that only require yield return null, use MainThreadDispatcher.StartUpdateMicroCoroutine. This is faster and more memory-efficient than standard Unity StartCoroutine.

    Limitations:

    • Only supports yield return null.
    • Update timing is determined by the method used: StartUpdateMicroCoroutine, StartFixedUpdateMicroCoroutine, or StartEndOfFrameMicroCoroutine.
    int counter;
    
    IEnumerator Worker()
    {
        while(true)
        {
            counter++;
            yield return null;
        }
    }
    
    void Start()
    {
        for(var i = 0; i < 10000; i++)
        {
            // fast, memory efficient
            MainThreadDispatcher.StartUpdateMicroCoroutine(Worker());
    
            // slow...
            // StartCoroutine(Worker());
        }
    }