C# events and Unity3d
Did you know that C# has a built-in event system? And a very good one! It can be quite useful with Unity3D. This article provides an example.
In order to respond to events dispatched by a GameObject you typically would create a script that extends MonoBehaviour and implement the methods you need. So if you want to react to the user hovering the object with the mouse you would create the OnMouseOver method. It would typically look something like this:
void OnMouseOver () {
renderer.material.color = Color.red;
}
That works fine. But what if you want another object to be notified about that event? One way would be to keep a reference to that other object in your script and call it from within your method:
public MyScript myScript;
//
void OnMouseOver () {
myScript.NotifyMouseOver();
}
This also works fine, but you always need to keep a reference to the object you notify. Also, you can only notify this object, unless you keep more references to other object. This can quickly get messy.
Messages
Another solution would be to use SendMessage or SendMessageUpwards methods. While they seem to be the right tool for the job, these methods have several major flaws, and in my opinion you should completely avoid using them.
Their syntax is awkward to start with, and you need to pass method names as string literals - which is very error prone! Also, those methods work only within the object's transform hierarchy. You can invoke methods on scripts that are either attached to the same GameObject or to one of it ancestors.
Events
Fortunately there is a better way to solve this problem, and this is with the C# built in event system. I can't explain how the event system works in detail, but if you are interested on learning more on this topic, go and check the tutorial on MSDN.
Now, let's see how to use events in Unity3D. Consider this script:
using UnityEngine;
public class EventDispatcher : MonoBehaviour {
//
public delegate void EventHandler(GameObject e);
public event EventHandler MouseOver;
//
void OnMouseOver () {
if (MouseOver != null)
MouseOver (this.gameObject);
}
}
Don't worry if you don't know exactly what it does. What's important is that once you attach it to a GameObject you can go to another script (any script in your project!) and if you have a reference to that object you can write something like this:
private GameObject s;
// [...]
s.GetComponent().MouseOver += Listener;
// [...]
void Listener(GameObject g) {
// g is being hovered, do something...
}
This approach is more flexible than using messages, because it works with any script not only with those that are in the same transform hierarchy. If you keep a Singleton object that manages the state of the application, you could listen to events coming from any GameObject in it. Of course not only a GameObject can send events - any object can.
One of the consequences of such setup is that the same listening function can be used to respond to events coming from different objects. By passing a reference as argument you always know which object dispatched the event - this is what I do in the example.
References, controllers and MVC
In the first scenario above you needed to keep a reference to the listener in the object that sends the event, and I said it's not a good idea. In the version which uses the built-in events, it needs to be the other way around - you need a reference to the object that sends the event in the object that listens to it. How is it better, you might ask?
First of all, here, the sender doesn't know who is listening to the events is sends - and even how many listeners there are. All it does is to send the event and forget. In the first scenario above, imagine how cumbersome it would be to tell the sender to stop notifying the listener!
With the event system, it's the listener that has the responsibility to decide what events it listens to, when to start, and when to stop listening to them. Such object usually manages the state of the application or executes some game logic. To borrow the term from the MVC design pattern - it's the controller. That is why it's perfectly logical to give him such responsibility. In that way, using events creates more solid code.
Finally, I love the overloaded += operator used to add a listener, it's so clean! As you might have guessed by now, when you are done listening to the event you can just say:
s.GetComponent().MouseOver -= Listener;
Of course you can create a generic EventDispatcher class that implements all events that a GameObject can send - here's a version that implements a few of them already. By looking at the code, I'm sure you can figure out how to add others. However, beware of implementing OnGUI that way! (if you want to know why, read this post).