Unity: Declarative Approach Borrowed from Web Development

John Tucker
4 min readMay 14, 2018

--

Through example, we explore issues around writing declarative Unity code. In the end we demonstrate a pattern inspired by Redux (a popular web application state-management library).

First, a disclaimer… The pattern that I describe here have not been used in a large-scale application; but the motivation for it draws from just that scenario.

The final solution is available for download; requires separate install of UniRx from the Unity Asset Store.

The Anti-Pattern

As I was working through the excellent tutorial on Unity, Learn Unity 3D for Absolute Beginners, I bumped into a pattern that is fragile and not scalable. It reminded me of the jQuery spaghetti code that we have come to dread in the world of web development.

The particular lesson walks one through building a simple game displaying a score. The particular pattern that I found problematic was how the score is incremented as the player picks up coins.

The implementation is in the Player object’s script component.

Assets/Script/PlayerController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class PlayerController : MonoBehaviour {
...
public Text txtScore;
int score;
...
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Coin")
{
Destroy(other.gameObject);
score++;
txtScore.text = "Score : " + score;
}
...
}

...
}

Observations

  • The logic specific to coins is in the Player GameObject’s script component. If one follows this pattern for other GameObject interactions, this script will become unwieldy complex.
  • The score is stored in a private field, score, of the Player GameObject’s script component; if another GameObject’s script component later needs access to the score, this script would have to be refactored.
  • The Player GameObject’s script component imperatively updates the UI GameObject displaying the score. If additional UI GameObjects need to be updated, this code would become increasingly complex.

The Idea

By profession, I build complex web applications and have settled on using React +Redux. Redux, in particular, enables one to completely de-couple the state from the UI (solving the problems we identified above)

At a high-level, this approach applied to this problem would go something as follows:

  1. When the player and coin collide, the coin GameObject’s script component would dispatch an action (event) to increment the score.
  2. A global store object would consume the action; updating the score (globally defined in the store).
  3. Any GameObject’s, e.g,. the score UI, script component subscribed to store updates would be alerted of the change; where they can update the GameComponent that they are associated with.

A quick search on the observer pattern (pretty much what we described) turned up an article Game Programming Patterns in Unity with C# with a fairly straight-forward section on the observer pattern.

Another search on Redux with Unity led me to the Unidux library; Redux architecture for Unity.

Unfortunately, neither solution was exactly what I was looking for; the article on the observer pattern was too simplistic and I found Unidux library confusing.

The Secret Sauce

Several months ago, I spent some time learning ReactiveX using JavaScript (also available on a variety of platforms including Unity as UniRx). At that time, I was able to re-implement the functionality of Redux using RxJS in fundamentally three lines of code; wrote a series of articles starting with RxJS by Example: Part 1.

In much the same way, I was able to implement a Redux-like solution in Unity with fundamentally the same three lines of code.

The Example

I setup a slightly more complicated example (than the anti-pattern example) to illustrate the power of the Redux-like solution. In this example, there are two scores (for capsules and cylinders). Hitting the green capsule adds one the capsule score (red reduces it be one). Similarly for the cylinders (behind the player).

Let use walk through the code involved when the player hits the green capsule.

First, the capsule’s script component dispatches an action to increment the A score (the capsule score).

Assets/src/IncrementAController.cs

using UnityEngine;public class IncrementAController : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
Store.storeDispatch.OnNext(A.IncrementA());
}
}

Without going into the details, the Store static class (and several supporting classes) update the A score (stored in the store). The core functionality is provided by the UniRx concepts: Subject, BehaviorSubject and Scan.

Assets/src/store/Store.cs

...
public static readonly ISubject<Action> storeDispatch = new Subject<Action>();
public static readonly BehaviorSubject<State> storeState = new BehaviorSubject<State>(initialState);static readonly System.IDisposable storePrivate = storeDispatch.Scan(initialState, reducer).Subscribe(storeState);
...

The capsule score’s script component, subscribed to the stores updates, determines if the capsule score has changed (comparing the updated score against a cached copy), and then (if changed) updates the cached copy and its related ScoreA GameComponent.

Assets/src/ScoreAController.cs

using System;
using UnityEngine;
using UnityEngine.UI;
using UniRx;
public class ScoreAController : MonoBehaviour
{
int score = 0;
public Text scoreUI;
class MyObserver : IObserver<State>
{
readonly ScoreAController outer;
public MyObserver(ScoreAController outer)
{
this.outer = outer;
}
public void OnCompleted()
{
}
public void OnError(Exception e)
{
}
public void OnNext(State state)
{
int nextScore = A.getA(state);
if (nextScore == outer.score)
{
return;
}
outer.score = nextScore;
outer.scoreUI.text = "Capsule: " + outer.score.ToString();
}
}
void Start()
{
Store.storeState.Subscribe(new MyObserver(this));
}
}

note: The length of this script comes from the boilerplate code in implementing the IObserver<State> interface.

Conclusion

In the end, this example provides a fairly simple (especially if you already are familiar with Redux) pattern of writing declarative code for Unity applications.

Enjoy.

--

--

John Tucker

Broad infrastructure, development, and soft-skill background