Both can be made events, both support generics and can send different info to listening methods, exact same instance methods, exact same functionality. So what’s the reason for adding UnityActions? Or it was added to provide event support for other languages in time where there was UnityScript, JS, etc.?
I think the main reason for UnityEvents is to support serialization and editing through the inspector.
But I believe UnityAction is simply a delegate, defined the same way as System.Action, and I think the reason for it is that System.Action hadn’t yet been added to the language at the time UnityAction was created.
Although there’s maybe also an argument related to the fact that Unity doesn’t put “using System” in your files by default? (Using both System and UnityEngine results in some naming conflicts, e.g. Random)
Actually UnityAction<T> and System.Action<T> are both the same generic delegate declarations, just seperate reimplementations. AFAIK UnityEvents used System.Actions in the past and just recently switched to their own version. Both are not really serializable since they are just plain C# delegate which are generally hard up to implossible to serialize. So the exact reason why they shipped their own version is kinda unclear. For me the most sound explanation would be the independence from the System namespace / mscorlib implementation. Though that’s just speculation.
Be warned that even though both generic delegate are implemented exactly the same way, it doesn’t make them compatible to each other. A conversion should be possible but casting is out of the question. Casting only allows you to walk up or down the inheritance chain. UnityAction and System.Action are two separate types.
I think this is where UnityEvent gets its magic because they serialize it in the local namespace context of the scene or prefab. This is what happens to a button when I hook up a local function to it (in this case set_enabled):

Pretty spiffy… I imagine there’s editor-aware reflection magic going on to make this bidirectionally friendly within the editor context.
I believe the reason might purely just be for the inspector serialization support.
It’s quicker to setup events/listeners via the inspector and components listening to the events don’t need an explicit reference to the script containing the event, which also creates greater code decoupling.
Still, I think there are both cases where UnityEvents might be preferred over C# events, and C# events might be preferred over UnityEvents.
I like to use UnityEvents for components that could serve a sort of abstract purpose in a scene.
For example, something like a simple counter component:
public class Counter : MonoBehaviour {
[SerializeField] private int min;
[SerializeField] private int max;
[SerializeField] private int current;
[SerializeField] private UnityEvent<int> onCurrentChange;
[SerializeField] private UnityEvent onMaxReached;
[SerializeField] private UnityEvent onMinReached;
public int Min { get => min; set => min = value; }
public int Max { get => max; set => max = value; }
public int Current {
get => current;
set {
current = Mathf.Clamp(value, min, max);
onCurrentChange.Invoke(current);
if(current == max) {
onMaxReached.Invoke();
}
else if(current == min) {
onMinReached.Invoke();
}
}
}
}
This component could be used for things like:
-
A health system
-
“onCurrentChange” invoked → update health UI, play hurt/heal effects, etc.
-
“onMinReached” invoked → play death animation/effects, game-over for player, end of boss fight, etc.
-
A quest tracker
-
Display (“current” / “max”) things to collect/kill/interact/etc. in UI.
-
“onCurrentChange” invoked → update UI, play effects, etc.
-
“onMaxReached” invoked → complete quest, update objective, reward player, unlock new content, etc.
-
Item slots in an inventory system
-
“max” limits how many of a certain item can be picked up.
-
Display “current” amount items in inventory slot UI.
-
“onCurrentChange” invoked → update inventory slot UI, play effects, etc.
-
“onMaxReached” invoked → disable picking up more of this specific item.
-
“onMinReached” invoked → all items used/dropped/whatever; clear item slot from inventory.
Since there could be many different things that this component influences in the scene, UnityEvents make it convenient to bind different listeners on different components without needing to create explicit references to the component for every single listener.
On the other hand, I like to use regular C# events for components that serve a specific purpose and aren’t bound to any specific scene.
For example, a player manager in a game with local co-op support. Suppose I have a system setup to detect when players join/leave the game via connecting/disconnecting input devices, which invoke “onPlayerJoin” & “onPlayerLeave” events respectively.
When that happens, maybe I want to enable/disable a split-screen camera view, update some global game difficulty stat to reflect on how many players there are, instantiate/destroy the joined/left players, things like that.
It seems you both confused UnityEvent with UnityAction. UnityEvent is a serializable method call with several limitations. The target object has be to a UnityEngine.Object (the only references Unity can serialize) and it supports a custom “closure construct” that allows to serialize a single parameter for the method call.
The UnityAction<T> type is literally just a one-to-one copy of the System.Action<T> type. Namely:
public delegate void UnityAction<T0>(T0 arg0);
which is defined here. The System.Action<T> type looks exactly the same. Those are both not serializable by default.
As you may know I’ve been poking around in the managed references for years. I just recently have discovered the UnityAction classes. I’m pretty sure they didn’t exist when the UnityEvents were introduced. I would need to check some older UnityEngine.dlls to be sure. Though even if they were used from the beginning, the UnityAction and the System.Action delegate types are 100% the same so it makes no difference besides the mentioned namespace advantage.
We recently had a thread about serializing UnityActions , which is not really possible without a lot of reflection and hacks around that to restore the objects and object references. There also was this thread about UnityEvents and how to access the stored / serialized data .