Unity Events System

Facebooktwittergoogle_plusredditpinterestlinkedinmailby feather

Problem:

As a game grows in complexity objects can become coupled, i.e. the behaviour of one object is closely dependant on the behaviour of another.  This can result in hard to maintain code as changing one object can have undesired effects on another.
For example, suppose we have a Player object that can move around on screen and crash into Enemy objects.  Every time the user presses an arrow key the Player object must process the input and move the player.  If there is a collision, a sound should play and the user’s score should be reduced.
Now the Player object is responsible for:
  • Processing input
  • Moving on screen
  • Listening for collisions
  • Playing sound
  • Updating the score
Ideally the player object should be responsible for as few of these functions as possible.  There should be a separate object for playing sound, updating the score and processing input.  We could store references to these manager objects in the Player object and have the Player object call methods when necessary.  But there is still a problem here, the Player object needs to know how the sound is being played, i.e. it needs to know how to call play_sound() in the sound manager when it detects a collision.  The Player object should have no knowledge of how these functions are performed so that its behaviour is totally isolated from them.
One strategy to reduce coupling between components of a system is to introduce a mediator between them that handles communication.  Instead of objects directly calling each other’s methods when they need external behaviour (e.g. playing a sound), they instead broadcast an event message using the mediator.  The broadcasting object does not know what objects are listening for this event, it simply broadcasts a message saying the event occurred.

The Game Event System

This messaging system is composed of three classes.
  1. Game Event – An object that is broadcast to alert other objects of an event.  It may have data associated with it.
  2. Game Event Listener – An object that listens for game events and responds to them.
  3. Game Event Manager – Stores a list of all Game Event Listeners and notifies them when a Game Event is broadcast.

Game Event

This is a marker interface.  It identifies implementing objects as Game Events so that the Game Event Manager can post them.
Implementing classes may extend its functionality to include extra data or methods.
Below is the code for the GameEvent interface:
//////////////////////////////////////////////////////////////////////////////
// Event.cs
//////////////////////////////////////////////////////////////////////////////
// This is a marker interface that allows implementing events to be 
// identified as events.
//////////////////////////////////////////////////////////////////////////////
using UnityEngine;
using System.Collections;
namespace GameEvents
{
   public interface GameEvent 
   {
   }
}
There are no methods that must be implemented.  The class is simply a marker interface.  It uses the GameEvents namespace so that it can easily be identified throughout the project.

Game Event Listener

This is an interface that allows implementing objects to listen for events.
//////////////////////////////////////////////////////////////////////////////
// EventListener.cs
//////////////////////////////////////////////////////////////////////////////
// This interface allows objects to register themselves with an event manager
// in order to listen for events.
//////////////////////////////////////////////////////////////////////////////
using UnityEngine;
using System.Collections;
namespace GameEvents
{
   public interface GameEventListener 
   {
      void eventReceived(GameEvent e);
   }
}
The interface defines one method that must be implemented:  eventReceived(GameEvent e)
 
This method will be called when a Game Event is broadcast.
What happens when the event is received should be defined by the implementing class.

Game Event Manager

This class contains a list of all listeners.  The listeners are held as weak references so that they do not need to be unregistered explicitly.  (note:  Objects inheriting MonoBehaviour will need to be unregistered when destroyed as they are not immediately removed).
//////////////////////////////////////////////////////////////////////////////
// GameEventManager.cs
//////////////////////////////////////////////////////////////////////////////
// Stores a list of listeners and notifies them when an event is dispatched.
//////////////////////////////////////////////////////////////////////////////
 
code>using UnityEngine;
using System;
using System.Linq;
using System.Collections.Generic;
 
namespace GameEvents
{
 
   public class GameEventManager
   {
   
      static List<WeakReference> listeners = new List<WeakReference>();
      
      public static void registerListener(GameEventListener el)
      {      
         listeners.Add(new WeakReference(el));
      }
      
      public static void post(GameEvent e)
      {
         //Filter out the dead weak references
         var alive_list = (from el in listeners
                           where el.IsAlive
                           select el);
         
         //store the alive references
         listeners = alive_list.ToList();
         
         //iterate through alive references         
         foreach(WeakReference wref in alive_list)
         {           
            //call eventReceived on the listener
            (wref.Target as GameEventListener).eventReceived(e);
         }         
      }
      
      public static void unregisterListener(GameEventListener el)
      {
         listeners.RemoveAll(x => x.Target == el);
      }
      
   }
}
The class simply stores a list of listeners and when an event is posted it calls eventReceived on all of them so that it can pass the event onto them.
The list of listeners and all the methods have been marked as static.  This allows classes to reference it without having to create an instance and then pass it around for each class.