A Type-Safe Event System for Unity3D

The Event Listener pattern is an extremely common design pattern. Using Events instead of method calls let an object communicate with another object (or many objects) without explicit knowledge of the other object. With events acting as an implicit interface between objects, we can write much more loosely coupled (thus more reusable) code.

Unity’s own message passing system can be leveraged to achieve this effect, but there are a few problems with it. First, sending messages is hierarchy-dependent. You either need a reference to the object you wish to send the message (event) to, or you need a reference to that object’s parent object. This is not loosely coupled. Secondly, it’s not statically type-safe.

There have been several solutions to this problem (for example, FlashBang’s messaging system or this one on the UnifyCommunity wiki). These still lack type-safety, and won’t quite do.

Here is my event system implementation. It looks quite a bit like the event system in AS3:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
public class GameEvent
{
 
}
 
public class Events
{
    private static Events eventsInstance = null;
    public static Events instance
    {
        get
        {
            if (eventsInstance == null)
            {
                eventsInstance = new Events();
            }
 
            return eventsInstance;
        }
    }
 
    public delegate void EventDelegate<T> (T e) where T : GameEvent;
 
    private Dictionary<System.Type, System.Delegate> delegates = new Dictionary<System.Type, System.Delegate>();
 
    public void AddListener<T> (EventDelegate<T> del) where T : GameEvent
    {
        if (delegates.ContainsKey(typeof(T)))
        {
            System.Delegate tempDel = delegates[typeof(T)];
 
            delegates[typeof(T)] = System.Delegate.Combine(tempDel, del);
        }
        else
        {
            delegates[typeof(T)] = del;
        }
    }
 
    public void RemoveListener<T> (EventDelegate<T> del) where T : GameEvent
    {
        if (delegates.ContainsKey(typeof(T)))
        {
            var currentDel = System.Delegate.Remove(delegates[typeof(T)], del);
 
            if (currentDel == null)
            {
                delegates.Remove(typeof(T));
            }
            else
            {
                delegates[typeof(T)] = currentDel;
            }
        }
    }
 
    public void Raise (GameEvent e)
    {
        if (e == null)
        {
            Debug.Log("Invalid event argument: " + e.GetType().ToString());
            return;
        }
 
        if (delegates.ContainsKey(e.GetType()))
        {
            delegates[e.GetType()].DynamicInvoke(e);
        }
    }
}

To use this thing, first we declare a GameEvent subclass. This event can carry with it all of the parameters needed by the objects listening for the event.

public class SomethingHappenedEvent : GameEvent
{
	// Add event parameters here
}

Registering to listen for the event looks like this:

public class SomeObject : MonoBehaviour
{
	void OnEnable ()
	{
		Events.instance.AddListener<SomethingHappenedEvent>(OnSomethingHappened);
	}
 
	void OnDisable ()
	{
		Events.instance.RemoveListener<SomethingHappenedEvent>(OnSomethingHappened);
	}
 
	void OnSomethingHappened (SomethingHappenedEvent e)
	{
		// Handle event here
	}
}

And finally, to raise the event, do this:

Events.instance.Raise(new SomethingHappenedEvent());

The cool thing about this implementation is that it’s type-safe (listener registration errors will be caught at compile time, and no casting of events or event arguments) and it doesn’t require listening objects to implement a special interface or use Unity’s built-in message passing system.

The interface for this system is almost identical to that presented by Mike Mittleman at Unite 08, and I’d wager our implementations are similar. If you really want a rundown of the benefits and pitfalls of event-driven Unity development, I suggest watching his presentation on Unity’s website.

UPDATE:

I’ve made a Gist for this thing with revisions suggested in the comments:

