Featured Articles:

Monday, September 7, 2026

Delegates, Events, Actions and Func in Unity: When to Use Each One

Welcome to another blog post about one of those C# topics that trips up almost every solo game developer at some point: delegates, events, Action, and Func. If you've ever stared at a tutorial and thought "okay but why would I use this over just calling a method directly," well, ..you're in the right place. 

Let's take a look shall we? 

 

 

 

 

 

What Even Is A Delegate?

At its core, a Unity C# delegate is just a type that holds a reference to a method instead of a value. Think of it like a variable, except instead of storing an int or a string, it stores "a method that matches this signature." That's it. That's the whole spooky mystery. A little anticlimactic, I know...

Here's the classic syntax:

public delegate void DamageDelegate(int amount);

public class Player : MonoBehaviour
{
    public DamageDelegate onDamage;

    void TakeDamage(int amount)
    {
        onDamage?.Invoke(amount);
    }
}

Now anything that wants to "hook into" damage being taken can assign a method to onDamage. No inheritance, no interfaces, no tightly coupled mess. Just a plug for other code to plug into.

Why does this matter for game devs specifically? Because Unity C# delegates are basically a shortcut to decoupled code without needing a whole architecture pattern. You don't have time to build an enterprise event bus for your one-person developed game. Delegates give you 80% of the benefit for 20% of the effort.

 

Events: Delegates With A Bouncer

An event is a delegate with restrictions bolted on. Specifically, code outside the declaring class can subscribe (+=) or unsubscribe (-=), but it can't invoke the delegate or overwrite it entirely with =. That's the whole difference.

public class Player : MonoBehaviour
{
    public event DamageDelegate OnDamage;

    void TakeDamage(int amount)
    {
        OnDamage?.Invoke(amount);
    }
}

public class HealthUI : MonoBehaviour
{
    void Start()
    {
        FindObjectOfType<Player>().OnDamage += UpdateHealthBar;
    }

    void UpdateHealthBar(int amount)
    {
        // update the UI slider or whatever
    }
}

Without event, any script with a reference to Player could do player.onDamage = null; and silently wipe out every subscriber. With event, that's a compiler error outside the class. It's a small guardrail, but it saves you from a very annoying 2 a.m. debugging session where UI just... stops updating for no apparent reason.

Player takes damage → OnDamage fires → HealthUI updates → SFX plays → Achievement checks progress

That arrow chain above is basically the entire pitch for events: one action, multiple independent reactions, none of which need to know about each other. 

 

Action And Func: Delegates Without The Ceremony

Writing a custom delegate type every time is fine, but 90% of the time you don't need to. C# ships with two generic delegate types that cover almost everything: Action and Func.

  • Action -- a method that returns void. Can take 0 to 16 parameters via generics.
  • Func -- a method that returns a value. The last generic type parameter is always the return type.
// Action examples
Action onGameOver;
Action<int> onScoreChanged;
Action<string, int> onEnemyKilled;

// Func examples
Func<bool> isPlayerAlive;
Func<int, int, int> addDamage; // takes two ints, returns an int

This is genuinely one of the most useful things to internalize when you're comparing Action vs Func vs delegate in C#. You almost never need to declare a custom delegate anymore, Action and Func already exist for basically every shape of method you'll write. Custom delegates still have a place (mostly readability, or when you want a named type for something that gets passed around a lot), but they're the exception, not the default.

 

A Quick Practical Example: Inventory System

Let's say you're building an inventory system. You want a method that checks whether the player can afford an item, and you don't want to hardcode "gold" as the only currency forever.

public class ShopManager : MonoBehaviour
{
    public Func<int, bool> canAfford;

    void Start()
    {
        canAfford = (cost) => PlayerWallet.Gold >= cost;
    }

    public void TryBuy(int cost)
    {
        if (canAfford(cost))
        {
            PlayerWallet.Gold -= cost;
            Debug.Log("Purchased!");
        }
    }
}

Later, if you add a gem currency or a "free weekend" event, you swap out what canAfford points to without touching TryBuy at all. That's the whole appeal: behavior becomes a value you can reassign, not a hardcoded chunk of logic buried in an if-statement.


 

So Which One Should You Actually Use?

Here's the gut-check version, since you're not going to memorize a spec sheet:

  • Use Action when you just need to run some code and don't care about a return value. Button clicks, "something happened" notifications, simple callbacks.
  • Use Func when you need an answer back. Validation checks, calculations, "can this happen" queries.
  • Use event when multiple, unrelated systems need to react to something happening, and you want to protect the invocation so outside code can't hijack or clear it.
  • Use a custom delegate when you want a named, reusable type for readability; think a public delegate void EnemyDefeatedHandler(Enemy enemy, int xpReward) that gets used in five different scripts and reads better than a wall of Action<Enemy, int>.

If you're building a small system where only one class cares about the callback, an Action or Func field is fine. The second you have multiple independent listeners: UI, audio, achievements, save system; reach for event. This is one of the more common Unity events vs delegates questions solo devs ask, and honestly the answer usually comes down to "how many things need to listen, and do I trust them not to mess with each other's subscriptions."

 

A Word on UnityEvents

You might be wondering where UnityEvent fits into all this. Quick answer: it's Unity's own serializable wrapper around the delegate concept, designed so you can hook up listeners in the Inspector without writing code. It's great for designer-friendly workflows, but it's slower at runtime than a plain Action or event due to reflection overhead, so for performance-sensitive, code-only systems, stick with the C# native versions covered above.


Common Gotchas

  • Forgetting to unsubscribe. If HealthUI subscribes in OnEnable but never unsubscribes in OnDisable, you get a memory leak and, in the worst case, a destroyed object still trying to receive callbacks.
  • Null reference on invoke. Always use ?.Invoke() instead of .Invoke() - if nobody's subscribed, the delegate is null, and a plain .Invoke() throws.
  • Multicast surprises with Func. If you assign multiple methods to one Func, only the return value of the last invoked method is returned. This trips devs up constantly. If you need every result, use Action plus an out-parameter pattern, or a list of Func you iterate manually.

Wrapping Up

Delegates, events, Actions, and Funcs all boil down to the same idea: treating a method like it's data you can pass around, store, and swap out. Once that clicks, a huge chunk of "clean architecture" advice in Unity stops feeling abstract and starts feeling like common sense. Start small, swap one hardcoded method call for an Action field and the rest builds naturally from there.

~happy Coding!

No comments:

Post a Comment