https://github.com/GandhiGames/unity_game_foundations

What is it?

Game events are commonly discussed as they can be an integral part of most games architecture.

A game event can be anything that other game objects could be interested in. The player dying, an explosion going off, and a coin being collected can all be classed as events. There is often a need to notify other entities when these actions occur without tightly coupling the entities.

When should you use it?

I use some form of an event system in most of my projects. Common uses include:

  • Achievement system (various entities can raise events without needing a reference to the achievement manager, easily implement incremental achievements).
  • Audio player. Any object can raise an audio event when required.
  • AI systems

How do you use it?

Implementing Your Own Events

Once you have imported the event system (included in the GitHub project above and full code listing provided below) you need to write your own events. These events will be specific to your own game’s needs.

The base event class is straightforward (read: empty). All of your events should inherit from this class.

public abstract class GameEvent {}

An example of an audio event is shown below (the audio queue included in the GitHub project responds to these events).

///<summary>
/// Raised when audio clip should be played. Picked up by AudioPlayer2D.
/// </summary>
public class AudioEvent2D : GameEvent, IAudioEvent2D
{
private AudioClip audioClip;

public AudioClip Audio
{
get
{
return audioClip;
}
}

public AudioEvent2D(AudioClip audioClip)
{
this.audioClip = audioClip;
}
}

Raising Events

Once you have implemented your own events it is easy to “raise” them. This simply means “I don’t know who cares but this event has just happened, do what you will with that information”.

Events.instance.Raise(new ExplosionEvent(position, radius, force));

Listening to Events

No actions will be performed when an event is raised if they’re no listeners for that event. It is important to remember to stop listening to the event when the object is removed from the scene or you are no longer interested in the event.

void OnEnable()
{
Events.instance.AddListener<CubeSelectedEvent>(OnSelected);
Events.instance.AddListener<ExplosionEvent>(OnExplosion);
}

void OnDisable()
{
Events.instance.RemoveListener<CubeSelectedEvent>(OnSelected);
Events.instance.RemoveListener<ExplosionEvent>(OnExplosion);
}

protected void OnSelected(GameEvent e)
{
// See Object Pool post for more information.
try
{
ObjectPool.instance.PoolObject(gameObject);
}
catch (PrefabNotFoundException ex)
{
Debug.Log(ex.Message);
Destroy(gameObject);
}
}

protected void OnExplosion(ExplosionEvent e)
{
if (e.InExplosionRadius(transform.position))
{
_rigidbody.AddExplosionForce(e.Force, e.Position, e.Radius);
}
}

Event Manager (Full Code)

The event manager class is included in full below.

///<summary>
/// Type-safe Event Manager. Holds pool of event listeners that are notified when the event is raised.
/// </summary>
public class Events
{
private static Events _instance;
public static Events instance
{
get
{
if (_instance == null)
{
_instance = new Events();
}

return _instance;
}
}

///<summary>
/// Delagate for game event data sent to listeners.
/// </summary>
/// <typeparam name="T">Game Event Sub Class</typeparam>
public delegate void EventDelegate<T>(T e) where T : GameEvent;
private delegate void InternalDelegate(GameEvent e);
private Dictionary<System.Type, InternalDelegate> delegates = new Dictionary<System.Type, InternalDelegate>();
private Dictionary<System.Delegate, InternalDelegate> delegateLookup = new Dictionary<System.Delegate, InternalDelegate>();

///<summary>
/// Adds method to be invoked when event raised.
/// </summary>
/// <typeparam name="T">The event associated with the event delegate.</typeparam>
/// <param name="del">The method to be stored and invoked if the event is raised.</param>
public void AddListener<T>(EventDelegate<T> del) where T : GameEvent
{
// Create non-generic delegate.
InternalDelegate internalDelegate = (e) => del((T)e);

// If event method already stored, return.
if (delegateLookup.ContainsKey(del) && delegateLookup[del] == internalDelegate)
{
return;
}

// Store in delegate lookup for future checks.
delegateLookup[del] = internalDelegate;

// Store delegate in method lookup (invoked when event raised).
// If delegates already contains an event of type T then the event is added to that
// else the event is stored in a new position in the dictionary.
InternalDelegate tempDel;
if (delegates.TryGetValue(typeof(T), out tempDel))
{
delegates[typeof(T)] = tempDel += internalDelegate;
}
else
{
delegates[typeof(T)] = internalDelegate;
}
}

///<summary>
/// Removes method to be invoked when event raised.
/// </summary>
/// <typeparam name="T">The event associated with the event delegate.</typeparam>
/// <param name="del">The method to be removed.</param>
public void RemoveListener<T>(EventDelegate<T> del) where T : GameEvent
{
InternalDelegate internalDelegate;

// Attempts to find delegate in lookup table.
if (delegateLookup.TryGetValue(del, out internalDelegate))
{
InternalDelegate tempDel;
// Attempt to find in delegate dictionary.
if (delegates.TryGetValue(typeof(T), out tempDel))
{
// Removes internal delagte.
tempDel -= internalDelegate;
// If internal delegate is now null (as all events have been removed from it).
if (tempDel == null)
{
// Remove delegate completely.
delegates.Remove(typeof(T));
}
else
{
// Store delegate (minus removed method).
delegates[typeof(T)] = tempDel;
}
}
// Remove from lookup table.
delegateLookup.Remove(del);
}
}

///<summary>
/// Raises an event. ALl methods associated with event are invoked.
/// </summary>
/// <param name="e">THe event to raise. This is passed to associated delegates.</param>
public void Raise(GameEvent e)
{
InternalDelegate del;
if (delegates.TryGetValue(e.GetType(), out del))
{
del.Invoke(e);
}
}
}

As you can see I’ve implemented the event system as a singleton. However this is not always the best idea and I only use it this way during testing and not in any of my final projects.

Thank you for reading 🙂