> For the complete documentation index, see [llms.txt](https://asperazera.gitbook.io/ultimate-event-system/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://asperazera.gitbook.io/ultimate-event-system/cpp-guide.md).

# C++ Developer Guide

Everything you can do in Blueprints, you can do in C++. The Blueprint nodes are thin wrappers around the same event bus - the class **`UUltimateSubsystem`** (a `UGameInstanceSubsystem`).

{% hint style="info" %}
This page is for programmers integrating UES from code. If you work in Blueprints, you can safely skip it - start with [Send an Event](/ultimate-event-system/quickstart/send-event.md) instead.
{% endhint %}

***

### Step 1: Add the Module Dependency

In your game module's `*.Build.cs`, add `UltimateEventSystem` (plus the two engine modules whose types you'll touch directly):

```csharp
public class MyGame : ModuleRules
{
    public MyGame(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange(new string[]
        {
            "Core",
            "CoreUObject",
            "Engine",
            "GameplayTags",       // FGameplayTag
            "UltimateEventSystem" // the event bus
        });
    }
}
```

{% hint style="info" %}
**No `StructUtils` entry needed.** `FInstancedStruct` - the payload container - ships in the engine's `StructUtils` plugin on **UE 5.3 / 5.4** and moved into `CoreUObject` in **UE 5.5**, where the old plugin is deprecated. Either way `UltimateEventSystem` already depends on whichever your engine uses and passes it through to your module.
{% endhint %}

Then include the subsystem header where you need it:

```cpp
#include "UESSubsystem.h"   // UUltimateSubsystem - also pulls in the types below
```

`UESSubsystem.h` transitively includes `UESTypes.h` (the delegates, the binding handle, the payload wrappers, and the stats struct) along with `FInstancedStruct` itself, so a single include is usually enough - and it keeps you clear of the header move described above.

***

### Step 2: Get the Subsystem

The bus lives on the `GameInstance`, so it is reachable from anywhere with a world context. From an `AActor` or `UActorComponent` it's a one-liner:

```cpp
UUltimateSubsystem* Events = GetGameInstance()->GetSubsystem<UUltimateSubsystem>();
if (!Events)
{
    return;
}
```

{% hint style="warning" %}
Always null-check the result. The subsystem exists for the whole game session, but `GetGameInstance()` can return null very early in an object's lifetime.
{% endhint %}

***

### Step 3: Send an Event

`SendEvent` takes the **sender**, the **tag**, a **payload**, and whether to route to **parent tags**:

```cpp
void SendEvent(UObject* Sender, FGameplayTag EventTag,
               const FInstancedStruct& Payload, bool bTriggerParentSubscriptions);
```

**Signal only** (no data) - pass an empty `FInstancedStruct`:

```cpp
const FGameplayTag Tag = FGameplayTag::RequestGameplayTag(FName("Event.Player.Death"));
Events->SendEvent(this, Tag, FInstancedStruct(), /*bTriggerParentSubscriptions*/ false);
```

**With a payload** - box any `USTRUCT` with `FInstancedStruct::Make`:

```cpp
USTRUCT()
struct FScorePayload
{
    GENERATED_BODY()

    UPROPERTY()
    int32 Score = 0;

    UPROPERTY()
    FString PlayerName;
};
```

```cpp
FScorePayload Data;
Data.Score = 150;
Data.PlayerName = TEXT("Alice");

const FInstancedStruct Payload = FInstancedStruct::Make(Data);
Events->SendEvent(this, Tag, Payload, /*bTriggerParentSubscriptions*/ false);
```

***

### Step 4: Subscribe

Code-side subscribers use the native delegate **`FUltimateDelegate`**, whose handler takes three arguments - **sender, tag, payload**:

```cpp
// In your class header:
void OnScoreChanged(UObject* Sender, FGameplayTag EventTag, const FInstancedStruct& Payload);

FUltimateEventBindingHandle ScoreHandle; // keep this to cancel just this subscription
```

