【问题标题】:Unity EventManager with delegate instead of UnityEventUnity EventManager 与委托而不是 UnityEvent
【发布时间】:2017-02-03 22:48:42
【问题描述】:

我正在寻找这个 Manager using UnityEvent 的 c# 委托版本。我不想使用它,因为UnityEvent 在大多数情况下都比 C# 事件慢。

关于如何实现这一点的任何线索?

【问题讨论】:

    标签: c# unity3d


    【解决方案1】:

    您可以使用Action,它实际上是这样声明的委托:

    namespace System
    {
        public delegate void Action();
    }
    

    1。将所有UnityAction 替换为使用委托的System 命名空间中的Action

    2.将所有thisEvent.AddListener(listener);替换为thisEvent += listener;

    3.将所有thisEvent.RemoveListener(listener);替换为thisEvent -= listener;

    这里是 Unity 的 original EventManager 的修改版本,移植到使用委托/动作。

    无参数:

    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]();
            }
        }
    }
    

    测试脚本:

    下面的测试脚本通过每 2 秒触发一次事件来测试事件。

    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!");
        }
    }
    

    带参数:

    从其他问题来看,大多数人都在问如何支持参数。这里是。您可以使用class/struct 作为参数,然后将要传递的所有变量添加到此类/结构内的函数中。我将以EventParam 为例。随意添加/删除要在此代码末尾的事件EventParam 结构中传递的变量。

    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;
    }
    

    测试脚本:

    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!");
        }
    }
    

    【讨论】:

    • UnityEvent 做了哪些额外的工作导致它变慢?基本上,我们通过使用Action 和自定义事件管理器来实现更快的速度?
    • @ScottChamberlain 内存分配 + 过多使用反射。它的唯一优点是您可以在编辑器中将其公开,并从编辑器为其分配事件,因为它是序列化的。它在其他所有方面都失败了,不应该用于真正的游戏。可以自己测试,也可以查看thisthis
    • 啊,现在没问题了。我通过为带有参数的事件创建单独的字典来解决问题。您可以在此处查看我的代码: sta.sh/226znk8jknx3 \n 但是,我遇到了另一个问题。 :( 当我触发一个有两个或更多订阅者的事件时,只有一个订阅者会收到通知。我仍在浏览其他一些有关如何解决此问题的链接...当我再次访问这里时'我有这方面的更新。^_^ 但如果你有解决方案,请告诉我,呵呵。:'( PS 我注意到@CharlieSeligman 和我有同样的问题。
    • @CharlieSeligman 和@Zarashi99 ...找到并修复了EventManager 错误。你们可以测试并通知我吗? 另外,如果有人想让它接受一个参数,最简单的方法是让它接受一个classstruct 作为参数。然后,您可以在该类/结构中声明要传递给事件的所有变量。这使得它更易于重用,因为您所要做的就是修改类/结构以从中添加/删除参数。
    • @Zarashi99 不客气。我注意到您在其中一个 cmets 中提到了 DynamicInvoke。不要用那个。我只是在答案中添加了参数支持,因为大多数人都在其他问题上问过我这个问题。检查出。编码愉快!
    【解决方案2】:

    !!接受的答案不完整!!

    作为一个懒惰的程序员,我只是简单地复制了程序员写的东西,但在评论部分遇到了同样的问题。

    程序员的解决方案不适用于同一事件的多个订阅者。

    这是修复(参数版本的相同更改):

    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();
            }
        }
    }
    

    这是我在此发布的 StackOverflow 问题的链接

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

    当您调用 TryGetValue(eventName, out thisEvent) 时,您提供了字典将写入值的引用。您没有获得对 Dictionary 内部内容的引用(我的意思是,您没有获得指向 Dictionary 结构的深层指针,这意味着分配给它不会修改 Dictionary)。

    【讨论】:

      【解决方案3】:

      聚会有点晚了,@programmer 上面的回答确实帮了很多忙,但是如果有人想用返回值触发事件,仍然想分享一个答案,版主当然知道如何处理这个答案。

      .net 提供 func 和 action , func 或 func

      这是带有返回值的@programmers 代码:

       private Dictionary<string, Func<EventParam,bool>> 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, Func<EventParam, bool>>();
          }
      }
      
      public static void StartListening(string eventName,Func<EventParam, bool> listener)
      {
          Func<EventParam, bool> thisEvent;
          if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
          {
      
              thisEvent += listener;
      
      
              instance.eventDictionary[eventName] = thisEvent;
          }
          else
          {
      
              thisEvent += listener;
              instance.eventDictionary.Add(eventName, thisEvent);
          }
      }
      
      public static void StopListening(string eventName, Func<EventParam, bool> listener)
      {
          if (eventManager == null) return;
          Func<EventParam, bool> thisEvent;
          if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
          {
      
              thisEvent -= listener;
      
      
              instance.eventDictionary[eventName] = thisEvent;
          }
      }
      
      public static bool TriggerEvent(string eventName, EventParam eventParam)
      {
          Func<EventParam, bool> thisEvent = null;
          if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
          {
              bool value;
              value = thisEvent.Invoke(eventParam);
              return value;
          }
          return false;
      }
      

      }

      公共结构事件参数 { 公共字符串参数1;

      }

      所以现在Trigger可以这样调用

      EventParam newparam = new EventParam();
          newparam.param1 = "Ty Mr Programmer this custom eventmanager";
          bool checkme;
          checkme =  EventManager.TriggerEvent("API", newparam);
      

      【讨论】:

        猜你喜欢
        • 2016-09-03
        • 1970-01-01
        • 1970-01-01
        • 2013-07-07
        • 2011-01-12
        • 2020-04-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多