Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unity EventManager with delegate instead of UnityEvent

Tags:

c#

unity3d

I am looking for c# delegate version of this Manager using UnityEvent. I don't want to use this because UnityEvent is slower than C# event at most time.

Any clue on how to implement this?

like image 912
weijia_yu Avatar asked Nov 28 '22 20:11

weijia_yu


2 Answers

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!");
    }
}
like image 94
Programmer Avatar answered Nov 30 '22 08:11

Programmer


!! Accepted answer is not complete !!

As a lazy programmer I simply copied what Programmer had written, but ran into the same problem people in the comment section ran into.

Programmer's solution does not work for multiple subscribers to the same event.

This is the fix (same changes for parameters version):

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

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)
    {
        if (instance.eventDictionary.ContainsKey(eventName))
        {
            instance.eventDictionary[eventName] += listener;
        }
        else
        {
            instance.eventDictionary.Add(eventName, listener);
        }
    }

    public static void StopListening(string eventName, Action listener)
    {
        if (instance.eventDictionary.ContainsKey(eventName))
        {
            instance.eventDictionary[eventName] -= listener;
        }
    }

    public static void TriggerEvent(string eventName)
    {
        Action thisEvent = null;
        if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
        {
            thisEvent.Invoke();
        }
    }
}

Here is a link to a StackOverflow question I posted on this

Why do I get a clone of Action<> when getting from dictionary?

When you call TryGetValue(eventName, out thisEvent) you are providing a reference to which the Dictionary will write the value. You are not getting a reference to what is inside the Dictionary (I mean, you are not getting a deep pointer to the Dictionary structure, meaning that assigning to it will NOT modify the Dictionary).

like image 45
TheForgot3n1 Avatar answered Nov 30 '22 09:11

TheForgot3n1