Skip to content

An Unreal Engine plugin that provides a subsystem for sending non replicated events with arbitrary structs as payloads

License

Notifications You must be signed in to change notification settings

strayTrain/SimpleEventSubsystemPlugin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

48 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Simple Event Plugin

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.

API Summary

Sending an event

image


Events are identified using two gameplay tags:

  • EventTag e.g Events.UI.ButtonClicked
  • DomainTag e.g EventDomains.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 the MakeInstancedStruct 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

image

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

image


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.


image

Utility Nodes

WaitForSimpleEvent: This async node allows you to wait for an event and respond to it in the same place.

image

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


image

Requirements

  • 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.

Installation Steps

  1. 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 cloned SimpleEventSubsystemPlugin in C:\Projects\SimpleEventTest\Plugins)
  2. Rebuild your project.
  3. Enable the plugin in your Unreal Engine project by navigating to Edit > Plugins and searching for "SimpleEventPlugin". (it should be enabled by default)

C++ Examples

Sending Events

#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);
  }
}

Listening for Events

#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());
}

Unsubscribing from Events

#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);
    }
}

License

This project is licensed under the MIT License. See the LICENSE file for details.


Contributions

Feel free to submit a pull request or file an issue on GitHub. Contributions are always welcome!

About

An Unreal Engine plugin that provides a subsystem for sending non replicated events with arbitrary structs as payloads

Topics

Resources

License

Stars

Watchers

Forks