深圳极速网站建设报价,腾讯云快速搭建网站,wordpress打印文章,织梦 友情链接 网站名 分隔符前言
框架的事件系统主要负责高效的方法调用与数据传递#xff0c;实现各功能之间的解耦#xff0c;通常在调用某个实例的方法时#xff0c;必须先获得这个实例的引用或者新实例化一个对象#xff0c;低耦合度的框架结构希望程序本身不去关注被调用的方法所依托的实例对象…前言
框架的事件系统主要负责高效的方法调用与数据传递实现各功能之间的解耦通常在调用某个实例的方法时必须先获得这个实例的引用或者新实例化一个对象低耦合度的框架结构希望程序本身不去关注被调用的方法所依托的实例对象是否存在通过事件系统做中转将功能的调用封装成事件使用事件监听注册、移除和事件触发完成模块间的功能调用管理。常用在UI事件、跨模块事件上。
一、作用
访问其它脚本时不直接访问而是通过发送一条类似“命令”让监听了这条“命令”的脚本自动执行对应的逻辑。
二、原理
1、让脚本向事件中心添加事件监听对应的“命令”。 2、发送“命令”事件中心就会通知监听了这条“命令”的脚本让它们自动执行对应的逻辑。
三、不使用事件管理器 新增3个测试脚本
public class Player : MonoBehaviour {public void Log(){Debug.Log(我是玩家);}
}public class Player1 : MonoBehaviour {public void Log(){Debug.Log(我是玩家1);}
}public class Player2 : MonoBehaviour {public void Log(){Debug.Log(我是玩家2);}
}调用各个脚本的log方法
public class EventManagerTest: MonoBehaviour
{private void Start(){GameObject go GameObject.Find(Player);go.GetComponentPlayer().Log(); GameObject go1 GameObject.Find(Player1);go1.GetComponentPlayer1().Log();GameObject go2 GameObject.Find(Player2);go2.GetComponentPlayer2().Log();}
}效果
四、使用事件管理器
1、事件管理器
新增EventManager事件管理器
/// summary
/// 事件管理器
/// /summary
public class EventManager : SingletonEventManager
{Dictionarystring, UnityAction eventsDictionary new Dictionarystring, UnityAction();/// summary/// 事件监听/// /summary/// param nameeventName事件名称/param/// param nameaction监听方法/parampublic void AddEventListener(string eventName, UnityAction action){if (eventsDictionary.ContainsKey(eventName)){eventsDictionary[eventName] action;}else{eventsDictionary.Add(eventName, action);}}/// summary/// 触发事件/// /summary/// param nameeventName事件名称/parampublic void Dispatch(string eventName){if(eventsDictionary.ContainsKey(eventName)){eventsDictionary[eventName]?.Invoke();}}
}2、添加事件监听 分别在Player、Player1、Player2新增如下代码添加事件监听
private void Start() {EventManager.Instance.AddEventListener(打印日志, Log);
}3、触发事件 在EventManagerTest中触发事件
public class EventManagerTest : MonoBehaviour
{private void Start(){// GameObject go GameObject.Find(Player);// go.GetComponentPlayer().Log(); // GameObject go1 GameObject.Find(Player1);// go1.GetComponentPlayer1().Log();// GameObject go2 GameObject.Find(Player2);// go2.GetComponentPlayer2().Log();EventManager.Instance.Dispatch(打印日志);}
}4、结果 五、移除事件
比如有几个小怪都添加了事件监听杀死后会被销毁如果不把事件移除直接再次执行命令则会报错: MissingReferenceException:The object of type Capsulehas been destroyed but you are still trying to access it. 修改EventManager添加移除事件方法
/// summary
/// 移除事件某个监听方法
/// /summary
/// param nameeventName事件名称/param
/// param nameaction监听方法/param
public void RemoveEventListener(string eventName, UnityAction action){if(eventsDictionary.ContainsKey(eventName)){eventsDictionary[eventName] - action;}
}/// summary
/// 移除整个事件
/// /summary
/// param nameeventName名称/param
public void RemoveEvent(string eventName){if(eventsDictionary.ContainsKey(eventName)){eventsDictionary[eventName] null;}
}测试调用
public class EventManagerTest : MonoBehaviour
{ private void OnGUI(){if (GUI.Button(new Rect(0, 0, 150, 50), 触发事件)){EventManager.Instance.Dispatch(打印日志);}if (GUI.Button(new Rect(0, 50, 150, 50), 移除Player事件监听)){GameObject go GameObject.Find(Player);EventManager.Instance.RemoveEventListener(打印日志, go.GetComponentPlayer().Log); }if (GUI.Button(new Rect(0, 100, 150, 50), 移除整个事件)){EventManager.Instance.RemoveEvent(打印日志);}}
}效果
六、自定义枚举事件名称
目前事件名称是字符串手打容易出错我们可以选择使用枚举的方式
/// summary
/// 事件名称枚举
/// /summary
public enum EventNameEnum{Log, //打印AddHealth //群体回血
}修改EventManager新增获取事件名称方法
/// summary
/// 获取事件名称
/// /summary
/// param nameeventNameEnum事件枚举/param
/// returns事件名称/returns
private string GetEnventName(object EventNameEnum){return EventNameEnum.GetType().Name _ EventNameEnum.ToString();
}修改测试调用
public class EventManagerTest : MonoBehaviour
{ private void OnGUI(){if (GUI.Button(new Rect(0, 0, 150, 50), 触发事件)){EventManager.Instance.Dispatch(EventNameEnum.Log);}if (GUI.Button(new Rect(0, 50, 150, 50), 移除Player事件监听)){GameObject go GameObject.Find(Player);EventManager.Instance.RemoveEventListener(EventNameEnum.Log, go.GetComponentPlayer().Log); }if (GUI.Button(new Rect(0, 100, 150, 50), 移除整个事件)){EventManager.Instance.RemoveEvent(EventNameEnum.Log);}}
}结果和之前一样
七、传递带有一个参数的事件
如果我们想要传递带有一个参数的事件可以遵循里氏替换原则Liskov Substitution Principle即子类可以替换父类而不会影响程序的正确性。 里氏替换原则 通过使用 IEventInfo 接口可以确保 EventInfoT 和 EventInfo 类可以在需要 IEventInfo 的上下文中被替换而不影响程序的功能。这使得事件管理器能够处理不同类型的事件回调。 单一职责原则 每个 EventInfo 类都有自己的职责EventInfoT 处理带参数的回调而 EventInfo 处理不带参数的回调。这增强了代码的清晰性和可维护性。
这种设计提供了灵活性使得事件管理系统能够处理多种类型的事件同时也遵循了面向对象设计的原则。你可以根据需要扩展或修改 IEventInfo 和 EventInfo 类以支持更多的事件类型和逻辑。
1、接口 IEventInfo
定义一个标记接口 IEventInfo用于标识事件信息的类型。这样可以在系统中使用多态性确保遵循里氏替换原则。
public interface IEventInfo { }2、泛型类 EventInfo
EventInfo 类实现了 IEventInfo 接口。这个类用于处理带有参数的事件回调UnityAction允许在事件触发时传递参数。action 字段用于保存事件回调。
private class EventInfoT : IEventInfo
{public UnityActionT action;public EventInfo(UnityActionT call){action call; // 将传入的回调添加到 action 上}
}3、非泛型类 EventInfo
另一个 EventInfo 类用于处理没有参数的事件回调UnityAction。这种设计使得可以处理不同类型的事件。
private class EventInfo : IEventInfo
{public UnityAction action;public EventInfo(UnityAction call){action call; // 将传入的回调添加到 action 上}
}4、修改EventManager
事件名称记得修改一下不然我们可能很难分出哪个是带传参的我们可以选择把这个参数的类型的名字也传进去
Dictionarystring, IEventInfo eventsDictionary new Dictionarystring, IEventInfo();/// summary
/// 无参数的事件监听
/// /summary
/// param nameEventNameEnum事件枚举/param
/// param nameaction监听方法/param
public void AddEventListener(object EventNameEnum, UnityAction call)
{string eventName GetEnventName(EventNameEnum);if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo).action call;}else{eventsDictionary.Add(eventName, new EventInfo(call));}
}/// summary
/// 带1个参数的事件监听
/// /summary
public void AddEventListenerT(object EventNameEnum, UnityActionT call)
{string eventName GetEnventName(EventNameEnum) _ typeof(T).Name;if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfoT).action call;}else{eventsDictionary.Add(eventName, new EventInfoT(call));}
}//其他类似IEventInfo是我们人为制造出来的一个副接口这样的话就可以成功把有参数的事件和无参数的事件都存到字典里面去了
5、事件监听
Player、Player1、Player2都添加带一个参数的事件监听
public class Player : MonoBehaviour
{private void Start(){EventManager.Instance.AddEventListener(EventNameEnum.Log, Log);EventManager.Instance.AddEventListenerint(EventNameEnum.AddHealth, AddHealth);}public void Log(){Debug.Log(我是玩家);}public void AddHealth(int health){Debug.Log($玩家恢复{health 1}血);}
}6、触发事件
测试触发事件
public class EventManagerTest : MonoBehaviour
{private void OnGUI(){if (GUI.Button(new Rect(150, 0, 150, 50), 触发带1个参数事件)){EventManager.Instance.Dispatchint(EventNameEnum.AddHealth, 1);}if (GUI.Button(new Rect(150, 50, 150, 50), 移除Player带1个参数事件监听)){GameObject go GameObject.Find(Player);EventManager.Instance.RemoveEventListenerint(EventNameEnum.AddHealth, go.GetComponentPlayer().AddHealth); }if (GUI.Button(new Rect(150, 100, 150, 50), 移除整个带1个参数事件)){EventManager.Instance.RemoveEventint(EventNameEnum.AddHealth);}}
}7、效果 八、传递带有多个参数的事件
方法一、自定义类
相当于将多个参数合并到一个类里在传递进去
比如
public class MyInfo
{public int a;public float b;public double c;
}调用
方法二、元组
相当于通过元组把多个参数合并传递进去
方法三、添加带不同数量参数的方法推荐
这种办法虽然最麻烦但是不会有性能问题可以避免下面的问题
1、GC垃圾回收
创建元组或自定义类实例会导致额外的内存分配从而增加垃圾回收的压力。在高频率调用的场景下频繁分配和回收内存会导致性能下降影响游戏的帧率。
2、装箱问题
对于值类型如 int、struct 等使用元组或对象时可能会导致装箱和拆箱增加内存开销和降低性能。这在使用泛型时尤为明显因为值类型会被包装为对象。
3、开销和复杂性
封装多个参数在一个元组或自定义类中虽然提高了代码的可读性但也增加了开销特别是在事件频繁触发的情况下开销可能会显著。
九、最终代码
这里我添加最多支持添加4个参数的事件一般都够了如果觉得还是不够可以模仿我的方式继续添加即可
using System.Collections.Generic;
using UnityEngine.Events;/// summary
/// 事件管理器之所以这么多函数主要是出于性能考虑避免产生GC、装箱问题
/// /summary
public class EventManager : SingletonEventManager
{Dictionarystring, IEventInfo eventsDictionary new Dictionarystring, IEventInfo();/// summary/// 获取事件名称/// /summary/// param nameeventNameEnum事件枚举/param/// returns事件名称/returnsprivate string GetEnventName(object EventNameEnum){return EventNameEnum.GetType().Name _ EventNameEnum.ToString();}#region 事件监听/// summary/// 无参数的事件监听/// /summary/// param nameEventNameEnum事件枚举/param/// param nameaction监听方法/parampublic void AddEventListener(object EventNameEnum, UnityAction call){string eventName GetEnventName(EventNameEnum);if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo).action call;}else{eventsDictionary.Add(eventName, new EventInfo(call));}}/// summary/// 带1个参数的事件监听/// /summarypublic void AddEventListenerT(object EventNameEnum, UnityActionT call){string eventName GetEnventName(EventNameEnum) _ typeof(T).Name;if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfoT).action call;}else{eventsDictionary.Add(eventName, new EventInfoT(call));}}/// summary/// 带2个参数的事件监听/// /summarypublic void AddEventListenerT0, T1(object EventNameEnum, UnityActionT0, T1 call){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name;if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfoT0, T1).action call;}else{eventsDictionary.Add(eventName, new EventInfoT0, T1(call));}}/// summary/// 带3个参数的事件监听/// /summarypublic void AddEventListenerT0, T1, T2(object EventNameEnum, UnityActionT0, T1, T2 call){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name _ typeof(T2).Name;if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfoT0, T1, T2).action call;}else{eventsDictionary.Add(eventName, new EventInfoT0, T1, T2(call));}}/// summary/// 带4个参数的事件监听/// /summarypublic void AddEventListenerT0, T1, T2, T3(object EventNameEnum, UnityActionT0, T1, T2, T3 call){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name _ typeof(T2).Name _ typeof(T3).Name;if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfoT0, T1, T2, T3).action call;}else{eventsDictionary.Add(eventName, new EventInfoT0, T1, T2, T3(call));}}#endregion#region 触发事件/// summary/// 触发事件/// /summary/// param nameEventNameEnum事件枚举/parampublic void Dispatch(object EventNameEnum){string eventName GetEnventName(EventNameEnum);if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo).action?.Invoke();}}/// summary/// 触发带1个参数事件/// /summarypublic void DispatchT(object EventNameEnum, T parameter){string eventName GetEnventName(EventNameEnum) _ typeof(T).Name;//如果字典中该事件的名字存在且该事件不为空则执行该事件不存在则什么也不做。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT).action?.Invoke(parameter);}/// summary/// 触发带2个参数事件/// /summarypublic void DispatchT0, T1(object EventNameEnum, T0 parameter0, T1 parameter1){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name;//如果字典中该事件的名字存在且该事件不为空则执行该事件不存在则什么也不做。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT0, T1).action?.Invoke(parameter0, parameter1);}/// summary/// 触发带3个参数事件/// /summarypublic void DispatchT0, T1, T2(object EventNameEnum, T0 parameter0, T1 parameter1, T2 parameter2){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name _ typeof(T2).Name;//如果字典中该事件的名字存在且该事件不为空则执行该事件不存在则什么也不做。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT0, T1, T2).action?.Invoke(parameter0, parameter1, parameter2);}/// summary/// 触发带4个参数事件/// /summarypublic void DispatchT0, T1, T2, T3(object EventNameEnum, T0 parameter0, T1 parameter1, T2 parameter2, T3 parameter3){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name _ typeof(T2).Name _ typeof(T3).Name;//如果字典中该事件的名字存在且该事件不为空则执行该事件不存在则什么也不做。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT0, T1, T2, T3).action?.Invoke(parameter0, parameter1, parameter2, parameter3);}#endregion#region 移除事件某个监听方法/// summary/// 移除无参数事件某个监听方法/// /summary/// param nameEventNameEnum事件枚举/param/// param namecall监听方法/parampublic void RemoveEventListener(object EventNameEnum, UnityAction call){string eventName GetEnventName(EventNameEnum);if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo).action - call;}}/// summary/// 移除带1个参数事件某个监听方法/// /summarypublic void RemoveEventListenerT(object EventNameEnum, UnityActionT call){string eventName GetEnventName(EventNameEnum) _ typeof(T).Name;if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT).action - call;}/// summary/// 移除带2个参数事件某个监听方法/// /summarypublic void RemoveEventListenerT0, T1(object EventNameEnum, UnityActionT0, T1 call){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name;if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT0, T1).action - call;}/// summary/// 移除带3个参数事件某个监听方法/// /summarypublic void RemoveEventListenerT0, T1, T2(object EventNameEnum, UnityActionT0, T1, T2 call){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name _ typeof(T2).Name;if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT0, T1, T2).action - call;}/// summary/// 移除带4个参数事件某个监听方法/// /summarypublic void RemoveEventListenerT0, T1, T2, T3(object EventNameEnum, UnityActionT0, T1, T2, T3 call){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name _ typeof(T2).Name _ typeof(T3).Name;if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT0, T1, T2, T3).action - call;}#endregion#region 移除整个事件/// summary/// 移除整个不带参数的事件/// /summary/// param nameEventNameEnum事件枚举/parampublic void RemoveEvent(object EventNameEnum){string eventName GetEnventName(EventNameEnum);if (eventsDictionary.ContainsKey(eventName)){(eventsDictionary[eventName] as EventInfo).action null;}}/// summary/// 移除整个带1个参数的事件/// /summarypublic void RemoveEventT(object EventNameEnum){string eventName GetEnventName(EventNameEnum) _ typeof(T).Name;//如果字典中存在要移除的命令则把这个命令的所有事件移除掉。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT).action null;}/// summary/// 移除整个带2个参数的事件/// /summarypublic void RemoveEventT0, T1(object EventNameEnum){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name;//如果字典中存在要移除的命令则把这个命令的所有事件移除掉。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT0, T1).action null;}/// summary/// 移除整个带3个参数的事件/// /summarypublic void RemoveEventT0, T1, T2(object EventNameEnum){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name _ typeof(T2).Name;//如果字典中存在要移除的命令则把这个命令的所有事件移除掉。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT0, T1, T2).action null;}/// summary/// 移除整个带4个参数的事件/// /summarypublic void RemoveEventT0, T1, T2, T3(object EventNameEnum){string eventName GetEnventName(EventNameEnum) _ typeof(T0).Name _ typeof(T1).Name _ typeof(T2).Name _ typeof(T3).Name;//如果字典中存在要移除的命令则把这个命令的所有事件移除掉。if (eventsDictionary.ContainsKey(eventName))(eventsDictionary[eventName] as EventInfoT0, T1, T2, T3).action null;}#endregion/// summary/// 移除事件中心的所有事件。可以考虑在切换场景时调用。/// /summarypublic void RemoveAllEvent(){eventsDictionary.Clear();}#region 里氏替换原则private interface IEventInfo { }private class EventInfo : IEventInfo{public UnityAction action;public EventInfo(UnityAction call){action call;}}private class EventInfoT : IEventInfo{public UnityActionT action;public EventInfo(UnityActionT call){action call;}}private class EventInfoT0, T1 : IEventInfo{public UnityActionT0, T1 action;public EventInfo(UnityActionT0, T1 call){action call;}}private class EventInfoT0, T1, T2 : IEventInfo{public UnityActionT0, T1, T2 action;public EventInfo(UnityActionT0, T1, T2 call){action call;}}private class EventInfoT0, T1, T2, T3 : IEventInfo{public UnityActionT0, T1, T2, T3 action;public EventInfo(UnityActionT0, T1, T2, T3 call){action call;}}#endregion
}完结
赠人玫瑰手有余香如果文章内容对你有所帮助请不要吝啬你的点赞评论和关注你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法也欢迎评论私信告诉我哦
好了我是向宇https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者闲暇之余边学习边记录分享站在巨人的肩膀上通过学习前辈们的经验总是会给我很多帮助和启发如果你遇到任何问题也欢迎你评论私信或者加群找我 虽然有些问题我也不一定会但是我会查阅各方资料争取给出最好的建议希望可以帮助更多想学编程的人共勉~