As mentioned in last week’s article on the “pure code” approach to Unity code design, capturing events can be problematic. I gave an example of how this could be overcome, but didn’t flesh it out to cover the sixty events that a MonoBehaviour can receive. Today’s article includes the source code for a class that does just that. It should prove useful to anyone interested in exploring “pure code” design.

Let’s first recap the problem being solved today. Unity’s way of telling your code that an event has happened is by calling a function on the MonoBehaviour attached to the applicable GameObject. This function must have a particular name and not be private. For example, Start is called on the first frame your GameObject is active in the scene:

using UnityEngine;
 
public class StartTest : MonoBehaviour
{
	void Start()
	{
		Debug.Log("started!");
	}
}

You don’t need to explicitly register to receive the event and there’s no way to unregister for it. Simply by having that function you will receive the event by having that function called until either your MonoBehaviour or the GameObject it’s attached to is destroyed.

In the “pure code” approach to Unity app design, you only have one MonoBehaviour and it contains and calls all your app code. You still create more GameObject instances, but don’t attach app logic to them via more MonoBehaviour instances.

So how do you get events for the game objects your app creates? If you don’t have any MonoBehaviour on them, you don’t get the events. The trick is to employ the “event forwarding” system mentioned briefly in the previous article.

This system involves the creation of a MonoBehaviour that doesn’t do any app logic. Instead, it’s sole purpose is to receive events from Unity and dispatch them to anyone interested via a standard C# event. Here’s a very simple version to illustrate:

using UnityEngine;
 
public class EventForwarder : MonoBehaviour
{
	// Type of function that handles Start events
	public delegate void EventHandler();
 
	// Event used to forward the Start event
	public event EventHandler StartEvent = () => {};
 
	// Receiver for the Start event
	// Unity calls this
	public void Start()
	{
		// Forward the Start event by dispatching the
		// associated C# event
		StartEvent();
	}
}

The app code residing on and called by the main MonoBehaviour uses this EventForwarder class like so:

using UnityEngine;
 
public class TestScript : MonoBehaviour
{
	private EventForwarder forwarder;
 
	void Start()
	{
		// Create a GameObject
		var otherGameObject = new GameObject("created at runtime");
 
		// Add an EventForwarder to it so we can receive events
		forwarder = otherGameObject.AddComponent<EventForwarder>();
 
		// Listen for it to start
		forwarder.StartEvent += HandleStartEvent;
	}
 
	private void HandleStartEvent()
	{
		// Stop listening for it to start
		forwarder.StartEvent -= HandleStartEvent;
 
		// Do something in response to the event
		Debug.Log("Detected other game object's Start()");
	}
}

There are some advantages to this system. First, it makes the “pure code” approach much more useful as it allows you to receive events from Unity. Second, it allows you to explicitly register and unregister for events. This means you can listen to them only when you want to rather than always listening to them. Third, the C# event system often feels more natural to those unfamiliar with the Unity events system. Fourth, it helps reduce the number of bugs that crop up when you accidentally name a function incorrectly and then never receive any events.

With that in mind, the following is a complete version of the EventForwarder class above. It features all 60 events available to a MonoBehaviour as of Unity 4.6.1.

using UnityEngine;
 
/**
 * Utility component to add to game objects whose events you want forwarded from Unity's message
 * system to standard C# events. Handles all events as of Unity 4.6.1.
 * @author Jackson Dunstan - http://jacksondunstan.com/articles/2922
 */
public class EventForwarder : MonoBehaviour
{
	public delegate void EventHandler0();
	public delegate void EventHandler1<TParam>(TParam param);
	public delegate void EventHandler2<TParam1,TParam2>(TParam1 param1, TParam2 param2);
 
