Skip to main content

Quick start

Overview

The Light Event System consists of 2 modules:

  1. LightEventSystem

    The main module that provides the primary functionality, namely the Event System class and its supporting machinery. It provides:

    • C++ and Blueprint classes for the Event System functionality.
    • A game instance subsystem (ULES_EventSubsystem) with an Event System object instance available for other code to use.
    • A set of basic Event classes, one for each basic data type (like int, bool, FString, FVector, etc.)
  2. LightEventSystemTests, which has unit tests for verifying the correctness of the LightEventSystem's code.

note

Some classes come with a ULES_ or FLES_ prefix. The LES_ part is a shorthand for "Light Event System". It's a way to make the names less likely to conflict with the already existing or future Unreal Engine's names, or names from other plugins. Every class exposed to Unreal Engine's reflection system has this prefix. Regular C++ code is instead available in the namespace LES (like e.g. LES::Create<ULES_StringEvent>(...)).

tip

To use C++ code from the LightEventSystem module, you have to first add it as a private or public dependency in your project's Build.cs file, like so:

PrivateDependencyModuleNames.AddRange(new string[] { "LightEventSystem" });

How to use?

Create an Event System object

LightEventSystem module provides the ULES_EventSystem class which object's can be subscribed to. An example below shows how you can create an Event System:

MyActor.h
UCLASS()
class MYPROJECT_API AMyActor : public AActor
{
GENERATED_BODY()

public:
UPROPERTY(Transient)
TObjectPtr<ULES_EventSystem> EventSystem = nullptr;

protected:
virtual void BeginPlay() override;
};
MyActor.cpp
void AMyActor::BeginPlay()
{
Super::BeginPlay();
EventSystem = NewObject<ULES_EventSystem>(this);

// Other method to obtain a global Event System:
// ULES_EventSystem* EventSystem = ULES_EventSubsystem::GetGlobalEventSystem(this);
}

You can also use a globally available Event System object through a game instance subsystem, using GetGlobalEventSystem static method.

note

The rest of the guide will assume you use the global Event System object for simplicity's sake.

Create an Event class

MyEvent.h

UCLASS()
class MYPROJECT_API UMyEvent : public ULES_Event
{
GENERATED_BODY()

public:
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn), Category = Event)
int SomeDataRelatedToMyEvent = 0;
};

Subscribe to events

Define an event handler method in the class you want to act as an Observer:

MyActor.h
UCLASS()
class MYPROJECT_API AMyActor : public AActor
{
GENERATED_BODY()
FLES_ObserverHandle ObserverHandle;

void OnMyEvent(const UMyEvent* Event);

protected:
virtual void BeginPlay() override;

public:
virtual void BeginDestroy() override;
}
MyActor.cpp
void AMyActor::OnMyEvent(const UMyEvent* Event)
{
// Do something with the Event.
}

Subscribe to the event:

MyActor.cpp
void AMyActor::BeginPlay()
{
Super::BeginPlay();

ULES_EventSystem* EventSystem = ULES_EventSubsystem::GetGlobalEventSystem(this);
ObserverHandle = EventSystem->AddObserver<UMyEvent>(this, &AMyActor::OnMyEvent, "SomeChannel");
}

You may also use a lambda function:

ObserverHandle = EventSystem->AddObserver<UMyEvent>(this, [this](const UMyEvent* Event)
{
// Do something with the Event.
});
tip

It's safe to capture this in the lambda. The Event System will not run callbacks for garbage-collected objects.

It's a good practice to unsubscribe:

MyActor.cpp
void AMyActor::BeginDestroy()
{
Super::BeginDestroy();

ULES_EventSystem* EventSystem = ULES_EventSubsystem::GetGlobalEventSystem(this);
EventSystem->RemoveByHandle(ObserverHandle);
}
warning

The Event System doesn't keep its registered Observers alive. If an Observer is registered in the Event System and no other object has any strong references to it, it will be garbage-collected.

If, after that, an event is sent, that the now-dead Observer was listening for, the event handlers for that Observer won't be executed.

tip

After Observer registration, you should always remember to unregister it later somewhere. If you never do this, the Event System will keep observer records about objects that have already been garbage-collected. You can remove records to expired Observers using Clean method.

Send events

ULES_EventSystem* EventSystem = ULES_EventSubsystem::GetGlobalEventSystem(this);

UMyEvent* MyEvent = NewObject<UMyEvent>(this);
MyEvent->Sender = this;
MyEvent->Channel = "Important";
MyEvent->SomeDataRelatedToMyEvent = 42;

EventSystem->SendEvent(MyEvent);
tip

When an Observer is listening for an event, it will only receive events of that class. If you create an event type, UMyEvent, a subclass, UMyDerivedEvent, and register the Observer to listen for UMyEvent, it will not receive events of type UMyDerivedEvent. You'd have to register one more time to receive both types of events.

(Advanced) Extending Event System

You can customize some aspects of how the Event System handles dispatching events. To do this, you have to create a new ULES_EventSystem subclass, in which you may override a few hook methods.

MyEventSystem.h
UCLASS()
class MYPROJECT_API UMyEventSystem : public ULES_EventSystem
{
GENERATED_BODY()

public:
virtual bool BeforeSend_Implementation(ULES_Event* Event) override;
virtual bool BeforeReceive_Implementation(ULES_Event* Event, UObject* Observer) override;
virtual void AfterReceive_Implementation(ULES_Event* Event, UObject* Observer) override;
virtual void AfterSend_Implementation(ULES_Event* Event) override;
};
MyEventSystem.cpp
bool UMyEventSystem::BeforeSend_Implementation(ULES_Event* Event)
{
// Happens before the Event has been sent.
// If you return false, the Event won't be sent at all.
return true;
}

bool UMyEventSystem::BeforeReceive_Implementation(ULES_Event* Event, UObject* Observer)
{
// Happens before the Observer has received the Event.
// If you return false, the Observer won't receive the Event.
return true;
}

void UMyEventSystem::AfterReceive_Implementation(ULES_Event* Event, UObject* Observer)
{
// Happens after the Observer has received the Event.
}

void UMyEventSystem::AfterSend_Implementation(ULES_Event* Event)
{
// Happens after the Event has been sent.
}

The Event System is customizable in the same way in Blueprint as well: