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 MonoBehaviours. 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!