	public event EventHandler0 AwakeEvent = () => {};
	public event EventHandler0 FixedUpdateEvent = () => {};
	public event EventHandler0 LateUpdateEvent = () => {};
	public event EventHandler1<int> OnAnimatorIKEvent = layerIndex => {};
	public event EventHandler0 OnAnimatorMoveEvent = () => {};
	public event EventHandler1<bool> OnApplicationFocusEvent = focusStatus => {};
	public event EventHandler1<bool> OnApplicationPauseEvent = pauseStatus => {};
	public event EventHandler0 OnApplicationQuitEvent = () => {};
	public event EventHandler2<float[],int> OnAudioFilterReadEvent = (data,channels) => {};
	public event EventHandler0 OnBecameInvisibleEvent = () => {};
	public event EventHandler0 OnBecameVisibleEvent = () => {};
	public event EventHandler1<Collision> OnCollisionEnterEvent = collision => {};
	public event EventHandler1<Collision2D> OnCollisionEnter2DEvent = collision => {};
	public event EventHandler1<Collision> OnCollisionExitEvent = collision => {};
	public event EventHandler1<Collision2D> OnCollisionExit2DEvent = collision => {};
	public event EventHandler1<Collision> OnCollisionStayEvent = collision => {};
	public event EventHandler1<Collision2D> OnCollisionStay2DEvent = collision => {};
	public event EventHandler0 OnConnectedToServerEvent = () => {};
	public event EventHandler1<ControllerColliderHit> OnControllerColliderHitEvent = hit => {};
	public event EventHandler0 OnDestroyEvent = () => {};
	public event EventHandler0 OnDisableEvent = () => {};
	public event EventHandler1<NetworkDisconnection> OnDisconnectedFromServerEvent = info => {};
	public event EventHandler0 OnDrawGizmosEvent = () => {};
	public event EventHandler0 OnDrawGizmosSelectedEvent = () => {};
	public event EventHandler0 OnEnableEvent = () => {};
	public event EventHandler1<NetworkConnectionError> OnFailedToConnectEvent = error => {};
	public event EventHandler1<NetworkConnectionError> OnFailedToConnectToMasterServerEvent = error => {};
	public event EventHandler0 OnGUIEvent = () => {};
	public event EventHandler1<float> OnJointBreakEvent = breakForce => {};
	public event EventHandler1<int> OnLevelWasLoadedEvent = level => {};
	public event EventHandler1<MasterServerEvent> OnMasterServerEventEvent = msEvent => {};
	public event EventHandler0 OnMouseDownEvent = () => {};
	public event EventHandler0 OnMouseDragEvent = () => {};
	public event EventHandler0 OnMouseEnterEvent = () => {};
	public event EventHandler0 OnMouseExitEvent = () => {};
	public event EventHandler0 OnMouseOverEvent = () => {};
	public event EventHandler0 OnMouseUpEvent = () => {};
	public event EventHandler0 OnMouseUpAsButtonEvent = () => {};
	public event EventHandler1<NetworkMessageInfo> OnNetworkInstantiateEvent = info => {};
	public event EventHandler1<GameObject> OnParticleCollisionEvent = other => {};
	public event EventHandler1<NetworkPlayer> OnPlayerConnectedEvent = player => {};
	public event EventHandler1<NetworkPlayer> OnPlayerDisconnectedEvent = player => {};
	public event EventHandler0 OnPostRenderEvent = () => {};
	public event EventHandler0 OnPreCullEvent = () => {};
	public event EventHandler0 OnPreRenderEvent = () => {};
	public event EventHandler2<RenderTexture,RenderTexture> OnRenderImageEvent = (src,dest) => {};
	public event EventHandler0 OnRenderObjectEvent = () => {};
	public event EventHandler2<BitStream,NetworkMessageInfo> OnSerializeNetworkViewEvent = (stream,info) => {};
	public event EventHandler0 OnServerInitializedEvent = () => {};
	public event EventHandler1<Collider> OnTriggerEnterEvent = other => {};
	public event EventHandler1<Collider2D> OnTriggerEnter2DEvent = other => {};
	public event EventHandler1<Collider> OnTriggerExitEvent = other => {};
	public event EventHandler1<Collider2D> OnTriggerExit2DEvent = other => {};
	public event EventHandler1<Collider> OnTriggerStayEvent = other => {};
	public event EventHandler1<Collider2D> OnTriggerStay2DEvent = other => {};
	public event EventHandler0 OnValidateEvent = () => {};
	public event EventHandler0 OnWillRenderObjectEvent = () => {};
	public event EventHandler0 ResetEvent = () => {};
	public event EventHandler0 StartEvent = () => {};
	public event EventHandler0 UpdateEvent = () => {};
 
	public void Awake()
	{
		AwakeEvent();
	}
 
	public void FixedUpdate()
	{
		FixedUpdateEvent();
	}
 
	public void LateUpdate()
	{
		LateUpdateEvent();
	}
 
	public void OnAnimatorIK(int layerIndex)
	{
		OnAnimatorIKEvent(layerIndex);
	}
 
	public void OnAnimatorMove()
	{
		OnAnimatorMoveEvent();
	}
 
	public void OnApplicationFocus(bool focusStatus)
	{
		OnApplicationFocusEvent(focusStatus);
	}
 
	public void OnApplicationPause(bool pauseStatus)
	{
		OnApplicationPauseEvent(pauseStatus);
	}
 
	public void OnApplicationQuit()
	{
		OnApplicationQuitEvent();
	}
 
	public void OnAudioFilterRead(float[] data, int channels)
	{
		OnAudioFilterReadEvent(data, channels);
	}
 
	public void OnBecameInvisible()
	{
		OnBecameInvisibleEvent();
	}
 
	public void OnBecameVisible()
	{
		OnBecameVisibleEvent();
	}
 
	public void OnCollisionEnter(Collision collision)
	{
		OnCollisionEnterEvent(collision);
	}
 
	public void OnCollisionEnter2D(Collision2D collision)
	{
		OnCollisionEnter2DEvent(collision);
	}
 
