Event Broadcasting - maksmaisak/Saxion_Y2Q1_Project_Clients_Call GitHub Wiki

The game's global event broadcasting system provides a mechanism by which instances of a custom class may be delivered to all objects who subscribe to that class. Defining, subscribing to and posting events is extremely easy:

public class OnExampleEvent : BroadcastEvent<OnExampleEvent> {}
public class ExampleBehavior : MyBehaviour, IEventReceiver<OnExampleEvent>
{
    public void On(OnExampleEvent message)
    {
	// Handle message
    }
}
new OnExampleEvent().PostEvent();

Here's an example of how it's used in Profiler, which handles player type profiling:

public class Profiler : PersistentSingleton<Profiler>,   
    IEventReceiver<OnEnemyKilled>,  
    IEventReceiver<OnScoreChange>,  
    IEventReceiver<OnCageOpen>,  
    IEventReceiver<OnPortalEntered>  
{  
    [SerializeField] int onEnemyKilledBonus = 4;  
    [SerializeField] int onScoreChangeBonus = 1;  
    [SerializeField] int onPathChangeBonus  = 4;  
    [SerializeField] int onCageOpenBonus    = 3;  
  
    private GlobalState globalState;  
  
    void Start()  
    {  
        globalState = GlobalState.instance;  
    }  
      
    public void On(OnEnemyKilled message)  
    {  
        globalState.profiles[PlayerProfile.Killer] += onEnemyKilledBonus;  
    }  
  
    public void On(OnScoreChange message)  
    {  
        globalState.profiles[PlayerProfile.Achiever] += onScoreChangeBonus * message.scoreDelta;  
    }  
  
    public void On(OnCageOpen message)  
    {  
        globalState.profiles[PlayerProfile.Socializer] += onCageOpenBonus;  
    }  
  
    public void On(OnPortalEntered portal)  
    {  
        if (portal.kind == Portal.Kind.BonusLevelEntry)  
            globalState.profiles[PlayerProfile.Explorer] += onPathChangeBonus;  
    }  
}

Implementation

image

The event queue is maintained by an EventsManager, a singleton under DontDestroyOnLoad. Whenever an event is fired, it either puts it on the queue to be delivered the next FixedUpdate, or delivers it immediately, based on its specified delivery type. If no delivery type was specified, it falls back to the default one, configurable in the inspector.

In order to receive events, an object must be registered with the events manager via EventsManager.instance.Add(obj). To avoid doing that manually, scripts may inherit from MyBehaviour (derived from MonoBehaviour), which adds itself on Awake, and removes itself in OnDestroy.

Each BroadcastEvent inheritor has a public static list of handlers (implemented as a standard C# event). This way delivering a BroadcastEvent is simply a matter of invoking the handlers with the event object as a parameter.

Reflection is used once at startup. For each type that inherits from BroadcastEvent<TEvent>, an EventReceiverRegisterer<TEvent> is created. Each is responsible for subscribing/unsubscribing objects that implement IEventReceiver<TEvent>. When registering potential subscribers, each registerer simply checks if the object implements the correct interface and if so, updates the handlers list for the respective event type. Registering and unregistering a given object is then simply a matter of calling registerer.Add(obj) or registerer.Remove(obj) for each registerer.

Since Reflection is only used once, and interface implementation checking (via a soft cast using the as keyword) is only done when registering/unregistering subscribers, this system is very high performance.

⚠️ **GitHub.com Fallback** ⚠️