44 Replies to “A Type-Safe Event System for Unity3D”

  1. Trying to use this and getting compiler errors here:

    AddListener(del, false);

    The error is:

    Assets/_Game/Scripts/Events/GameEvent.cs(46,9): error CS1501: No overload for method `AddListener’ takes `2′ arguments

    Is there another function missing from your code?

    Really interested in trying this out on my iPhone app.

    Thanks.

    Paul

    1. You’ve got to give AddListener a type parameter and a method (and no bool). For example:

      Events.instance.AddListener<MyEventType>(MyMethod)

      MyMethod isn’t a delegate. It’s just a method in your class that takes a parameter of type MyEvent. If you have more problems, I’d be happy to look at your code.

      1. Hm your updated code still does not convince me as to me it looks like GetInvocationList also uses some kind of reflection which is always slow – but did not check.

        I propose using a generic Raise function which you can check out here: https://gist.github.com/3786466

        Dont worry, the correct type is automatically detected by the compiler so no need to change the function call.

        1. Disassembling MutlicastDelegate reveals that while there isn’t any reflection going on in GetInvocationList(), it does loop over its delegate list and makes copy of it.

          I ran some tests on your version and mine. Your version took 695 milliseconds to raise 10000000 events, while mine took 3787 milliseconds. I’d imagine yours is using less memory as well, considering it doesn’t have to allocate an object array to pass parameters into Invoke().

          Clearly your version is better. Thanks!

  2. Will, I found this searching for references to Mike Mittleman’s video. Question: why do you use DynamicInvoke instead of just invoking the delegate directly (i.e. via Invoke?)

    1. Thanks for the input. I’ve put up a new version which doesn’t use DynamicInvoke. I haven’t banged on it very much, but I think it should work.

  3. Hi Will. I’m curious why you aren’t using C#’s own event system. At a quick glance, your code seems to have roughly the same function, so is there some speed or other benefits I’m missing?

    1. Hi Eddie. I actually do use C# events in my code for situations where I need a little more control over who is able to raise the event. I use the event model I’ve presented in this post to broadcast messages to anything that might want to listen. This event model is very similar to that implemented in Cocoa with the NSNotificationCenter class.

      Using this event model comes with two distinct advantages over native C# events. The first is that a C# event can only be raised by the class that defines it. The events in my system can be raised anywhere and listened to by anything. This kind of flexibility might not always be desirable, but I find I need it quite a lot when writing games. The other advantage is that objects communicating through these events are more loosely coupled than objects communicating through C# events. In order for an object to listen to a C# event on another object, it must have knowledge of the object instance and that object’s interface to register for the event, making those objects tightly coupled. Using my events, the only thing an object needs to know about is the event itself, not the object (or objects) that raise it. The event, as Mike Mittleman put it, acts as an implicit interface between objects.

      I hope that helps. We’re using this approach at Firaxis, and it’s working very well. Again, it’s not a silver bullet. I still use C# events (or just delegates) where appropriate.

      1. Ah, thanks, didn’t think of being able to raise them from anywhere. However using static events would also get rid of the need to know what instance the event is coming from.
        Still, I can see how this can be useful, I’ll keep it in mind.

        1. Static C# events would certainly let you register for them without knowledge of an instance, but the listening class still needs to know about the class with the events, making the event raising class and the event listening classes tightly coupled.

          This isn’t a really big deal, but if you’re reusing classes that listen for the event in another project, you’ll have to rewrite parts of them to register for those events in a different place.

  4. The system looks great. Can’t wait to try it, thanks for releasing this.

    Some questions.

    – Will this eventually result in tons of Event subclasses? Is this much of a problem? Do you dump all events in the same .cs file?
    – How is performance compared to its alternatives, ie delegates, BroadcastMessage and the linked event systems?

    Thanks.

    1. Glad you like it!

      You’ll need a subclass for each event. Generally, I define an event in the same .CS file with the class that is going to raise it. This better reinforces the “implicit public interface” paradigm. If more than one class can raise an event, I pull the event out into its own .CS file.

      Performance is going to be slightly slower than using C#’s built-in event system because of the overhead incurred looking the delegates up from a dictionary. The performance difference is negligible.

      1. Thanks for the response. I have tried the system, and it sure beats the mess I was using before.

        Good discussion about the pros/cons vs C#’s event system in the previous comment thread, too.

  5. I am not sure but i think you miss the following:

    where T : GameEvent

    At the end of the RemoveListener:

    public void RemoveListener (EventDelegate eventDelegate)

  6. Hey Will, thanks for sharing this really awesome event system!

    One thing though: I kinda got lost at “EventDelegate internalDelegate = (e) => del((T)e);” – I’m kinda new to lamda expressions, could you write the normal code equivalent to this? what does it do?

    Thanks.

    1. I add event listeners in OnEnable() and remove them in OnDisable(). OnDisable() gets called whenever an object is destroyed. This will clean up listeners when a new scene is loaded.

  7. Hey Mike, if you can, please answer my lambda expression question – it’s above my last one. Thanks.

  8. Greetings!

    First, I’m finding your event system very useful, so thanks for sharing it! I’ve done a couple things with it that I thought were worth sharing.

    I added a “propagateUpward” property to GameEvent, which has a special handling case in Raise. This searches up the transform tree and raises the event on every consecutive parent which has Events. I’m using this in a game where I have sub-objects which have distinct responsibilities, but which need to notify objects higher up in the hierarchy of what’s happening, e.g., a ShieldModule component on a ship which dispatches an event, for which a ShipManager component on a higher level object is listening.


    public void Raise (GameEvent e)
    {
    EventDelegate del;
    if (delegates.TryGetValue(e.GetType(), out del))
    {
    del.Invoke(e);
    }

    if(e.propagateUpward && transform.parent)
    {
    Transform parent = transform.parent;

    if(parent != null)
    {
    Events parentEvents = parent.gameObject.GetComponent();
    if(parentEvents != null)
    {
    parentEvents.Raise(e);
    }
    }
    }
    }

    Another is a radial raise. I’m using this to do splash damage, “sensor” notifications for AI, and a couple of other things.


    public void Raise (GameEvent e, float radius)
    {
    Collider[] candidates = Physics.OverlapSphere(this.transform.position, radius);

    foreach(Collider candidate in candidates)
    {
    Events candidateEvents = candidate.gameObject.GetComponent();
    if(candidateEvents != null && candidateEvents != this)
    {
    candidateEvents.Raise(e);
    }
    }
    }

    Note, I realize that by combining the two behaviors above (radially raising a propagating event), parent Events objects may receive redundant Events. I’m hoping to solve this at some point, but for now I’m merely treading cautiously.

    Anyway, thanks again, I hope someone finds this interesting or even useful.

    1. This is really great! Thanks for sharing. I’ll be interested to see what else you add and how it works out for you.

  9. Maybe a noob question, but this doesn’t creating all these GameEvents for each message hit the GC?

    1. That depends. In the example I’ve provided, yes. Each event raised would hit the GC. This is not recommended for high-frequency usage. There are a couple of ways to mitigate this, however. If you have an event that’s getting raised frequently, you could allocate it once and reuse it, resetting it each time it’s raised instead of making a new one. Also, there’s no reason the GameEvent baseclass needs to be a class at all. It could just as easily be a struct, which would solve the problem as well.

      1. Fast response thanks! My question was a little mangled from being tired but I wanted to make sure there wasn’t some subtle reason caching the GameEvent would be a bad idea.

        1. Should be fine. Just make sure you have a way to reset the state of your cached event before raising it.

      2. Please excuse my noobness to C#, but how would adjust your scripts when turning the GameEvent base class into a struct since GameEvent is subclassed? Should i use an interface?

        1. Yes, you are absolutely right (what I get for writing c++ at work all day). You could easily turn GameEvent into an interface and use structs. In fact, there is no real reason to have GameEvent in the implementation at all (it’s an empty class, after all). I kept it there because my own version of this has debugging and serialization functionality build into GameEvent. But you could rewrite the interface to accept straight object references or interfaces instead.

          1. Thanks for the quick and helpful response. Are there any potential limitations that you see with an interface – struct implementation?

            In regards to: “If you have an event that’s getting raised frequently, you could allocate it once and reuse it, resetting it each time it’s raised instead of making a new one”. Where(how) would you make the code to test if allocated then reuse-reset? I wonder about the performance difference with reuse-reset instead of solely adding? It might be a more performant approach in moderate to high frequency situations anyways?

            PS: I watched Mike Mittleman’s Techniques for Making Reusable Code at Unite 08 – great stuff not just on events but scripting in general. Thanks again.

          2. I see no problems with using structs. Structs are value types though, so if you have lots of data living in your event struct, copying it might not be as performant. You’d save a lot of garbage collector thrash though. I might go back and change the implementation to support this. Great idea!

            [WORDPRESS HASHCASH] The poster sent us ‘0 which is not a hashcash value.

          3. Thinking about this a little more… If you refactored the event system to take an interface (possibly implemented by structs) instead of an object, you might run into some boxing shenanigans which would create heap-allocated data that would need to be garbage collected. I’m going to look into this a little more and get back to you.

            I think the best option for now is to use the event system as it is, and cache high-frequency events as you need to. Have the object that raises the event cache and reset it.

            [WORDPRESS HASHCASH] The poster sent us ‘0 which is not a hashcash value.

          4. An idea with using structs and avoiding boxing would be to use generics and an interface, e.g. You still run into the issue of copying the value (-guess- you can pass by ref, but that may have unforeseen consequences depending on your game events, but they probably would be outwardly immutable anyways)

            public interface IGameEvent { }

            public class Events
            {
            ….
            public void Raise(T e) where T : IGameEvent
            {
            …..
            }
            }

          5. Formatting issues…

            public interface IGameEvent { }

            public class Events
            {
            ...
            public void Raise(T e) where T : IGameEvent
            {
            ...
            }
            }

  10. Coming from AS3 dev this is just great.

    I’m a C# noob, but from what I see it seems that if 2 objects add an event listener for the same type of event, both would get noticed. Is this right?

    1. That’s right. If you register two listeners for one event, both listeners will be called when that event is raised.

  11. Great system, it works very well, and achieved what I was looking for.
    Unity messages are great for quick prototyping, but when projects start to grow, debugging becomes a mess…

    I took your system and modifed it a bit to add some way to specialize receivers, which can mimic the functionality of the Sendmessage/Broadcastmessage functions. I’d like to try to put this on the Unity asset store, for free. Is it ok with you?

  12. Hello friend! I’m using this script of yours and noticed something strange in the profiler.
    Take a look at the attached screenshot.

    Why does that nesting happens? It makes hard to see stuff in the profiler.
    Can you think of any way to reduce that?

    Thanks for your hard work!

    screenshot of the profiler when an event is raised: http://imgur.com/DhAeUCf

    1. If an event calls code which raises another event (which might call code that raises another event, etc) you will see this nesting in the profiler. Think of it as a stack trace.

      1. I just found the criminal: a stray event was being raised in an unrelated class. That event caused then player’s transform to move. PlayerOnMove is raised as a reaction to the player’s transform changing position. And there we go. Nested event calls!

        Thanks for the pointer!

        By the way, I’m using the event raising type-safely!

        What I did was make derived events with constructors that require the type that is allowed to raise the event. This way we have to pass a this reference when raising an event.

        Here’s an example:


        public sealed class PlayerOnMove : TransformChangesEventBase {
        public Transform PlayerTransform { get { return ChangedTransform; } }
        public new PlayerMovement Sender { get; private set; }

        public PlayerOnMove(PlayerMovement sender) : base(sender.gameObject.transform) {
        Sender = sender;
        }
        }

        Here’s the TransformChangesBase


        public abstract class TransformChangesEventBase : GameEventBase {
        protected Transform ChangedTransform { get; set; }

        protected TransformChangesEventBase(Transform transform) {
        ChangedTransform = transform;
        }
        }

        And the GameEventBase


        public abstract class GameEventBase {
        ///
        /// The sender object. Is overridden with a specific type in the concrete implementation. This gives us type-safety in calling events (events can only be raised by the specific type of the sender)
        ///
        public virtual object Sender { get; set; }
        }

        Now we can only raise PlayerOnMovement from the PlayerMovement type.

        One advantage of that is that we don’t require casting to use anything we might need from the sender, as we have the type statically set.

        As disadvantages we have to create more classes and event raising is much more rigid.

Comments are closed.