/UnityEventBus

⚙ Generic Unity event system (event bus pattern)

Primary LanguageC#MIT LicenseMIT

Description

Designed to be easy to use, extendable and optimized where it's possible.

An EventBus is a mechanism that allows different components to communicate with each other without knowing about each other. A component can send an Event to the EventBus without knowing who will pick it up or how many others will pick it up.

Features

  • interface based
  • listeners priority sorting
  • ability to send functions and messages
  • local buses creation with the ability to subscribe as a listener
  • timeline compatibility
  • filtered messaging
  • expandability

Minimal usage example

// subscriber class definition
public class SampleListener : Subscriber,
                              IListener<GlobalEvent>,          // react on GlobalEvent(Enum) messages
                              IListener<IEvent<GlobalEvent>>,  // react on IEvent<GlobalEvent> messages
                              IDamageTaker,
                              IHandle<IDamageTaker>   	       // provide IDamageTaker interface for invokation
// send enum event to the GlobalBus
GlobalBus.Send(GlobalEvent.Activate);

// send IEvent<GlobalEvent> with custom data
GlobalBus.SendEvent(GlobalEvent.Activate, 1f);

// send action to the GlobalBus
GlobalBus.SendAction<IDamageTaker>(damageTaker => damageTaker.TakeDamage(1));

