Unity: More Redux-Like Patterns

Exploring a robust and easy-to-troubleshoot approach to managing application state across an Unity application.

Image for post
Image for post

The example application used to illustrate the patterns here is available for download (in the middleware branch).

This article provides additional patterns extending from an earlier article exploring Redux-like patterns in Unity.

Previously, we had simple action creators like:

Assets/src/ducks/A.cs

...
public Action IncrementA()
{
return new Action(Provider.Actions.INCREMENT_A);
}
...

At the same time, there are more complicated patterns that we want to consider. For example in the following application:

Image for post
Image for post
  • Colliding with the purple capsule updates a SpeedCheck value using the Player’s velocity; uses actions with payloads
  • Colliding with the stacked green cylinder and capsule increments both the A and B values (scores); uses actions that dispatch other actions
  • Colliding with the stacked black cube, cylinder, and capsule sets an ABDelay boolean to true, increments both the A and B values after 5 seconds, and then sets the ABDelay boolean to false; uses actions that wait for an asynchronous task before dispatching actions

Singleton

All of the ducks in this application are written as singletons as opposed to static classes (as in earlier article); thus the following syntax when referring to actions; A.Instance.IncrementA(). The implementation is built upon the Singleton pattern that extends MonoBehavior.

This implementation is required for the coroutine example below; used on all ducks for consistency.

Payloads

Updating the SpeedCheck simply uses a integer parameter (the action’s payload); pretty straightforward.

Assets/src/ducks/SpeedCheck.cs

...
public Action SetSpeedCheck(int speed)
{
return new Action(Provider.Actions.SET_SPEED_CHECK, speed);
}
...

Thunks

Updating both the A and B values requires thunk action creators (much like redux-thunk), i.e, the action creator returns an action comprising of a function; specifically a function that is supplied a dispatch function as a parameter. When this action is dispatched, the function is executed (in-turn dispatching two actions).

Assets/src/ducks/A.cs

...
public Action IncrementAB()
{
return new Action(dispatch =>
{
dispatch(A.Instance.IncrementA());
dispatch(B.Instance.IncrementB());
});
}
...

Another variant thunk-like action creator is one that returns a function that is supplied both a dispatch and getState parameter. The getState function is used to retrieve the state and conditionally dispatch actions based on it.

Assets/src/ducks/ABDelay.cs

...
public Action DelayZeroAB()
{
return new Action((dispatch, getState) =>
{
...
});
}
...

Coroutine

The last example builds upon the thunk action creator, except it uses Unity coroutines to perform asynchronous tasks.

Assets/src/ducks/ABDelay.cs

...
IEnumerator DelayZeroABEnumerator(Action<Action> dispatch, Func<State> getState)
{
State state = getState();
bool abDelay = GetABDelay(state);
if (abDelay)
{
yield break;
}
dispatch(DelayZeroABStart());
yield return new WaitForSeconds(5f);
dispatch(A.Instance.ZeroA());
dispatch(B.Instance.ZeroB());
dispatch(DelayZeroABEnd());
}
public Action DelayZeroAB()
{
return new Action((dispatch, getState) =>
{
IEnumerator enumerator = DelayZeroABEnumerator(dispatch, getState);
StartCoroutine(enumerator);
});
}
...

The DelayZeroABEnumerator function (after verifing the ABDelay boolean is not set), dispatches a starting event, yields control for 5 seconds, and then dispatches the final events.

While not demonstrated in this example, this same approach can be used with other asynchronous tasks such as getting data from a server with UnityWebRequest.

Conclusion

While seeming complicated to setup, especially if you are not familiar with Redux, these patterns provide a robust and easy-to-troubleshoot approach to managing application state across an Unity application.

Written by

Broad infrastructure, development, and soft-skill background

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store