← All posts

Creating Event-Based Architecture in Unity with Event Args

UnityScriptingEvent-based architecture
Event-Based Architecture in Unity

Event-based architecture is a widely used design pattern in software development, and it has been adopted by many game developers as well. This pattern involves the use of events to trigger actions and reactions between different components of an application. Unity, one of the most popular game engines, provides an event system that developers can use to implement event-based architecture in their projects.

In this blog post, we will explore the basics of event-based architecture in Unity and show how event args can be used to enhance the functionality of the event-based architecture. We’ll also provide a practical example to help you understand how to implement event args in Unity.

What is Event-Based Architecture?

Event-based architecture is a programming pattern that simplifies code maintenance and scalability by decoupling components of the system using events. This pattern makes it easy to add new features or remove existing ones without causing any breaking changes. In event-based architecture, when an event occurs, all subscribed components (i.e., listeners) receive a notification to perform an action. This way, components can respond to changes without knowing about other components or the system’s internal workings.

In Unity, this pattern is commonly used in UI systems, animation systems, and game mechanics that rely on player input. (For a lighter-weight take on decoupling, ScriptableObject-based events work well too — I cover them in 10 Ways to Use Scriptable Objects in Unity.) An excellent example of using an event-based architecture in Unity is creating a health system for a game character.

Health System Implementation with Event-Based Architecture

A health system is a vital component of any game, and creating one that’s flexible and easy to maintain is essential. Let’s implement a basic health system using event-based architecture in Unity. To achieve this, we’ll create a HealthSystem class that manages the character’s health, define a HealthChangedEventArgs class that holds the current health value, create a HealthUI class that updates the UI whenever the health changes, and then subscribe the HealthUI class to the HealthSystem class using the EventBus class.

The HealthSystem Class

First, we’ll create a HealthSystem class that manages the character’s health. We’ll define an event named HealthChanged that will notify all listeners whenever the health changes. We’ll also define the TakeDamage and Heal methods to update the character’s health and raise the HealthChanged event whenever the health changes.

public class HealthSystem : MonoBehaviour
{
    public event EventHandler<HealthChangedEventArgs> HealthChanged;
    public int MaxHealth { get; private set; }
    private int _currentHealth;

    public int CurrentHealth
    {
        get => _currentHealth;
        private set
        {
            if (_currentHealth != value)
            {
                _currentHealth = Mathf.Clamp(value, 0, MaxHealth);
                HealthChanged?.Invoke(this, new HealthChangedEventArgs(_currentHealth));
            }
        }
    }

    private void Start()
    {
        MaxHealth = 100;
        CurrentHealth = MaxHealth;
    }

    public void TakeDamage(int damageAmount)
    {
        CurrentHealth -= damageAmount;
    }

    public void Heal(int healAmount)
    {
        CurrentHealth += healAmount;
    }
}

As you can see, we have an event called HealthChanged that is raised whenever the character’s health changes. We also have a MaxHealth property and a CurrentHealth property that is clamped between 0 and MaxHealth. Finally, we have TakeDamage and Heal methods that decrease and increase the CurrentHealth, respectively.

The HealthChangedEventArgs Class

We also need to create a HealthChangedEventArgs class that we will use to pass the current health value to any listeners of the HealthChanged event.

public class HealthChangedEventArgs : EventArgs
{
    public int CurrentHealth { get; }

    public HealthChangedEventArgs(int currentHealth)
    {
        CurrentHealth = currentHealth;
    }
}

The HealthUI Component

Now that we have created the health system, we need to create a HealthUI component that listens to the HealthChanged event and updates the health UI on the screen.

public class HealthUI : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI _healthText;
    private HealthSystem _healthSystem;

    private void Start()
    {
        _healthSystem = FindObjectOfType<HealthSystem>();
        EventBus.Subscribe<HealthChangedEventArgs>(OnHealthChanged);
    }

    private void OnDestroy()
    {
        EventBus.Unsubscribe<HealthChangedEventArgs>(OnHealthChanged);
    }

    private void OnHealthChanged(object sender, HealthChangedEventArgs e)
    {
        _healthText.SetText($"Health: {e.CurrentHealth}");
    }
}

