Unity R3 - Modern Reactive Extensions for Unity
Overview
R3 is a modern, high-performance Reactive Extensions library for Unity developed by Cysharp (same author as UniTask), providing observable streams and reactive patterns optimized for Unity.
Library: R3 by Cysharp
R3 vs UniRx: R3 is the modern successor to UniRx with better performance, async enumerable support, and Unity 2022+ optimization. For legacy UniRx projects, see unity-unirx skill.
Foundation Required: unity-csharp-fundamentals (TryGetComponent, FindAnyObjectByType), csharp-async-patterns (async fundamentals), unity-async (Unity context)
Core Topics:
- •Observable sequences and observers
- •Reactive operators and transformations
- •Hot vs Cold observables
- •ReactiveProperty for state management
- •Event-driven architecture patterns
- •MVVM/MVP implementation
- •UI event handling and data binding
Learning Path: C# events → Reactive patterns → Observable composition → MVVM architecture
Quick Start
Basic Observable Patterns
using R3;
// Create observable from events
button.OnClickAsObservable()
.Subscribe(_ => Debug.Log("Button clicked!"))
.AddTo(this);
// Property observation
this.ObserveEveryValueChanged(x => x.transform.position)
.Subscribe(pos => Debug.Log($"Position: {pos}"))
.AddTo(this);
// Time-based observables
Observable.Interval(TimeSpan.FromSeconds(1))
.Subscribe(x => Debug.Log($"Tick: {x}"))
.AddTo(this);
ReactiveProperty
// Reactive state management
public class Player : MonoBehaviour
{
public ReactiveProperty<int> Health { get; } = new(100);
public ReadOnlyReactiveProperty<bool> IsDead { get; }
public Player()
{
IsDead = Health.Select(h => h <= 0).ToReadOnlyReactiveProperty();
}
}
When to Use
Unity Reactive (This Skill)
- •Event-driven architecture and complex event handling
- •MVVM/MVP pattern implementation
- •UI data binding and reactive state
- •Asynchronous event streams
- •Complex state management
- •Real-time data flow coordination
Alternatives
- •unity-unirx: Legacy UniRx library (pre-2022 projects)
- •unity-async/unity-unitask: Single async operations, not event streams
- •C# events: Simple event handling without composition
R3-Specific Features
- •Async enumerable (
IAsyncEnumerable<T>) integration - •Better performance than UniRx
- •Unity 2022+ optimization
- •Struct-based observers for zero allocation
- •Built-in time providers for testing
Reference Documentation
Reactive Fundamentals
Core R3 concepts:
- •Observable creation patterns
- •Hot vs Cold observables
- •Subscription lifecycle
- •Marble diagrams
- •Basic operators (Select, Where, DistinctUntilChanged)
Reactive Operators
Transformation and composition:
- •Filtering operators (Where, Throttle, Debounce)
- •Transformation operators (Select, SelectMany)
- •Combination operators (CombineLatest, Merge, Zip)
- •Time operators (Delay, Timeout, Sample)
- •Error handling operators (Catch, Retry)
Architecture Patterns
Application patterns:
- •MVVM with ReactiveProperty
- •Event Aggregator pattern
- •State management systems
- •UI data binding
- •Message broker implementation
Key Principles
- •Declarative Event Handling: Define what should happen, not how to subscribe/unsubscribe
- •Automatic Disposal: Use
AddTo(this)for MonoBehaviour lifecycle management - •Composition over Callbacks: Chain operators instead of nested callbacks
- •Hot/Cold Awareness: Understand when observables start emitting
- •Marble Diagram Thinking: Visualize data flow over time
Common Patterns
UI Event Handling
// Button with throttle to prevent spam
button.OnClickAsObservable()
.Throttle(TimeSpan.FromSeconds(1))
.Subscribe(_ => OnButtonClick())
.AddTo(this);
// Input validation
inputField.OnValueChangedAsObservable()
.Where(text => text.Length > 3)
.Throttle(TimeSpan.FromSeconds(0.5))
.Subscribe(ValidateInput)
.AddTo(this);
State Management
public class GameState : MonoBehaviour
{
public ReactiveProperty<int> Score { get; } = new(0);
public ReactiveProperty<int> Lives { get; } = new(3);
public ReadOnlyReactiveProperty<bool> GameOver { get; }
public GameState()
{
GameOver = Lives.Select(l => l <= 0).ToReadOnlyReactiveProperty();
GameOver.Where(over => over)
.Subscribe(_ => OnGameOver())
.AddTo(this);
}
}
Multiple Stream Combination
// Combine position and health for decision making
Observable.CombineLatest(
playerTransform.ObserveEveryValueChanged(t => t.position),
playerHealth.Health,
(pos, health) => new { Position = pos, Health = health }
)
.Where(state => state.Health < 30)
.Subscribe(state => FindNearestHealthPack(state.Position))
.AddTo(this);
Integration with Other Skills
- •unity-unitask: Convert observables to UniTask with
ToUniTask() - •unity-vcontainer: Inject ReactiveProperty as dependencies via VContainer
- •unity-ui: Bind observables to UI elements for automatic updates
- •unity-async: Bridge async operations with
Observable.FromAsync() - •unity-unirx: For legacy projects (not recommended for new projects)
Platform Considerations
- •WebGL: Full support with frame-based timing
- •Mobile: Efficient for UI and event handling
- •All Platforms: Zero allocation after initial setup
Best Practices
- •Always use AddTo(): Prevent memory leaks with automatic disposal
- •Throttle/Debounce user input: Prevent excessive processing
- •Use ReactiveProperty for state: Better than manual event raising
- •Understand hot vs cold: Know when subscriptions trigger work
- •Avoid nested subscriptions: Use
SelectManyfor flattening - •Test with TestScheduler: Write deterministic reactive tests
- •Consider backpressure: Handle fast producers with
SampleorBuffer