A lightweight, type-safe event system for Unity and C# projects. This system provides a simple and efficient way to implement the observer pattern without the overhead of UnityEvents or C# events.
- ✨ Type-safe: Generic implementation ensures compile-time type checking
- 🚀 Lightweight: Minimal overhead with no reflection
- 🎯 Easy to use: Simple subscribe/unsubscribe API
- 🔒 Memory safe: Prevents duplicate subscriptions
- 🎮 Unity-friendly: Works seamlessly with Unity projects
- 📦 No dependencies: Pure C# implementation
- Download the
srcfolder - Copy it to your Unity project's
Scriptsfolder or your C# project
using System;
// Define your event argument class
public class PlayerHealthArgs
{
public int CurrentHealth { get; set; }
public int MaxHealth { get; set; }
}
// Create an event
public class GameManager
{
public GameEvent<PlayerHealthArgs> OnPlayerHealthChanged = new();
public void TakeDamage(int damage)
{
// Your damage logic here
// Raise the event
OnPlayerHealthChanged.Raise(new PlayerHealthArgs
{
CurrentHealth = 75,
MaxHealth = 100
});
}
}
// Subscribe to the event
public class UIHealthBar
{
private GameManager _gameManager;
void Start()
{
_gameManager = FindObjectOfType<GameManager>();
_gameManager.OnPlayerHealthChanged.Subscribe(OnHealthChanged);
}
void OnHealthChanged(PlayerHealthArgs args)
{
Debug.Log($"Health: {args.CurrentHealth}/{args.MaxHealth}");
// Update your UI here
}
void OnDestroy()
{
// Don't forget to unsubscribe!
_gameManager.OnPlayerHealthChanged.Unsubscribe(OnHealthChanged);
}
}// For simple events without data
public class GameManager
{
public GameEventNoArgs OnGameStart = new();
public GameEventNoArgs OnGameOver = new();
public void StartGame()
{
OnGameStart.Raise();
}
}
public class AudioManager
{
void Start()
{
var gameManager = FindObjectOfType<GameManager>();
gameManager.OnGameStart.Subscribe(PlayGameStartMusic);
}
void PlayGameStartMusic()
{
// Play music
}
}public class ScoreArgs
{
public int Score { get; set; }
public int Combo { get; set; }
}
public GameEvent<ScoreArgs> OnScoreChanged = new();
// Multiple systems can subscribe to the same event
uiManager.OnScoreChanged.Subscribe(UpdateScoreUI);
achievementManager.OnScoreChanged.Subscribe(CheckAchievements);
audioManager.OnScoreChanged.Subscribe(PlayScoreSound);public class EventManager : MonoBehaviour
{
public static EventManager Instance { get; private set; }
public GameEvent<PlayerHealthArgs> OnPlayerHealthChanged = new();
public GameEvent<ScoreArgs> OnScoreChanged = new();
public GameEventNoArgs OnLevelComplete = new();
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
// Usage from anywhere
EventManager.Instance.OnPlayerHealthChanged.Subscribe(HandleHealthChange);| Method | Description |
|---|---|
Subscribe(Action<T> listener) |
Subscribe a method to the event. Prevents duplicate subscriptions. |
Unsubscribe(Action<T> listener) |
Unsubscribe a method from the event. |
Raise(T args) |
Invoke all subscribed methods with the provided arguments. |
| Method | Description |
|---|---|
Subscribe(Action listener) |
Subscribe a method to the event. Prevents duplicate subscriptions. |
Unsubscribe(Action listener) |
Unsubscribe a method from the event. |
Raise() |
Invoke all subscribed methods. |
// Always unsubscribe in OnDestroy/Dispose
void OnDestroy()
{
eventManager.OnPlayerDeath.Unsubscribe(HandlePlayerDeath);
}
// Use meaningful event argument classes
public class DamageArgs
{
public int Damage { get; set; }
public GameObject Attacker { get; set; }
public DamageType Type { get; set; }
}
// Keep event handlers focused and simple
void OnPlayerDamaged(DamageArgs args)
{
UpdateHealthBar(args.Damage);
}// Don't forget to unsubscribe (causes memory leaks!)
void OnDestroy()
{
// Missing unsubscribe!
}
// Don't use complex logic in event handlers
void OnPlayerDamaged(DamageArgs args)
{
// Too much logic here!
CalculateDamage();
UpdateUI();
PlaySound();
CheckAchievements();
// Better to split this up
}
// Don't subscribe in Update or frequently called methods
void Update()
{
// ❌ Creates multiple subscriptions!
eventManager.OnScoreChanged.Subscribe(HandleScore);
}- Event invocation is O(n) where n is the number of subscribers
- Subscription/unsubscription is O(n) due to list operations
- Minimal memory overhead (one List per event)
- No boxing/unboxing or reflection
- Thread-safe: No (use locks if calling from multiple threads)
| Feature | GameEvent | UnityEvent | C# event |
|---|---|---|---|
| Type-safe | ✅ | ✅ | ✅ |
| Editor visible | ❌ | ✅ | ❌ |
| Serializable | ❌ | ✅ | ❌ |
| Performance | ⚡⚡⚡ | ⚡ | ⚡⚡⚡ |
| Easy unsubscribe | ✅ | ❌ | |
| Prevents duplicates | ✅ | ❌ | N/A |
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
If you find this useful, please consider:
- ⭐ Starring the repository
- 🐛 Reporting bugs
- 💡 Suggesting new features
Created for the Unity and C# developer community.