# How to Subscribe and Unsubscribe to GameEvent in TEngine: A Complete Guide

> Master GameEvent subscription and unsubscription in TEngine. Learn to use AddEventListener and RemoveEventListener correctly to prevent memory leaks and ensure clean event handling. Explore game event management best practices ...

- Repository: [ALEX/tengine](https://github.com/alex-rachel/tengine)
- Tags: how-to-guide
- Published: 2026-02-24

---

**Use `GameEvent.AddEventListener()` with an event ID and matching delegate to subscribe, and always call `GameEvent.RemoveEventListener()` with the exact same signature in `OnDestroy` to prevent memory leaks; for object-scoped events, use `MemoryPool.Acquire<GameEventMgr>()` which automatically cleans up when released.**

TEngine is a Unity game framework that provides a robust event system for decoupled communication between game components. Understanding how to properly subscribe and unsubscribe to GameEvent in TEngine is essential for preventing memory leaks and ensuring clean architecture. This guide covers both global event subscriptions and local object-scoped events using the actual implementation from the alex-rachel/tengine repository.

## Understanding TEngine's Event Architecture

TEngine provides two distinct event subscription models. The **global event system** uses the static `GameEvent` class for application-wide communication, storing listeners in a `Dictionary<int, List<Delegate>>` structure. The **local event system** uses `GameEventMgr` instances for object-scoped events that automatically clean up when the manager is released to the memory pool.

## Subscribing and Unsubscribing to Global GameEvents

Global events in TEngine are managed through the static API in [`UnityProject/Assets/TEngine/Runtime/Core/GameEvent/GameEvent.cs`](https://github.com/alex-rachel/tengine/blob/main/UnityProject/Assets/TEngine/Runtime/Core/GameEvent/GameEvent.cs).

### Basic Subscription Without Parameters

Subscribe to simple events using the integer ID overload:

```csharp
// In Awake or Start
GameEvent.AddEventListener(EventDefined.OnGameStart, OnGameStart);

// Handler method
private void OnGameStart()
{
    Debug.Log("Game started!");
}

// In OnDestroy - MUST match signature exactly
GameEvent.RemoveEventListener(EventDefined.OnGameStart, OnGameStart);

```

### Subscribing to Events with Parameters

Use generic overloads to specify argument types:

```csharp
// Subscribe with one parameter
GameEvent.AddEventListener<int>(EventDefined.OnScoreChanged, OnScoreChanged);

private void OnScoreChanged(int newScore)
{
    Debug.Log($"Score updated: {newScore}");
}

// Unsubscribe with exact same generic signature
GameEvent.RemoveEventListener<int>(EventDefined.OnScoreChanged, OnScoreChanged);

```

### String-Based Event Registration

TEngine supports string-based event names as an alternative to integer constants:

```csharp
GameEvent.AddEventListener("Player.Die", OnPlayerDie);
GameEvent.RemoveEventListener("Player.Die", OnPlayerDie);

private void OnPlayerDie()
{
    Debug.Log("Player has died.");
}

```

### Critical Unsubscription Requirements

The event manager stores delegates in a `Dictionary<int, List<Delegate>>`. **The removal signature must exactly match the registration signature**, including generic type parameters. Using `Action` for registration but `Action<int>` for removal will fail silently, leaving the listener active and causing memory leaks.

## Using Local GameEventMgr for Object-Scoped Events

For events that should not outlive their owning object, use `GameEventMgr` from [`UnityProject/Assets/TEngine/Runtime/Core/GameEvent/GameEventMgr.cs`](https://github.com/alex-rachel/tengine/blob/main/UnityProject/Assets/TEngine/Runtime/Core/GameEvent/GameEventMgr.cs).

### Acquiring and Configuring GameEventMgr

```csharp
public class PlayerController : MonoBehaviour
{
    private GameEventMgr _localEventMgr;
    
    private void Awake()
    {
        // Acquire from memory pool
        _localEventMgr = MemoryPool.Acquire<GameEventMgr>();
        
        // Subscribe to local events
        _localEventMgr.AddEventListener(PlayerEventDefined.OnHpChange, OnHpChange);
    }
    
    private void OnHpChange(int hp)
    {
        Debug.Log($"Player HP: {hp}");
    }
}

```

### Automatic Cleanup Best Practices

Release the manager in `OnDestroy` (or `OnDisable` for UI components) to automatically unsubscribe all local listeners:

```csharp
private void OnDestroy()
{
    MemoryPool.Release(_localEventMgr);
}

```

This pattern prevents memory leaks without requiring manual `RemoveEventListener` calls for each subscription.

## Initializing the Event System

Before using any events, initialize the helper class in your game's entry point:

```csharp
GameEventHelper.Init();   // Generates wrappers for generated event interfaces

```

This step is required for the source-generated event interfaces to function correctly.

## Common Pitfalls and How to Avoid Them

| Pitfall | How to avoid |
|--------|--------------|
| Using a different delegate type for removal (e.g., `Action` vs `Action<int>`) | Keep the exact method signature; copy‑paste the registration line when writing the removal line. |
| Forgetting to unsubscribe in `OnDestroy`/`OnDisable` | Add the unsubscription logic in the same lifecycle method that created the listener, or use a local `GameEventMgr` that is released automatically. |
| Mixing string and int IDs for the same logical event | Choose a single convention (either all string‑based or all int‑based) and stick to it throughout the project. |
| Duplicate subscriptions causing multiple callbacks | Check if already subscribed before adding, or ensure initialization logic runs only once. |

## Summary

- **Global events** use the static `GameEvent` class with `AddEventListener` and `RemoveEventListener` methods, storing delegates in a `Dictionary<int, List<Delegate>>`.
- **Local events** use `GameEventMgr` instances acquired via `MemoryPool.Acquire<GameEventMgr>()` and released in `OnDestroy` for automatic cleanup.
- Always match delegate signatures exactly when unsubscribing to prevent memory leaks.
- Call `GameEventHelper.Init()` at game startup before using any events.

## Frequently Asked Questions

### What happens if I forget to call RemoveEventListener in TEngine?

If you forget to unsubscribe from a global GameEvent, the delegate remains in the internal `Dictionary<int, List<Delegate>>` even after your object is destroyed. This causes a memory leak because the event manager holds a reference to your method, preventing garbage collection. Always unsubscribe in `OnDestroy` or use `GameEventMgr` with automatic cleanup.

### Can I use both int IDs and string names for the same event in TEngine?

While TEngine supports both `int` IDs and `string` names in `GameEvent.AddEventListener`, mixing conventions for the same logical event is not recommended. The event system treats these as separate keys in the dictionary, so subscribing with an int ID and firing with a string name (or vice versa) will not trigger your handler. Choose one convention per project and stick to it.

### How do I pass multiple parameters through TEngine's GameEvent?

Use the generic overloads of `AddEventListener` to specify up to three type arguments. For example, use `GameEvent.AddEventListener<int, string>(EventDefined.OnDataUpdate, Handler)` where the handler signature is `void Handler(int arg1, string arg2)`. The generic type parameters must match exactly between subscription, the handler, and unsubscription.

### What is the difference between GameEvent and GameEventMgr in TEngine?

`GameEvent` is a static class providing global event functionality where listeners persist until manually removed, making it suitable for game-wide systems like game state changes. `GameEventMgr` is an instance-based manager acquired from `MemoryPool` that maintains its own private dispatcher; when you release the manager via `MemoryPool.Release()`, all its listeners are automatically unsubscribed, making it ideal for MonoBehaviour components that need lifecycle-bound events.