Awesome Unity : UniRx / FRP — Changing how we deal with changes

Daniel Tan
GlassBlade
Published in
3 min readMay 27, 2020

Introduction

Software engineers are mainly concerned with system architecture, and more specifically, how does the application deal with information. Most of the programs we write are directly connected to the real world, and the real world is messy and random. Systems we create should handle this messiness and randomness well enough for us to do something useful. There’s a good talk on youtube by Rich Hickey that you can check out for more information as well. The transcript is also available here.

We usually interact with the real world through “Events” (C# Events Documentation here). Mouse clicks, keyboard typing, screen tapping, etc, all fire events that can be consumed through our application. Traditionally this has been a troublesome affair since the real world is random and messy. We might need to listen to multiple mouse clicks, for example, or sequenced keyboard presses. In fact, any kind of state change can fire an Event. One might be tempted to use flags to handle them all, but I’ve shown in a previous post why flags are bad for readability and reasoning.

Practical interactive Functional Reactive Programming (FRP) has been created as a paradigm to inject some sanity into handling this messiness and randomness. We basically treat Event collections as data structures like arrays and operate on them, which makes it far easier to think about them.

UniRx is an implementation of FRP for Unity C# that I will use in this post, but most of the techniques can be applied to any implementation. The reason why Unity requires its own separate implementation is due to how Unity handles in-game time.

Observables

This is basic unit of UniRx that provides a similar API like Events. Previously you would listen to an Event, now you would Subscribe to an Observable. For example, you may have a Button that listens to mouse clicks. This Button can have an Observable that observes mouse clicks, which is then Subscribed by some class that’s interested by that mouse click, like a friend’s list panel which toggles between open and close on mouse click.

// in your friend's list panel 
public Button myButton;
public GameObject root;
public void Start(){
myButton.ClickAsObservable()
.Subscribe(event=>root.SetActive(root.activeSelf));
}

Simple enough. Now what if I want another button to also trigger the same action? Since Events can now be collected into lists, if I have two buttons that will fire Events, then I have two lists that contain similar data. For them to do the same thing, all I have to do is merge them so if any of the two lists contains the Events I want it will trigger the action.

public Button myButtonA;
public Button myButtonB;
public void Start(){
Observable.Merge(
myButtonA.ClickAsObservable(),
myButtonB.ClickAsObservable()
)
.Subscribe(event=>root.SetActive(root.activeSelf));
}

Now what if I want both of them to fire before I trigger the action? I simply wait for both lists to be populated before firing the action.

public Button myButtonA;
public Button myButtonB;
public void Start(){
Observable.ZipLatest(
myButtonA.ClickAsObservable(),
myButtonB.ClickAsObservable()
)
.Subscribe(event=>root.SetActive(root.activeSelf));
}

As you can see, it’s scalable and extensible beyond just two Observables without requiring flags and what-not anti-patterns.

One caveat about this though, is that just like Listeners to Events, where we have to remove Listeners, we have to do the same with Subscriptions. Each Subscription implements the IDisposable interface, which allows us to Dispose them.

Taking the first example,

public Button myButton;
public GameObject root;
private IDisposable disposable;public void Start(){
disposable = myButton.ClickAsObservable()
.Subscribe(event=>root.SetActive(root.activeSelf));
}
public void OnDestroy(){
disposable.Dispose();
}

However, UniRx also allows us to bind the Subscription to the lifecycle of the GameObject, like so.

public Button myButton;
public GameObject root;
public void Start(){
myButton.ClickAsObservable()
.Subscribe(event=>root.SetActive(root.activeSelf))
.AddTo(this);
}

There are alot more operators that you can find with these two documentation:

I try to update weekly. This is part three of the Awesome Unity Series.

Part one here.

Part two here.

--

--