```cpp
// In BeginPlay (or wherever you set things up):
FUltimateDelegate Callback;
Callback.BindUObject(this, &AMyActor::OnScoreChanged);

const FGameplayTag Tag = FGameplayTag::RequestGameplayTag(FName("Event.Player.ScoreChanged"));
ScoreHandle = Events->SubscribeToEvent(Tag, Callback);
```

{% hint style="info" %}
`FUltimateDelegate` is a plain (non-dynamic) delegate, so the handler **does not** need `UFUNCTION()`.
{% endhint %}

Other subscription flavours:

| Method                                      | Purpose                                                            |
| ------------------------------------------- | ------------------------------------------------------------------ |
| `SubscribeToEvent(Tag, Callback)`           | One tag. Returns a handle.                                         |
| `SubscribeToEvents(TagContainer, Callback)` | One independent subscription per tag. Returns an array of handles. |
| `SubscribeToAllEvents(Callback)`            | Catch-all - fires for every event on the bus. Returns a handle.    |

***

### Step 5: Read the Payload

Inside the handler, pull your struct back out with `GetPtr<T>()`. It returns `nullptr` when the payload holds a different type, so it doubles as your type check:

```cpp
void AMyActor::OnScoreChanged(UObject* Sender, FGameplayTag EventTag, const FInstancedStruct& Payload)
{
    if (const FScorePayload* Data = Payload.GetPtr<FScorePayload>())
    {
        UE_LOG(LogTemp, Log, TEXT("Score = %d for %s"), Data->Score, *Data->PlayerName);
    }
}
```

{% hint style="info" %}
**Interop with Blueprints:** when a Blueprint sends a *primitive* (Integer, Float, String, …), UES boxes it into a wrapper struct. To read a BP-sent integer in C++, unpack the matching wrapper:

```cpp
if (const FUltimateIntPayload* AsInt = Payload.GetPtr<FUltimateIntPayload>())
{
    const int32 Value = AsInt->Value;
}
```

Custom `USTRUCT`s are sent as-is; object references arrive inside `FUltimateObjectPayload`.
{% endhint %}

***

### Step 6: Unsubscribe

Pick the method that matches how you subscribed:

```cpp
// Cancel exactly one subscription, by its handle:
Events->Unsubscribe(ScoreHandle);

// Cancel a batch of handles (e.g. the result of SubscribeToEvents):
Events->UnsubscribeMany(Handles);

// Drop this object's subscriptions on specific tags:
Events->UnsubscribeFromEvents(this, FGameplayTagContainer(Tag));

// Remove everything this object subscribed to (recommended on teardown):
Events->UnsubscribeFromAllEvents(this, /*bIncludeIndividualSubscriptions*/ true);
```

A clean `EndPlay` looks like this:

```cpp
void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    if (UGameInstance* GameInstance = GetGameInstance())
    {
        if (UUltimateSubsystem* Events = GameInstance->GetSubsystem<UUltimateSubsystem>())
        {
            Events->UnsubscribeFromAllEvents(this, true);
        }
    }

    Super::EndPlay(EndPlayReason);
}
```

{% hint style="info" %}
Explicit cleanup is good hygiene, but not strictly required for destroyed objects: UES tracks subscribers weakly, skips dead ones during delivery, and prunes them on a timer. See [Unsubscribing & Lifecycle](/ultimate-event-system/advanced/unsubscribing.md).
{% endhint %}

***

### Introspection & Stats

```cpp
// Runtime metrics (the same data as the Get Event System Stats node):
const FUltimateEventSystemStats Stats = Events->GetEventSystemStats();
// Stats.ActiveSubscriptionsCount, .UniqueTagsCount, .TotalEventsSent, .StaleSubscriptionsCleaned

// Which live objects would receive an event sent on these tags right now:
TArray<UObject*> Subscribers;
Events->GetEventSubscribers(FGameplayTagContainer(Tag), /*bIncludeParents*/ true, Subscribers);
```

***

{% hint style="warning" %}
**Game thread only.** Every bus operation - subscribing, sending, unsubscribing, even reading stats - must run on the game thread. Calls from a worker thread are rejected and logged (in **all** build configurations, including Shipping), so marshal onto the game thread first if you need to.
{% endhint %}
