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/send events.
Sending an event
Events are identified using two gameplay tags:
EventTag
e.gEvents.UI.ButtonClicked
DomainTag
e.gEventDomains.UI.PauseMenu
Why use two tags instead of one?
Using a domain tag can help cut down on repetetive 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.
You can use any struct as a payload to provide extra context to the event with the MakeInstancedStruct
node.
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.
There are some additional arguments that are hidden by default in the ListenForEvent
node:
RequiredPayloadType
: The event won't trigger unless a payload exists and it is of this specific struct type. If left empty the event will accept any payload. 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.
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.
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 SimpleEventPlugin 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);
}
}
#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")));
EventSubsystem->ListenForEvent(Listener, EventTags, DomainTags, FSimpleEventDelegate::CreateUObject(Listener, &YourClass::YourCallbackFunction));
}
}
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)
{
if (USimpleEventSubsystem* EventSubsystem = World->GetGameInstance()->GetSubsystem<USimpleEventSubsystem>())
{
// Unsubscribe a specific delegate
EventSubsystem->StopListeningForEventDelegate(Listener, FSimpleEventDelegate::CreateUObject(Listener, &YourClass::YourCallbackFunction));
// Unsubscribe filtered by tags
FGameplayTagContainer EventTags;
EventTags.AddTag(FGameplayTag::RequestGameplayTag(TEXT("Game.PlayerDied")));
FGameplayTagContainer DomainTags;
DomainTags.AddTag(FGameplayTag::RequestGameplayTag(TEXT("Domains.Game")));
EventSubsystem->StopListeningForEventsFiltered(Listener, EventTags, DomainTags);
}
}
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!