Unity EventManager with delegate instead of UnityEvent
You can use Action
which is actually a delegate declared like this:
namespace System
{
public delegate void Action();
}
1.Replace all the UnityAction
with Action
from the System
namespace which uses delegates.
2.Replace all thisEvent.AddListener(listener);
with thisEvent += listener;
3.Replace all thisEvent.RemoveListener(listener);
with thisEvent -= listener;
Here is the modified version of Unity's original EventManager
ported to use delegate/Action.
Without Parameter:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EventManager : MonoBehaviour
{
private Dictionary<string, Action> eventDictionary;
private static EventManager eventManager;
public static EventManager instance
{
get
{
if (!eventManager)
{
eventManager = FindObjectOfType(typeof(EventManager)) as EventManager;
if (!eventManager)
{
Debug.LogError("There needs to be one active EventManger script on a GameObject in your scene.");
}
else
{
eventManager.Init();
}
}
return eventManager;
}
}
void Init()
{
if (eventDictionary == null)
{
eventDictionary = new Dictionary<string, Action>();
}
}
public static void StartListening(string eventName, Action listener)
{
Action thisEvent;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
//Add more event to the existing one
thisEvent += listener;
//Update the Dictionary
instance.eventDictionary[eventName] = thisEvent;
}
else
{
//Add event to the Dictionary for the first time
thisEvent += listener;
instance.eventDictionary.Add(eventName, thisEvent);
}
}
public static void StopListening(string eventName, Action listener)
{
if (eventManager == null) return;
Action thisEvent;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
//Remove event from the existing one
thisEvent -= listener;
//Update the Dictionary
instance.eventDictionary[eventName] = thisEvent;
}
}
public static void TriggerEvent(string eventName)
{
Action thisEvent = null;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.Invoke();
// OR USE instance.eventDictionary[eventName]();
}
}
}
Test script:
The test script below test the event by triggering events every 2 seconds.
public class TestScript: MonoBehaviour
{
private Action someListener;
void Awake()
{
someListener = new Action(SomeFunction);
StartCoroutine(invokeTest());
}
IEnumerator invokeTest()
{
WaitForSeconds waitTime = new WaitForSeconds(2);
while (true)
{
yield return waitTime;
EventManager.TriggerEvent("test");
yield return waitTime;
EventManager.TriggerEvent("Spawn");
yield return waitTime;
EventManager.TriggerEvent("Destroy");
}
}
void OnEnable()
{
EventManager.StartListening("test", someListener);
EventManager.StartListening("Spawn", SomeOtherFunction);
EventManager.StartListening("Destroy", SomeThirdFunction);
}
void OnDisable()
{
EventManager.StopListening("test", someListener);
EventManager.StopListening("Spawn", SomeOtherFunction);
EventManager.StopListening("Destroy", SomeThirdFunction);
}
void SomeFunction()
{
Debug.Log("Some Function was called!");
}
void SomeOtherFunction()
{
Debug.Log("Some Other Function was called!");
}
void SomeThirdFunction()
{
Debug.Log("Some Third Function was called!");
}
}
With Parameter:
From other questions, most people are asking how to support parameter. Here it is. You can use class
/struct
as parameter then add all the variables you want to pass into the function inside this class/struct. I will use EventParam
as an example. Feel free to add/remove variables you want to pass in the event EventParam
structure at the end of this code.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EventManager : MonoBehaviour
{
private Dictionary<string, Action<EventParam>> eventDictionary;
private static EventManager eventManager;
public static EventManager instance
{
get
{
if (!eventManager)
{
eventManager = FindObjectOfType(typeof(EventManager)) as EventManager;
if (!eventManager)
{
Debug.LogError("There needs to be one active EventManger script on a GameObject in your scene.");
}
else
{
eventManager.Init();
}
}
return eventManager;
}
}
void Init()
{
if (eventDictionary == null)
{
eventDictionary = new Dictionary<string, Action<EventParam>>();
}
}
public static void StartListening(string eventName, Action<EventParam> listener)
{
Action<EventParam> thisEvent;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
//Add more event to the existing one
thisEvent += listener;
//Update the Dictionary
instance.eventDictionary[eventName] = thisEvent;
}
else
{
//Add event to the Dictionary for the first time
thisEvent += listener;
instance.eventDictionary.Add(eventName, thisEvent);
}
}
public static void StopListening(string eventName, Action<EventParam> listener)
{
if (eventManager == null) return;
Action<EventParam> thisEvent;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
//Remove event from the existing one
thisEvent -= listener;
//Update the Dictionary
instance.eventDictionary[eventName] = thisEvent;
}
}
public static void TriggerEvent(string eventName, EventParam eventParam)
{
Action<EventParam> thisEvent = null;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.Invoke(eventParam);
// OR USE instance.eventDictionary[eventName](eventParam);
}
}
}
//Re-usable structure/ Can be a class to. Add all parameters you need inside it
public struct EventParam
{
public string param1;
public int param2;
public float param3;
public bool param4;
}
Test script:
public class Test : MonoBehaviour
{
private Action<EventParam> someListener1;
private Action<EventParam> someListener2;
private Action<EventParam> someListener3;
void Awake()
{
someListener1 = new Action<EventParam>(SomeFunction);
someListener2 = new Action<EventParam>(SomeOtherFunction);
someListener3 = new Action<EventParam>(SomeThirdFunction);
StartCoroutine(invokeTest());
}
IEnumerator invokeTest()
{
WaitForSeconds waitTime = new WaitForSeconds(0.5f);
//Create parameter to pass to the event
EventParam eventParam = new EventParam();
eventParam.param1 = "Hello";
eventParam.param2 = 99;
eventParam.param3 = 43.4f;
eventParam.param4 = true;
while (true)
{
yield return waitTime;
EventManager.TriggerEvent("test", eventParam);
yield return waitTime;
EventManager.TriggerEvent("Spawn", eventParam);
yield return waitTime;
EventManager.TriggerEvent("Destroy", eventParam);
}
}
void OnEnable()
{
//Register With Action variable
EventManager.StartListening("test", someListener1);
EventManager.StartListening("Spawn", someListener2);
EventManager.StartListening("Destroy", someListener3);
//OR Register Directly to function
EventManager.StartListening("test", SomeFunction);
EventManager.StartListening("Spawn", SomeOtherFunction);
EventManager.StartListening("Destroy", SomeThirdFunction);
}
void OnDisable()
{
//Un-Register With Action variable
EventManager.StopListening("test", someListener1);
EventManager.StopListening("Spawn", someListener2);
EventManager.StopListening("Destroy", someListener3);
//OR Un-Register Directly to function
EventManager.StopListening("test", SomeFunction);
EventManager.StopListening("Spawn", SomeOtherFunction);
EventManager.StopListening("Destroy", SomeThirdFunction);
}
void SomeFunction(EventParam eventParam)
{
Debug.Log("Some Function was called!");
}
void SomeOtherFunction(EventParam eventParam)
{
Debug.Log("Some Other Function was called!");
}
void SomeThirdFunction(EventParam eventParam)
{
Debug.Log("Some Third Function was called!");
}
}
How do I pass a function to be added to an event in Unity/C#?
UnityEvents are not the same thing as C# Events in the System namespace, and UnityAction are not the same thing as C# Actions in the System namespace. But in your code you are mixing the two, with OnDeathEvent
being in the UnityEngine.Events
namespace and Func
being in System
. The reason you're having a compiler issue is that the UnityEvent.AddListener
method takes a UnityAction
argument, not a System.Func
.
To fix, you will either need to switch your OnDeathEvent
to being a C# event, or change your AddOnDeathListener
to take UnityAction
as an argument.
References:
https://docs.unity3d.com/ScriptReference/Events.UnityEvent.AddListener.html
https://www.jacksondunstan.com/articles/3335
Code Sample:
public class CoolAbstract : TestMeAbstract
{
UnityEvent SomeKindOfEvent;
private void Start()
{
SomeKindOfEvent = new UnityEvent();
AddOnDeathListener(() => { Debug.Log("Cool Abstraction"); });
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.A))
{
SomeKindOfEvent.Invoke();
}
}
public override void AddOnDeathListener(UnityAction ua)
{
SomeKindOfEvent.AddListener(ua);
}
}
public abstract class TestMeAbstract : MonoBehaviour
{
public abstract void AddOnDeathListener(UnityAction ua);
}
Why choose UnityEvent over native C# events?
Am I overlooking something?
Nope, you are not overlooking anything. The only advantage and reason to use UnityEvent
is that it allows you to use events in the Editor. That's for drag and drop people or those making Editor plugins.
Another advantage of UnityEvent
is that it prevents the problem of Unity Object not being freed due to the misuse of delegates or using anonymous delegates with Unity Objects. Although they get freed when the main script that's holding them is destroyed. The reason for this is because UnityEvent
is implemented with weak references therefore removing/minimizing this problem. These two things are still not worth it to use UnityEvent
over native C# events.
You should always use native event and delegate over UnityEvent if you are not making an Editor plugin because of its fast performance and small memory usage. See this and this post post for more information.
Unity: error when trying to invoke an event
Reason behind the issue
The error appeared due to having the event invoked outside the class it was in.
More info: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/event
Potential solution
If you want to be able to invoke your delegate outside the EventManager
class, remove the event
keyword or add a public method that would allow to invoke the event using an another way.
Extra note
In this case you can use a built in delegate called Action
instead of making a custom one.
Independent Unity event
Instead of pressing "F" you can Invoke your function frequently with: InvokeRepeating
Also: I don't understand the purpose of the if condition in the first line of randomInvoke(), the statement will always be true, so instead you can delete the line or change the condition.
Related Topics
Modify Xml Existing Content in C#
How to Catch Exceptions from a Threadpool.Queueuserworkitem
Serialization Breaks in .Net 4.5
How to Use C# 8 with Visual Studio 2017
An Efficient Way to Base64 Encode a Byte Array
How to Remove Numbers from String Using Regex.Replace
How to Draw a Rounded Rectangle in C#
Registry.Localmachine.Opensubkey() Returns Null
How to Have a Variable Number of Generic Parameters
What Is the Correct Way to Cancel an Async Operation That Doesn't Accept a Cancellationtoken
Preventing Jit Inlining on a Method
Encoding Trouble with Httpwebresponse
Return "Raw" JSON in ASP.NET Core 2.0 Web API