Creating a Notification Bus in O3DE

A notification bus in O3DE is like the opposite of a request bus: whereas a request bus receives events, a notification bus dispatches events.

An example of a notification bus is the InputChannelNotificationBus, which is used to receive inputs events when implementing event-driven input handling.

Requirements

A notification bus is simpler than a request bus, since it isn't responsible for actually handling events, just broadcasting them. Thus, all you really need is the interface class that defines the events that it can emit.

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <AzCore/EBus/EBus.h>
namespace TomatoFarm {

    class TomatoHarvestNotifications: public AZ::EBusTraits {
    public:
        virtual ~TomatoHarvestNotifications() = default;

        virtual void DidPlant() = 0;
        virtual int ReadyToHarvest(int amount) = 0;
    };

    using TomatoHarvestNotificationBus = AZ::EBus<TomatoHarvestNotifications>;

}; //namespace TomatoFarm

Then, when you want to broadcast an event, you can use the Broadcast or Event functions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
TomatoHarvestNotificationBus::Broadcast(
    &TomatoHarvestNotificationBus::Events::DidPlant
);

int harvested_amount = -1;
TomatoHarvestNotificationBus::BroadcastResult(
    harvested_amount,
    &TomatoHarvestNotificationBus::Events::ReadyToHarvest,
    1234
);

BroadcastResult is used when the event handler is supposed to return something. For example, ReadyToHarvest tells handlers that there are amount tomatos available for harvesting, and returns the amount that was harvested by the handlers connected to the notification bus.

Some game system will calculate that there are 1234 tomatos ready to harvest, and it will broadcast an event on the TomatoHarvestNotificationBus to inform handlers. Handlers will then handle that event by overriding ReadyToHarvest, will decide to harvest X tomatos, then return X. Finally, the game system that broadcasted that event will have access to X, to maybe decrement the harvestable_tomatos count.

What if there are multiple handlers?!

Since BroadcastResult only returns one value, what do we do when there are multiple handlers (and thus multiple results)? That's what AZ::EBusAggregateResults is for:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
AZ::EBusAggregateResults<int> harvested_amounts;
TomatoHarvestNotificationBus::BroadcastResult(
    harvested_amounts,
    &TomatoHarvestNotificationBus::Events::ReadyToHarvest,
    1234
);

//harvested_amounts.values is a AZStd::vector
for(int &amount: harvested_amounts.values){
    harvestable_tomatos -= amount;
}

Events instead of Broadcasts

Since a notification bus can have multiple listeners attached to it, the Broadcast function will send the event to all attached handlers. If you want to restrict it to a specific handler, you can use Event, which offers an addressing system:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
TomatoHarvestNotificationBus::Event(
    <ID>,
    &TomatoHarvestNotificationBus::DidPlant
);

TomatoHarvestNotificationBus::Event(
    <ID>,
    &TomatoHarvestNotificationBus::ReadyToHarvest,
    1234
);

What exactly goes into <ID> depends on what the notification bus's EBusTraits::BusIdType is. Usually, it's an EntityId, so that events can be directed at specific entities. When a handler calls BusConnect() on the notification bus, it can provide an EntityId to tell the EBus system that it wants to receive Events addressed to that EntityId.

An obvious use case for this is with Components, since Components are always tied to an Entity (and can get the EntityId by calling the member function GetEntityId())

Sources