	public void OnCollisionExit(Collision collision)
	{
		OnCollisionExitEvent(collision);
	}
 
	public void OnCollisionExit2D(Collision2D collision)
	{
		OnCollisionExit2DEvent(collision);
	}
 
	public void OnCollisionStay(Collision collision)
	{
		OnCollisionStayEvent(collision);
	}
 
	public void OnCollisionStay2D(Collision2D collision)
	{
		OnCollisionStay2DEvent(collision);
	}
 
	public void OnConnectedToServer()
	{
		OnConnectedToServerEvent();
	}
 
	public void OnControllerColliderHit(ControllerColliderHit hit)
	{
		OnControllerColliderHitEvent(hit);
	}
 
	public void OnDestroy()
	{
		OnDestroyEvent();
	}
 
	public void OnDisable()
	{
		OnDisableEvent();
	}
 
	public void OnDisconnectedFromServer(NetworkDisconnection info)
	{
		OnDisconnectedFromServerEvent(info);
	}
 
	public void OnDrawGizmos()
	{
		OnDrawGizmosEvent();
	}
 
	public void OnDrawGizmosSelected()
	{
		OnDrawGizmosSelectedEvent();
	}
 
	public void OnEnable()
	{
		OnEnableEvent();
	}
 
	public void OnFailedToConnect(NetworkConnectionError error)
	{
		OnFailedToConnectEvent(error);
	}
 
	public void OnFailedToConnectToMasterServer(NetworkConnectionError error)
	{
		OnFailedToConnectToMasterServerEvent(error);
	}
 
	public void OnGUI()
	{
		OnGUIEvent();
	}
 
	public void OnJointBreak(float breakForce)
	{
		OnJointBreakEvent(breakForce);
	}
 
	public void OnLevelWasLoaded(int level)
	{
		OnLevelWasLoadedEvent(level);
	}
 
	public void OnMasterServerEvent(MasterServerEvent msEvent)
	{
		OnMasterServerEventEvent(msEvent);
	}
 
	public void OnMouseDown()
	{
		OnMouseDownEvent();
	}
 
	public void OnMouseDrag()
	{
		OnMouseDragEvent();
	}
 
	public void OnMouseEnter()
	{
		OnMouseEnterEvent();
	}
 
	public void OnMouseExit()
	{
		OnMouseExitEvent();
	}
 
	public void OnMouseOver()
	{
		OnMouseOverEvent();
	}
 
	public void OnMouseUp()
	{
		OnMouseUpEvent();
	}
 
	public void OnMouseUpAsButton()
	{
		OnMouseUpAsButtonEvent();
	}
 
	public void OnNetworkInstantiate(NetworkMessageInfo info)
	{
		OnNetworkInstantiateEvent(info);
	}
 
	public void OnParticleCollision(GameObject other)
	{
		OnParticleCollisionEvent(other);
	}
 
	public void OnPlayerConnected(NetworkPlayer player)
	{
		OnPlayerConnectedEvent(player);
	}
 
	public void OnPlayerDisconnected(NetworkPlayer player)
	{
		OnPlayerDisconnectedEvent(player);
	}
 
	public void OnPostRender()
	{
		OnPostRenderEvent();
	}
 
	public void OnPreCull()
	{
		OnPreCullEvent();
	}
 
	public void OnPreRender()
	{
		OnPreRenderEvent();
	}
 
	public void OnRenderImage(RenderTexture src, RenderTexture dest)
	{
		OnRenderImageEvent(src, dest);
	}
 
	public void OnRenderObject()
	{
		OnRenderObjectEvent();
	}
 
	public void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
	{
		OnSerializeNetworkViewEvent(stream, info);
	}
 
	public void OnServerInitialized()
	{
		OnServerInitializedEvent();
	}
 
	public void OnTriggerEnter(Collider other)
	{
		OnTriggerEnterEvent(other);
	}
 
	public void OnTriggerEnter2D(Collider2D other)
	{
		OnTriggerEnter2DEvent(other);
	}
 
	public void OnTriggerExit(Collider other)
	{
		OnTriggerExitEvent(other);
	}
 
	public void OnTriggerExit2D(Collider2D other)
	{
		OnTriggerExit2DEvent(other);
	}
 
	public void OnTriggerStay(Collider other)
	{
		OnTriggerStayEvent(other);
	}
 
	public void OnTriggerStay2D(Collider2D other)
	{
		OnTriggerStay2DEvent(other);
	}
 
	public void OnValidate()
	{
		OnValidateEvent();
	}
 
	public void OnWillRenderObject()
	{
		OnWillRenderObjectEvent();
	}
 
	public void Reset()
	{
		ResetEvent();
	}
 
	public void Start()
	{
		StartEvent();
	}
 
	public void Update()
	{
		UpdateEvent();
	}
}

I hope you find this class useful. If you have anything to add to it or find any issues with it, let me know in the comments.