We use the FindObjectOfType method to find the HealthSystem object in the scene and then subscribe to the HealthChanged event using the EventBus.Subscribe method. In the OnHealthChanged method, we update the health text on the screen using the SetText method.

The OnDestroy method is used to unsubscribe from the HealthChanged event when the HealthUI component is destroyed. This is important to prevent memory leaks.

The EventBus Class

Finally, we’ll create the EventBus class that handles event subscriptions, unsubscriptions, and raising. This class is a static class that uses a dictionary to store a list of handlers for each event type. Here’s the code:

public static class EventBus
{
    private static readonly object _lock = new object();
    private static readonly Dictionary<Type, List<Delegate>> _handlers = new();

    public static void Raise<T>(object sender, T args) where T : EventArgs
    {
        Type eventType = typeof(T);
        List<Delegate> handlers = null;

        lock (_lock)
        {
            if (!_handlers.TryGetValue(eventType, out handlers))
                return;

            for (int i = 0; i < handlers.Count; i++)
            {
                var handler = handlers[i];
                if (handler.Target != null)
                    handler.DynamicInvoke(sender, args);
            }
        }
    }

    public static void Subscribe<T>(Action<object, T> handler) where T : EventArgs
    {
        Type eventType = typeof(T);
        lock (_lock)
        {
            if (!_handlers.TryGetValue(eventType, out var handlers))
            {
                handlers = new List<Delegate>();
                _handlers.Add(eventType, handlers);
            }

            handlers.Add(handler);
        }
    }

    public static void Unsubscribe<T>(Action<object, T> handler) where T : EventArgs
    {
        Type eventType = typeof(T);
        lock (_lock)
        {
            if (_handlers.TryGetValue(eventType, out var handlers))
            {
                handlers.Remove(handler);

                if (handlers.Count == 0)
                    _handlers.Remove(eventType);
            }
        }
    }
}

The Raise method raises an event by invoking all the handlers that are subscribed to that event. The Subscribe method subscribes a handler to an event and the Unsubscribe method unsubscribes a handler from an event.

Conclusion

In this post, we showed you how to implement an event-based architecture in Unity. We created a health system for a game character that uses events to communicate changes in health to other components in the game. We also created a HealthUI component that listens to the HealthChanged event and updates the health text on the screen.

Finally, we created an EventBus class that handles event subscriptions, unsubscriptions, and raising. Using an event-based architecture like this can make your game code more maintainable and scalable, as well as easier to debug.

Decoupled architecture like this pays for itself most in complex projects — multiplayer games especially, where events crossing the network boundary punish tightly coupled code.

I hope this post has been helpful to you. Happy coding!

Common questions

What is event-based architecture in Unity?

It is a pattern where components communicate by raising and listening to events instead of calling each other directly. When something happens, like a character taking damage, every subscribed listener gets notified and reacts on its own. Unity projects commonly use it for UI updates, animation triggers, and input-driven game mechanics.

How do I pass data with events in Unity?

Create a class that inherits from EventArgs and give it properties for the data you want to send, such as a CurrentHealth value. Declare the event as EventHandler of that type, then construct a new instance when you raise it. Subscribers read the values straight off the args parameter in their handler.

What is an EventBus and how does it work?

An EventBus is a static class that acts as a central hub for events. It keeps a dictionary from event type to a list of handler delegates, with Subscribe and Unsubscribe methods to manage the list and a Raise method that invokes every registered handler for that type. Components talk to the bus instead of holding references to each other.

Why do I need to unsubscribe from events in Unity?

Because a subscribed handler keeps the listening object alive and reachable even after Unity destroys it, which leaks memory and can invoke handlers on dead components. The fix is to mirror every Subscribe call with an Unsubscribe in OnDestroy.

Can I use ScriptableObjects instead of an EventBus?

Yes, ScriptableObject-based events are a lighter-weight alternative for decoupling. An event asset holding a UnityEvent covers simple cases well, while a typed EventBus with EventArgs scales better when events need to carry structured data across many systems.