Updater: An Easy Way to Get Updates Without Inheriting MonoBehaviour
At first glance an Updater
class seems unnecessary in Unity. All you have to do is inherit from MonoBehaviour
and add an Update
function. But what if you don’t want to inherit from MonoBehaviour
? Enter Updater
, an easy way to still get an update event and cut your dependency on MonoBehaviour
. This works great for code in DLLs, “pure code” projects, or just projects that don’t want to put everything into MonoBehaviour
s. Read on for the source code and how to use it!
Let’s start with the interface: IUpdater
. This defines a type that has no dependency on MonoBehaviour
or any other Unity class. It’s therefore ideal for DLLs or code you want to share with other C# environments like a Photon server or command-line tool.
using System; /// <summary> /// Periodically dispatches an event /// </summary> /// <author>Jackson Dunstan, http://JacksonDunstan.com/articles/3382 /// <license>MIT</license> public interface IUpdater { /// <summary> /// Dispatched periodically /// </summary> event Action OnUpdate; /// <summary> /// Start dispatching OnUpdate events /// </summary> void Start(); /// <summary> /// Stop dispatching OnUpdate events /// </summary> void Stop(); }
Using IUpdater
is easy- just subscribe to the event!
class DamageOverTime { IPlayer player; DamageOverTime(IUpdater updater, IPlayer player) { this.player = player; // Subscribe to be called every frame updater.OnUpdate += HandleUpdate; } // Called every frame void HandleUpdate() { player.Health -= 5; if (player.Health <= 0) { // Unsubscribe to stop being called every frame updater.OnUpdate -= HandleUpdate; } } }
You can also explicitly start and stop the updater so that OnUpdate
gets called every frame or stops getting called every frame.
class HUD { void HandlePauseButton() { updater.Stop(); } void HandleResumeButton() { updater.Start(); } }
Now that we’ve seen how to use an IUpdater
, let’s see how one is implemented. There are many ways to do this, but for the purposes of this article we’ll use a MonoBehaviour
with a coroutine. Here’s the full CoroutineUpdater
class:
using System; using System.Collections; using UnityEngine; /// <summary> /// Dispatches an event every frame when a MonoBehaviour's coroutines are resumed /// </summary> /// <author>Jackson Dunstan, http://JacksonDunstan.com/articles/3382 /// <license>MIT</license> public class CoroutineUpdater : IUpdater { private MonoBehaviour monoBehaviour; private Coroutine coroutine; /// <summary> /// Dispatched every frame /// </summary> public event Action OnUpdate; /// <summary> /// The MonoBehaviour to run the coroutine with. /// Setting this stops any previous coroutine. /// </summary> public MonoBehaviour MonoBehaviour { get { return monoBehaviour; } set { Stop(); monoBehaviour = value; Start(); } } /// <summary> /// Start dispatching OnUpdate every frame /// </summary> public void Start() { if (coroutine == null && monoBehaviour) { coroutine = monoBehaviour.StartCoroutine(DispatchOnUpdate()); } } /// <summary> /// Stop dispatching OnUpdate every frame /// </summary> public void Stop() { if (coroutine != null && monoBehaviour) { monoBehaviour.StopCoroutine(coroutine); } coroutine = null; } private IEnumerator DispatchOnUpdate() { while (true) { if (OnUpdate != null) { OnUpdate(); } yield return null; } } }
At it’s core, the Start
function calls MonoBehaviour.StartCoroutine
to have Unity call DispatchOnUpdate
every frame. Likewise, the Stop
function stops it from being called. Both of them check to make sure they don’t start or stop if already started or stopped.
The MonoBehaviour
setter allows users to set the MonoBehaviour
they want the coroutine to run on. It automatically stops the previous coroutine and starts a new one on the new MonoBehaviour
.
Now let’s see how to use CoroutineUpdater
.
class MyScript : MonoBehaviour { IUpdater updater; IPlayer player; DamageOverTime dot; void Awake() { // Make the updater updater = new CoroutineUpdater(); // Give the updater a MonoBehaviour. This starts it. updater.MonoBehaviour = this; // Give a non-MonoBehaviour the updater dot = new DamageOverTime(updater, player); // Give the updater a new MonoBehaviour, perhaps because this one is // being destroyed due to a scene being loaded updater.MonoBehaviour = someOtherMonoBehaviour; } }
There’s really not much to it, but in the end we’ve created an abstract IUpdater
interface that allows us to decouple some of our code from MonoBehaviour
and even Unity.
Spot a bug? Have a question or suggestion? Post a comment!
#1 by Walker on February 29th, 2016 ·
Thanks for sharing, I use this pattern a lot. I often find it useful to pass the time delta with the update event. That way all my listening code don’t have to be bound to Unity’s Time class or implement dt math themselves. It also gives me a central place to scale time, making it faster or slower, if I need to. This tweak doesn’t make sense for everyone, though.