// send with filtration
GlobalBus.Send(GlobalEvent.Activate, sub => sub is Unit);
// none MonoBehaviour subscriber
public class SampleListenerClass : ISubscriberOptions,  // optional priority and name options
                                   IListener<GlobalEvent>,
                                   IDamageTaker,
                                   IHandle<IDamageTaker>
{
    public void Subscribe() => GlobalBus.Subscribe(this);
    public void UnSubscribe() => GlobalBus.UnSubscribe(this);

Installation

Through Unity Package Manager git URL: https://github.com/NullTale/UnityEventBus.git

PackageManager

Or copy-paste somewhere inside your project Assets folder.

Table of Content

Event Listener

On OnEnable event Listener will connect to the desired bus and will start receiving messages from it.

using UnityEngine;
using UnityEventBus;

// same as SimpleListener : Subscriber, IListener<string>
public class SimpleListener : Listener<string>
{
    public override void React(in string e)
    {
        Debug.Log(e);
    }
}

In the unity editor, you can set up the behavior in detail. Such as subscription targets and priority.

Note: Lower priority triggers first, highest last, equal invokes in order of subscription

Listener

To create your custom listener you need to implement at least one IListener<> interface and subscribe to the desired bus.

Note: Listener can have any number of IListener<> interfaces.

using UnityEngine;
using UnityEventBus;

public class SimpleListener : MonoBehaviour, IListener<string>
{
    private void Start()
    {
        // somewhere in code...
        // send string event to the global bus
        GlobalBus.Send<string>("String event");
    }

    // subscribe to the global bus
    private void OnEnable()
    {
        GlobalBus.Subscribe(this);
    }

    // unsubscribe from the global bus
    private void OnDisable()
    {
        GlobalBus.UnSubscribe(this);
    }

    // react to an event
    public void React(in string e)
    {
        Debug.Log(e);
    }
}

You can also implement ISubscriberOptions interface to setup debug name and listener priority.

public class SimpleListener : MonoBehaviour, IListener<string>, ISubscriberOptions
{
    public string Name     => name;
    public int    Priority => 1;

Event Bus

To create a local bus, you need to derive it from the EventBusBase class

Note: Event bus can be subscribed to the other buses like a listener, using Subscribe and UnSubscribe methods.

using UnityEngine;
using UnityEventBus;

public class Unit : EventBusBase
{
    private void Start()
    {
        // send event to this
        this.Send("String event");
    }
}

You can also derive it from the EventBus class to configure auto-subscription and priority.

Note: If EventBus implements any Subscribers interfaces, they will be automatically subscribed to it.

public class Unit : EventBus

Bus

Extentions

Action

Sometimes it can be more convenient to look at listeners as a set of interfaces, the SendAction method extension is used for this. For an object to be an action target it must execute an interface IHandle<> with the interface type it provides.

public class Unit : EventBus
{
    [ContextMenu("Heal Action")]
    public void Heal()
    {
        // send invoke heal action on the IUnitHP interface
        this.SendAction<IUnitHP>(hp => hp.Heal(1));
    }
}
public interface IUnitHP
{
    void Heal(int val);
}

// implement IHandle<IUnitHP> interface to be an action target
public class UnitHP : Subscriber, 
                      IUnitHP,
                      IHandle<IUnitHP>
{
    public int HP = 2;

    public void Heal(int val)
    {
        HP += val;
    }
}

Event

Event is a message that contains keys and optional data. To send an Event, the SendEvent extension method is used. To receive events must be implemented IListener<IEvent<TEventKey>> interface, where TEventKey is a key of events, which listener wants to react.

using UnityEngine;
using UnityEventBus;

// unit derived from the EventBus class, he is receives events and propagate them to subscribers
public class Unit : EventBusBase
{
    private void Start()
    {
        // send creation event without data
        this.SendEvent(UnitEvent.Created);
    }

    [ContextMenu("Damage")]
    public void DamageSelf()
    {
        // send event with int data
        this.SendEvent(UnitEvent.Damage, 1);
    }

    [ContextMenu("Heal")]
    public void HealSelf()
    {
        this.SendEvent(UnitEvent.Heal, 1);
    }
}

// unit event keys
public enum UnitEvent
{
    Created,
    Damage,
    Heal
}
using UnityEngine;
using UnityEventBus;

// OnEnable will subscribe to the unit and start to listen messages
// same as UnitHP : EventListener<UnitEvent>
public class UnitHP : Subscriber,
                      IListener<IEvent<UnitEvent>>
{
    public int HP = 2;

    // reacts to UnitEvents
    public override void React(in IEvent<UnitEvent> e)
    {
        switch (e.Key)
        {
            // event with damage or heal key always containts int data
            case UnitEvent.Damage:
                HP -= e.GetData<int>();
                break;
            case UnitEvent.Heal:
                HP += e.GetData<int>();
                break;

            case UnitEvent.Created:
                break;
        }
    }
}

UnitUsage

Also multiple data can be sent through an event.

// send multiple data 
SendEvent(UnitEvent.Created, 1, 0.2f, this);
// get multiple data 
var (n, f, unit) = e.GetData<int, float, Unit>();

// or
if (e.TryGetData(out int n, out float f, out Unit unit))
{
	// do something with data
}

Signals

The small extension that allow you to use Timeline.SignalAsset as messages in order to.

React to signals.

Menu

Send signals from the director,

Director

or through script or MonoBehaviour.

SignalEmitter

Request

Request is needed to get permission for something from another subscriber. Request works just like Event, contains key and optional data, but it can be either approved or ignored and he will propagate until first approval. It is in fact a usable event with the only difference that you can get the result of execution. The SendRequest method extension is used to send a Request.

public class Unit : EventBus
{
    [ContextMenu("HealRequest ")]
    public void HealRequest()
    {
        if (this.SendRequest(UnitEvent.Heal, 1))
        {
            // request was approved
            // do something, play animation or implement logic
        }
    }
}
public class UnitHP : Subscriber,
                      IListener<IRequest<UnitEvent>>
{
    public int HPMax = 2;
    public int HP = 2;

    public void React(in IRequest<UnitEvent> e)
    {
        switch (e.Key)
        {
            case UnitEvent.Heal:
            {
                // if can heal, approve heal request
                var heal = e.GetData<int>();
                if (heal > 0 && HP < HPMax)
                {
                    HP += heal;
                    e.Approve();
                }
            } break;
        }
    }
}