Mastering Events & Delegates in C# Unity Game Development

Events and delegates are key ideas in C# Unity game development that allow communication and interaction between various components of your game. These concepts, which are based on C# programming, allow you to build a flexible and modular system essential for developing experiences that can be experienced in real time. In this article we will look at how events and delegates are involved in the development of Unity games.

What are Delegates?

Delegates enable you to write scripts with robust and complicated behaviors. A delegate is basically a container for a function that can be passed around or used a similar way that a variable can. Similar to variables, the delegates can have values allocated to them and they could be modified at runtime. The difference is that while variables contain data, delegates contain functions.

Syntax and declaration of delegates

Let’s consider a simple scenario where a player character can take damage, and we want different game elements to respond to this event independently. Using a delegate, you can create a callback mechanism, allowing various elements to react independently when the player takes damage.

using UnityEngine;

public class Player : MonoBehaviour
{
    // Define a delegate type for the damage event
    public delegate void DamageEventHandler(int damageAmount);

    // Declare a delegate instance
    public DamageEventHandler OnPlayerDamaged;


    // Start is called before the first frame update
    void Start()
    {
        // Instantiate the delegate
        OnPlayerDamaged = TakeDamage;

        // Invoke the delegate
        OnPlayerDamaged (10);
    }

    // Simulate the player taking damage
    public void TakeDamage(int damageAmount)
    {
        Debug.Log($"Enemy reacts to player damage: {damageAmount} damage taken!");
        // Additional logic can be added here...
    }
}
Explanation:
1. Define a Delegate Type:

To define a new delegate type, use the delegate keyword. This will set the signature which must be followed by any method assigned to this delegate.

public delegate void DamageEventHandler(int damageAmount);
2. Declare a Delegate Instance:

Declare an instance of the delegate type. This is essentially creating a variable that can hold references to methods that match the delegate’s signature.

public DamageEventHandler OnPlayerDamaged;
3. Instantiate the Delegate:

Assign a method to the delegate instance. The method must have the same signature as the delegate.

OnPlayerDamaged = TakeDamage;
4. Invoke the Delegate:

Call the delegate as if it were a method. This invokes all the methods that are currently assigned to the delegate.

OnPlayerDamaged (10);
5. Method that Matches the Delegate Signature:

Create a method that matches the delegate’s signature. In this case, it takes an integer parameter.

 public void TakeDamage(int damageAmount)
    {
        Debug.Log($"Enemy reacts to player damage: {damageAmount} damage taken!");
        // Additional logic can be added here...
    }

Multicasting with Unity Delegates

Multicasting in Unity Delegates refers to the ability of a delegate to hold references to multiple methods and invoke all of them when the delegate is called. This feature allows you to create a list of methods that should respond to a particular event, and the delegate will execute each of these methods in the order they were added. Multicasting is particularly useful when multiple components or systems need to react to the same event without explicitly knowing about each other.

Let’s have a look at the concept of multicasting and use an actual example:

using UnityEngine;

public class EventManager : MonoBehaviour
{
    // Define a delegate type
    public delegate void GameEventDelegate(string eventName);

    // Declare a multicast delegate instance
    public GameEventDelegate onGameEvent;

    // Start is called before the first frame update
    void Start()
    {
        // Subscribe multiple methods to the multicast delegate
        onGameEvent += LogEventToConsole;
        onGameEvent += PlayParticleEffect;
        onGameEvent += UpdateScore;

        
       // Invoke the multicast delegate

       if(onGameEvent!= null)
       {
           onGameEvent("PlayerDeath");
       }

    }


    // Method to log the event to the console
    void LogEventToConsole(string eventName)
    {
        Debug.Log($"Event logged: {eventName}");
    }

    // Method to play a particle effect
    void PlayParticleEffect(string eventName)
    {
        Debug.Log($"Particle effect triggered for: {eventName}");
        // Additional logic for playing particle effect...
    }

    // Method to update the player's score
    void UpdateScore(string eventName)
    {
        Debug.Log($"Score updated due to event: {eventName}");
        // Additional logic for updating the score...
    }
}
Explanation:
1. Declare a Multicast Delegate:

Define a delegate type that represents the signature of methods that can handle game events.

public delegate void GameEventDelegate(string eventName);
2. Declare a Multicast Delegate Instance:

Declare an instance of the multicast delegate.

public GameEventDelegate onGameEvent;
3. Subscribe Methods to the Multicast Delegate:

Subscribe multiple methods to the multicast delegate using the += operator.

onGameEvent += LogEventToConsole;
onGameEvent += PlayParticleEffect;
onGameEvent += UpdateScore;
4. Invoke the Multicast Delegate:

Call the multicast delegate just like a singlecast delegate.

if(onGameEvent!= null)
{
     onGameEvent("PlayerDeath");
}
5. Methods that Match the Delegate Signature:

Create methods that match the delegate’s signature. These methods will be invoked when the game event is triggered.

void LogEventToConsole(string eventName)
{
    Debug.Log($"Event logged: {eventName}");
}

void PlayParticleEffect(string eventName)
{
    Debug.Log($"Particle effect triggered for: {eventName}");
    // Additional logic for playing particle effect...
}

void UpdateScore(string eventName)
{
    Debug.Log($"Score updated due to event: {eventName}");
    // Additional logic for updating the score...
}

What are Events?

Events are special type of delegates that are useful for when you want to notify other classes that something has happened. Events are commonly used to notify other scripts or game components when a specific action or state change occurs.

Components of an Event

Let’s consider a scenario where a player takes damage, and we want different elements in the game to respond to this event:

using UnityEngine;

public class Player : MonoBehaviour
{
    // Define the damage event using Unity's built-in event system
    public delegate void DamageEventHandler(int damageAmount);
    public static event DamageEventHandler OnPlayerDamaged;

    // Simulate the player taking damage
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            TakeDamage(10);
        }
    }

    // Method to simulate taking damage and trigger the event
    void TakeDamage(int damageAmount)
    {
        // Process damage logic...

        // Trigger the damage event
        OnPlayerDamaged?.Invoke(damageAmount);
    }
}

public class Enemy : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // Subscribe to the player's damage event
        Player.OnPlayerDamaged += HandlePlayerDamage;
    }

    void OnDisable()
    {
        // Unsubscribe from the event
         Player.OnPlayerDamaged -= HandlePlayerDamage;
    }

    // Method to handle player damage
    void HandlePlayerDamage(int damageAmount)
    {
        Debug.Log($"Enemy reacts to player damage: {damageAmount} damage taken!");
        // Additional logic can be added here...
    }
}
Explanation:
1. Delegate Type:

An event is defined by a delegate type, which defines the signature of methods that can be subscribed to an event.

 public delegate void DamageEventHandler(int damageAmount);
2. Event Declaration:

The event itself is declared using the event keyword and the delegate type.

public static event DamageEventHandler OnPlayerDamaged;
3. Trigger the Damage Event:

Call the event, invoking all subscribed methods.

 void TakeDamage(int damageAmount)
    {
        // Process damage logic...

        // Trigger the damage event
        OnPlayerDamaged?.Invoke(damageAmount);
    }
4. Subscribe to the Event:

In another script (e.g., an enemy script), subscribe to the player’s damage event using the += operator.

void Start()
{
    // Subscribe to the player's damage event
    Player.OnPlayerDamaged += HandlePlayerDamage;
}
5. Unsubscription from the Event:

To prevent memory leaks and ensure clean-up, it’s essential to unsubscribe from events when the subscribing object is disabled or destroyed. Unsubscribe to the player’s damage event using the -= operator.

   void OnDisable()
    {
        // Unsubscribe from the event
         Player.OnPlayerDamaged -= HandlePlayerDamage;
    }
6. Handle the Event:

Implement the method that handles the event. This method will be executed when the event is triggered.

void HandlePlayerDamage(int damageAmount)
{
    Debug.Log($"Enemy reacts to player damage: {damageAmount} damage taken!");
    // Additional logic can be added here...
}

In summary, delegates and events are an important element for creating flexible, modular, and responsive game systems.