Quick start
Overview
The Light Event System consists of 2 modules:
-
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.)
-
LightEventSystemTests, which has unit tests for verifying the correctness of the LightEventSystem's code.
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>(...)
).
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:
- C++
- Blueprint
UCLASS()
class MYPROJECT_API AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(Transient)
TObjectPtr<ULES_EventSystem> EventSystem = nullptr;
protected:
virtual void BeginPlay() override;
};
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);
}
Mark the Event System variable as "Transient" in the Advanced section of the Details panel.
You can also use a globally available Event System object through a game instance subsystem, using GetGlobalEventSystem static method.
The rest of the guide will assume you use the global Event System object for simplicity's sake.
Create an Event class
- C++
- Blueprint
UCLASS()
class MYPROJECT_API UMyEvent : public ULES_Event
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn), Category = Event)
int SomeDataRelatedToMyEvent = 0;
};
Mark your event's variables as "Expose on Spawn" in the Details panel.
Subscribe to events
- C++
- Blueprint
Define an event handler method in the class you want to act as an Observer:
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;
}
void AMyActor::OnMyEvent(const UMyEvent* Event)
{
// Do something with the Event.
}
Subscribe to the event:
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.
});
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:
void AMyActor::BeginDestroy()
{
Super::BeginDestroy();
ULES_EventSystem* EventSystem = ULES_EventSubsystem::GetGlobalEventSystem(this);
EventSystem->RemoveByHandle(ObserverHandle);
}
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.
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
- C++
- Blueprint
ULES_EventSystem* EventSystem = ULES_EventSubsystem::GetGlobalEventSystem(this);
UMyEvent* MyEvent = NewObject<UMyEvent>(this);
MyEvent->Sender = this;
MyEvent->Channel = "Important";
MyEvent->SomeDataRelatedToMyEvent = 42;
EventSystem->SendEvent(MyEvent);
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.
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;
};
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: