Simple Event Plugin is a lightweight Unreal Engine plugin that provides a subsystem to send and listen for events.
Events can come with a payload that can be any UStruct you create in C++ or Blueprints (via InstancedStruct).
Anything that has access to the GameInstance (e.g Widgets, Pawns, PlayerControllers) can listen for and send events.
Sending an event
Events are identified using two gameplay tags:
EventTag
e.gEvents.UI.ButtonClicked
DomainTag
e.gEventDomains.UI.PauseMenu
Events also have optional parameters:
Payload
: You can use any struct as a payload to provide extra context to the event by passing it through theMakeInstancedStruct
node.Sender
: This is an Actor input used to identify who sent the event. Later, when listening for an event you can filter by senders.
Why use two tags instead of one?
Using a domain tag can help cut down on repetitive tag names.
For example, imagine we have a manager object that wants to know when any UI button gets clicked.
Using a DomainTag
we can use the following to identify the events of interest
EventTag = UI.ButtonClicked
DomainTags = [EventDomains.MainMenu, EventDomains.OptionsMenu, EventDomains.PauseMenu]
Without a DomainTag
we would need to listen for:
EventTags = [UI.MainMenu.ButtonClicked, UI.OptionsMenu.ButtonClicked, UI.PauseMenu.ButtonClicked]
Without a DomainTag
we have 3 "ButtonClicked" tags and this looks messy in my opinion as we start to add more tags during development.
Listening for an event
You can listen for multiple events and domains at the same time.
If you leave EventFilter
or DomainFilter
empty then the delegate will be triggered for all events/domains.
OnlyTriggerOnce
will deregister the event listening actor after receving the event once.
There are some additional arguments that are hidden by default in the ListenForEvent
node:
PayloadFilter
: The event won't trigger unless a payload exists and it is one of the specified struct types. If left empty the event will accept any payload. Default value is empty.SenderFilter
: The event won't trigger unless it was sent by one of the specified actors. If left empty the event will accept events from any actor. Default behaviour is empty.OnlyMatchExactEvent
&OnlyMatchExactDomain
: if set to true, "A.B" will only match "A.B" and won't match "A.B.C" tags. By default they are set to only match tags exactly.
The ListenForEvent
node returns an ID representing the subscription which you can store and later reference to stop listening for this event.
Receiving an event
Use the GetInstancedStructValue
node to cast to the type you expect.
The output is initially a wildcard and you break your expected struct to cast the output type.
Stop listening for an event
There are three ways to stop listening for events:
StopListeningForAllEvents
: Remove all event subscriptions from a specified actor.
StopListeningForEventsByFilter
: Stop listening for a subset of events on a specified actor.
StopListeningForEventSubscriptionByID
: Stops listening for a specific event subscription. The event subscription ID is obtained from the output of the ListenForEvent
node.
Utility Nodes
WaitForSimpleEvent
: This async node allows you to wait for an event and respond to it in the same place.
A note on replication
The SendEvent
function is not replicated i.e calling SendEvent
on the client won't trigger a listener on the server and vice versa.
InstancedStruct
, which is the type of Payload
in SendEvent
, can be replicated though! So you can pass the payload though an RPC and the underlying wrapped struct will also replicate. e.g The server calls a multicast event which calls SendEvent
on all connected clients
- Unreal Engine 5.2* or higher.
*instanced structs were introduced in 5.0 with the StructUtils plugin and got replication support around 5.2. Thus, this plugin may work with UE < 5.2 but expect issues with replication.
- Download or clone the SimpleEventPlugin folder from this repo into your Unreal Engine project under your project's Plugins folder, create the Plugins folder if it doesn't exist. (e.g. If your project folder is
C:\Projects\SimpleEventTest
then place the clonedSimpleEventSubsystemPlugin
inC:\Projects\SimpleEventTest\Plugins
) - Rebuild your project.
- Enable the plugin in your Unreal Engine project by navigating to Edit > Plugins and searching for "SimpleEventPlugin". (it should be enabled by default)
#include "SimpleEventSubsystem.h"
#include "GameplayTagContainer.h"
#include "InstancedStruct.h"
void YourFunctionToSendEvent(UWorld* World)
{
if (USimpleEventSubsystem* EventSubsystem = World->GetGameInstance()->GetSubsystem<USimpleEventSubsystem>())
{
FGameplayTag EventTag = FGameplayTag::RequestGameplayTag(TEXT("Game.PlayerDied"));
FGameplayTag DomainTag = FGameplayTag::RequestGameplayTag(TEXT("Domains.Game"));
FInstancedStruct Payload = FInstancedStruct::Make(FVector::UpVector);
EventSubsystem->SendEvent(EventTag, DomainTag, Payload, this);
}
}
#include "SimpleEventSubsystem.h"
#include "GameplayTagContainer.h"
void YourFunctionToListenForEvent(UObject* Listener, UWorld* World)
{
if (USimpleEventSubsystem* EventSubsystem = World->GetGameInstance()->GetSubsystem<USimpleEventSubsystem>())
{
FGameplayTagContainer EventTags;
EventTags.AddTag(FGameplayTag::RequestGameplayTag(TEXT("Game.PlayerDied")));
FGameplayTagContainer DomainTags;
DomainTags.AddTag(FGameplayTag::RequestGameplayTag(TEXT("Domains.Game")));
FSimpleEventDelegate EventDelegate;
EventDelegate.BindDynamic(this, &YourClass::YourCallbackFunction);
TArray<UScriptStruct*> PayloadFilter;
// Note: StaticStruct is only available for structs defined with the USTRUCT() macro
PayloadFilter.Add(FYourCustomStruct::StaticStruct());
TArray<AActor*> SenderFilter;
EventSubsystem->ListenForEvent(Listener, false, EventTags, DomainTags, EventDelegate, PayloadFilter, SenderFilter);
}
}
void YourClass::YourCallbackFunction(FGameplayTag EventTag, FGameplayTag Domain, FInstancedStruct Payload)
{
// Test if an instanced struct is a vector
if (const FVector* TestVector = Payload.GetPtr<FVector>())
{
// Do something with the payload vector
}
UE_LOG(LogTemp, Log, TEXT("Event received: %s in domain: %s"), *EventTag.ToString(), *Domain.ToString());
}
#include "SimpleEventSubsystem.h"
void YourFunctionToUnsubscribe(UObject* Listener, UWorld* World, FGuid EventSubscriptionID)
{
if (USimpleEventSubsystem* EventSubsystem = World->GetGameInstance()->GetSubsystem<USimpleEventSubsystem>())
{
EventSubsystem->StopListeningForAllEvents(Listener);
EventSubsystem->StopListeningForEventSubscriptionByID(Listener, EventSubscriptionID);
FGameplayTagContainer EventFilter;
FGameplayTagContainer DomainFilter;
EventSubsystem->StopListeningForEventsByFilter(Listener, EventFilter, DomainFilter);
}
}
This project is licensed under the MIT License. See the LICENSE file for details.
Feel free to submit a pull request or file an issue on GitHub. Contributions are always welcome!