> 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/best-practices.md).

# Best Practices

A few habits that keep an event-driven project clean, predictable, and fast.

***

### 1. Name Tags with a Clear Hierarchy

Use a consistent dot-separated taxonomy so tags stay organized and parent routing is useful:

```
Domain . Category . Action
```

**Good:**

* `Event.Player.HealthChanged`
* `UI.HUD.InventoryOpened`
* `Weapon.Rifle.Fired`
* `Quest.Chapter1.ObjectiveCompleted`

**Avoid:**

* Flat names with no hierarchy (`HealthEvent`).
* Vague names (`Update`, `Event1`).

Register tags you rely on in **Project Settings → Gameplay Tags** - it enables autocomplete and is **required** for [parent-tag routing](/ultimate-event-system/advanced/tag-hierarchy.md).

***

### 2. Prefer a Struct for Payloads That Carry Meaning

* **Group related fields into one struct** (e.g. `S_DamageInfo` with amount, instigator, hit location) rather than firing several parallel events.
* **Reach for a struct even when you start with a single field.** Payloads are matched by **struct type, not by field list**, so a struct is future-proof: add a field to it six months later and **every existing subscriber keeps working** - their `Get Payload → Break` simply gains a new pin they can ignore, with no graph edits. Only *adding* fields is safe, though - renaming or removing one still breaks readers.
* **A raw primitive is convenient but rigid.** Wiring a bare `Integer` / `Float` / `Object` into the Payload pin is perfect for prototyping or a genuinely atomic, unchanging signal. But the day you need a second value, converting that event to a struct is a **breaking change**: every subscriber's `Get Payload` (typed to the primitive) stops matching and must be re-wired.
* On the receiving side, **branch on `Success`** from **Get Payload** if an event might arrive empty or with a different type.

***

### 3. Always Unsubscribe on Teardown

Even though destroyed objects are cleaned up automatically, call **Unsubscribe** (with *Unsubscribe from All Events*) on **End Play** / **Destroyed**. It frees the registry slot immediately and keeps debug dumps tidy.

```
[Event End Play] ──► [Unsubscribe]   (Unsubscribe from All Events = True)
```

***

### 4. Use Parent Routing Deliberately

Turn on **Trigger Parent Tags** only when a parent subscriber actually exists (a central logger, an analytics manager, a grouped UI handler). If nothing listens to the parent, leave it `False` to keep routing minimal.

***

### 5. Keep Handlers Light

An event callback should do quick work - update state, toggle a flag, kick off an async task. Avoid heavy loops or expensive allocations directly inside a handler, especially for high-frequency events.

***

### 6. Turn Off Verbose Logging for Shipping

**Log All Events** and especially **Log Subscriber Details** are great while developing, but they scan subscription tables. Disable them in shipping builds for zero overhead.
