Unity C# 事件监听和广播
Posted 破道三十六
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity C# 事件监听和广播相关的知识,希望对你有一定的参考价值。
事件监听和广播
创建三个脚本
1. EventCenter 事件的处理中心
2. EvenType 存放事件码
3. CallBack 定义委托的类
添加监听的时候要先传递过来一个事件码,一个委托
实现一个简单功能,按钮点击显示所有Text文本和内容
一.EventCenter 脚本
1.脚本不需要继承MonoBehaviour,不需要绑定物体
//定义一个字典 来存放事件码,注意参数类型不要少写一个t
private Dictionary<EventType, Delegate> m_EventTable = new Dictionary<EventType, Delegate>();
Dictionary:字典
EventType: 事件码变量
Delegate: 委托 要加命名空间 using System; 委托首字母大写,具体用法还要深入研究
m_EventTable: 字典表名
if (!m_EventTable.ContainsKey(eventType))
//先给字典添加事件码,委托设置为空
m_EventTable.Add(eventType, null);
ContainsKey(): 字典的属性 确定字典中是否包含指定的键,返回Bool值
Add(): 字典添加元素的方法
Remove(): 字典删除指定元素 Key
GetType(): 返回某个实例具体引用的数据类型.C#中任何对象都具有GetType()方法,x.GetType(),其中x为变量名
string.Format(): 将多个对象格式化成一个字符串
TryGetValue: 当在字典中不能确定是否存在该键时需要使用TryGetValue,以减少一次不必要的查找,同时避免了判断Key值是否存在而引发的“给定关键字不在字典中。”的错误。(TryGetValue是根据ID返回相应的数据,如果没有ID则返回默认值)
///事件的处理中心
///处理不同事件的参数的监听
///不同参数的移除监听
///广播
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 不需要继承MonoBehaviour
/// </summary>
public class EventCenter
//定义一个字典 来存放事件码
private static Dictionary<EventType, Delegate> m_EventTable = new Dictionary<EventType, Delegate>();
//*************************************************************************************************************************************
/// <summary>
/// 判断事件监听是不是有错误
/// </summary>
/// <param name="eventType">事件码</param>
/// <param name="callBack">委托</param>
/// 主要是为了精简代码
private static void OnListenerAdding(EventType eventType, Delegate callBack)
//先判断事件码是否存在于事件表中
//如果不存在
if (!m_EventTable.ContainsKey(eventType))
//先给字典添加事件码,委托设置为空
m_EventTable.Add(eventType, null);
//当前事件码和委托是否一致
//如果不一致,是不能绑定在一起的
//先把事件码传进去,接收值是 Delegate
//这句代码是先把事件码拿出来
Delegate d = m_EventTable[eventType];
//d为空或d 的参数如果和callBack参数不一样
if (d != null && d.GetType() != callBack.GetType())
//抛出异常
throw new Exception(string.Format("尝试为事件0添加不同事件的委托,当前事件所对应的委托是1,要添加的委托类型2", eventType, d.GetType(), callBack.GetType()));
/// <summary>
/// 移除监听时做的判断,主要为精简代码
/// </summary>
/// <param name="eventType">事件码</param>
/// <param name="callBack">委托</param>
private static void OnListenerRemoving(EventType eventType, Delegate callBack)
//判断是否包含指定键
if (m_EventTable.ContainsKey(eventType))
//先把事件码拿出来
Delegate d = m_EventTable[eventType];
if (d == null)
throw new Exception(string.Format("移除监听错误:事件0没有对应的委托", eventType));
else if (d.GetType() != callBack.GetType())//判断移除的委托类型是否和d的一致
throw new Exception(string.Format("移除监听错误,尝试为事件移除不同类型的委托,当前委托类型为1,要移除的委托类型为2", eventType, d.GetType(), callBack.GetType()));
else //不存在事件码的情况
throw new Exception(string.Format("移除监听错误;没有事件码", eventType));
/// <summary>
/// 移除监听后的判断,主要为精简代码
/// </summary>
/// <param name="eventType"></param>
private static void OnListenerRemoved(EventType eventType)
//判断当前的事件码所对应的事件是否为空
//如果为空,事件码就没用了,就将事件码移除
if (m_EventTable[eventType] == null)
//移除事件码
m_EventTable.Remove(eventType);
//*********************************************************************************************************************************
/// <summary>
/// 添加监听 静态 无参
/// </summary>
/// <param name="eventType">事件码</param>
/// <param name="callBack">委托</param>
public static void AddListener(EventType eventType, CallBack callBack)
//调用事件监听是不是有错误方法
OnListenerAdding(eventType, callBack);
//已经存在的委托进行关联,相当于链式关系,再重新赋值
//两个类型不一致,要强转换
//委托对象可使用 "+" 运算符进行合并。
//一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。"-" 运算符可用于从合并的委托中移除组件委托。
//使用委托的这个有用的特点,您可以创建一个委托被调用时要调用的方法的调用列表。这被称为委托的 多播(multicasting),也叫组播。
//下面的程序演示了委托的多播
m_EventTable[eventType] = (CallBack)m_EventTable[eventType] + callBack;
/// <summary>
/// 添加监听 静态 一个参数
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="eventType"></param>
/// <param name="callBack"></param>
/// 因为有一个参数,方法后要加一个泛型“<T>”,大写的T来代表
/// CallBack 也是一个泛型,方法是有参数的,所以CallBack也是有参数的
/// 除此之外其它与无参方法基本一致
/// 泛函数 T 可以指定为任意的类型,多参数也是
public static void AddListener<T>(EventType eventType, CallBack<T> callBack)
//调用事件监听是不是有错误方法
OnListenerAdding(eventType, callBack);
//这里是有参方法需要更改的地方
//强制转换类型要加一个泛型 "<T>"
m_EventTable[eventType] = (CallBack<T>)m_EventTable[eventType] + callBack;
/// <summary>
/// 添加监听 静态 两个参数
/// </summary>
public static void AddListener<T,X>(EventType eventType, CallBack<T,X> callBack)
OnListenerAdding(eventType, callBack);
m_EventTable[eventType] = (CallBack<T,X>)m_EventTable[eventType] + callBack;
/// <summary>
/// 添加监听 静态 三个参数
/// </summary>
public static void AddListener<T, X, Y>(EventType eventType, CallBack<T, X, Y> callBack)
OnListenerAdding(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y>)m_EventTable[eventType] + callBack;
/// <summary>
/// 添加监听 静态 四个参数
/// </summary>
public static void AddListener<T, X, Y, Z>(EventType eventType, CallBack<T, X, Y, Z> callBack)
OnListenerAdding(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y, Z>)m_EventTable[eventType] + callBack;
/// <summary>
/// 添加监听 静态 五个参数
/// </summary>
public static void AddListener<T, X, Y, Z, W>(EventType eventType, CallBack<T, X, Y, Z, W> callBack)
OnListenerAdding(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y, Z, W>)m_EventTable[eventType] + callBack;
//*******************************************************************************************************************************
/// <summary>
/// 移除监听 静态 无参
/// </summary>
/// <param name="eventType">事件码</param>
/// <param name="callBack">委托</param>
public static void RemoveListener(EventType eventType, CallBack callBack)
//移除监听前的判断
OnListenerRemoving(eventType, callBack);
//这句话是主要的
//事件码对应的委托-callBack 然后再重新赋值,强转型首字母要大写
//移除监听
m_EventTable[eventType] = (CallBack)m_EventTable[eventType] - callBack;
//移除监听后的判断
OnListenerRemoved(eventType);
/// <summary>
/// 移除监听 静态 一个参数
/// </summary>
/// <param name="eventType">事件码</param>
/// <param name="callBack">委托</param>
public static void RemoveListener<T>(EventType eventType, CallBack<T> callBack)
OnListenerRemoving(eventType, callBack);
//这里是有参方法需要更改的地方
//强制转换类型要加一个泛型 "<T>"
m_EventTable[eventType] = (CallBack<T>)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
/// <summary>
/// 移除监听 静态 两个参数
/// </summary>
public static void RemoveListener<T,X>(EventType eventType, CallBack<T,X> callBack)
OnListenerRemoving(eventType, callBack);
m_EventTable[eventType] = (CallBack<T,X>)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
/// <summary>
/// 移除监听 静态 三个参数
/// </summary>
public static void RemoveListener<T, X, Y>(EventType eventType, CallBack<T, X, Y> callBack)
OnListenerRemoving(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y>)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
/// <summary>
/// 移除监听 静态 四个参数
/// </summary>
public static void RemoveListener<T, X, Y, Z>(EventType eventType, CallBack<T, X, Y, Z> callBack)
OnListenerRemoving(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y, Z>)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
/// <summary>
/// 移除监听 静态 五个参数
/// </summary>
public static void RemoveListener<T, X, Y, Z, W>(EventType eventType, CallBack<T, X, Y, Z,W> callBack)
OnListenerRemoving(eventType, callBack);
m_EventTable[eventType] = (CallBack<T, X, Y, Z, W>)m_EventTable[eventType] - callBack;
OnListenerRemoved(eventType);
//******************************************************************************************************************************
/// <summary>
/// 广播监听 静态 无参
/// </summary>
/// <param name="eventType">事件码</param>
/// 把事件码所对应的委托从m_EventTable 字典表中取出来,然后调用这个委托
public static void Broadcast(EventType eventType)
Delegate d;
//如果拿到这个值成功了,对这个委托进行一个广播
if (m_EventTable.TryGetValue(eventType, out d))
//把d强转型CallBack类型
CallBack callBack = d as CallBack;
if (callBack != null)
callBack();
else
throw new Exception(string.Format("广播事件错误:事件0对应的委托具有不同类型", eventType));
/// <summary>
/// 广播监听 静态 一个参
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="eventType"></param>
/// <param name="arg"></param>
/// 以为有参数,所以在方法后面加一个参数 T arg
public static void Broadcast<T>(EventType eventType, T arg)
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
//带有一个参数的委托
CallBack<T> callBack = d as CallBack<T>;
if (callBack != null)
//把参数传过去
callBack(arg);
else
throw new Exception(string.Format("广播事件错误:事件0对应的委托具有不同类型", eventType));
/// <summary>
/// 广播 静态 两个参数
/// </summary>
public static void Broadcast<T,X>(EventType eventType, T arg1,X arg2)
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
CallBack<T,X> callBack = d as CallBack<T,X>;
if (callBack != null)
callBack(arg1,arg2);
else
throw new Exception(string.Format("广播事件错误:事件0对应的委托具有不同类型", eventType));
/// <summary>
/// 广播 静态 三个参数
/// </summary>
public static void Broadcast<T, X, Y>(EventType eventType, T arg1, X arg2, Y arg3)
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
CallBack<T, X, Y> callBack = d as CallBack<T, X, Y>;
if (callBack != null)
callBack(arg1, arg2, arg3);
else
throw new Exception(string.Format("广播事件错误:事件0对应的委托具有不同类型", eventType));
/// <summary>
/// 广播 静态 四个参数
/// </summary>
public static void Broadcast<T, X, Y, Z>(EventType eventType, T arg1, X arg2, Y arg3, Z arg4)
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
CallBack<T, X, Y, Z> callBack = d as CallBack<T, X, Y, Z>;
if (callBack != null)
callBack(arg1, arg2, arg3, arg4);
else
throw new Exception(string.Format("广播事件错误:事件0对应的委托具有不同类型", eventType));
/// <summary>
/// 广播 静态 五个参数
/// </summary>
public static void Broadcast<T, X, Y, Z, W>(EventType eventType, T arg1, X arg2, Y arg3, Z arg4, W arg5)
Delegate d;
if (m_EventTable.TryGetValue(eventType, out d))
CallBack<T, X, Y, Z, W> callBack = d as CallBack<T, X, Y, Z, W>;
if (callBack != null)
callBack(arg1, arg2, arg3, arg4, arg5);
else
throw new Exception(string.Format("广播事件错误:事件0对应的委托具有不同类型", eventType));
二.EventType 脚本
1.存放事件码
2.枚举类型
3.后续有什么需要,可以自定义添加
4.不需要任何命名空间,Clss类,继承,挂载物体
///存放事件码
///枚举类型
///后续有什么需要,可以自定义添加
public enum EventType
ShowText,
enum :枚举类型
ShowText:自己定义的变量
枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。
变量要用 逗号 “ ,”隔开,最后一个变量不用逗号
C# 枚举是值类型。换句话说,枚举包含自己的值,且不能继承或传递继承。
1、通过 Enum.GetNames(),和 Enum.GetValues() 进行名称和值的数组获取,从而实现遍历
2、之间使用 Enum 枚举值,默认 0 ,每个值增加 1 的特点,使用 for 的 i++ 使用遍历
三.CallBack 脚本
1.不需要任何命名空间,Clss类,继承,挂载物体
2.封装里了系统所使用到的委托
3.定义委托的类
4.定义多少参数都是可以的
5.定义了多少委托,EventCenter添加多少委托,过多添加会报错
CallBack脚本是种什么写法?
菜鸟教程:委托声明决定了可由该委托引用的方法。委托可指向一个与其具有相同标签的方法。
例如,假设有一个委托:
public delegate int MyDelegate (string s);
上面的委托可被用于引用任何一个带有一个单一的 string 参数的方法,并返回一个 int 类型变量。
声明委托的语法如下:
delegate <return type> <delegate-name> <parameter list>
委托对象可使用 "+" 运算符进行合并。一个合并委托调用它所合并的两个委托。只有相同类型的委托可被合并。"-" 运算符可用于从合并的委托中移除组件委托。
使用委托的这个有用的特点,您可以创建一个委托被调用时要调用的方法的调用列表。这被称为委托的 多播(multicasting),也叫组播。下面的程序演示了委托的多播
///封装里了系统所使用到的委托
///定义委托的类
///定义多少参数都是可以的
///定义了多少委托,EventCenter添加多少委托,过多添加会报错
/// <summary>
/// 无参委托
/// </summary>
public delegate void CallBack();
/// <summary>
/// 有参委托,一个参数,需要指定一个泛型,( 泛型类型 变量名 )
/// </summary>
/// <typeparam name="T">泛型类型</typeparam>
/// <param name="arg">变量名</param>
public delegate void CallBack<T>(T arg);
/// <summary>
/// 多个参数
/// </summary>
/// <typeparam name="T">泛型类型</typeparam>
/// <typeparam name="X">泛型类型</typeparam>
/// <param name="arg1">变量名</param>
/// <param name="arg2">变量名</param>
/// 一般只用到5个,如果还需要可以往后加
public delegate void CallBack<T, X>(T arg1, X arg2);
public delegate void CallBack<T, X, Y>(T arg1, X arg2, Y arg3);
public delegate void CallBack<T, X, Y, Z>(T arg1, X arg2, Y arg3, Z arg4);
public delegate void CallBack<T, X, Y, Z, W>(T arg1, X arg2, Y arg3, Z arg4, W arg5);
delegate: 委托声明
注册委托时的方法需要注意的点:
1.返回值类型要一致,下面我的返回值类型都是void,所以我注册的方法也必须是void返回值类型
2.参数的个数以及参数类型要保持一致
四.添加监听,移除监听,广播方法的使用
1.添加监听
1.方法参数为(事件码 方法)
2.参数里面的方法并不需要加参数
3.泛型类型要和参数的类型一致,不一致会报错
4.方法是自己写的方法
//单个参数
EventCenter.AddListener<string >(EventType.ShowText, Show);
脚本名 .方法名 <泛函数类型>(枚举.枚举变量 ,方法);
//多个参数
EventCenter.AddListener<string, string, float, int, bool>(EventType.ShowText, Show2);
2.移除监听
//单个参数
EventCenter.RemoveListener<string>(EventType.ShowText, Show);
脚本名 .方法名 <泛函数类型>(枚举.枚举变量 ,方法);
EventCenter.RemoveListener<string ,string, float, int, bool >(EventType.ShowText, Show);
3.广播
//调用广播的方法
//有参数时,要在广播后面加相应类型的参数
//一个参数(string)
EventCenter.Broadcast(EventType.ShowText,"你被监听了");
//四个参数(string,string float,int ,bool)
EventCenter.Broadcast(EventType.ShowText, "你被监听了", "对 是你", 12.02f, 36, true);
4.自己随便写的方法
//获取参数,并开启Txet对象,并赋值文字,一个参数
//将脚本绑定在Text文本物体上
public void Show(string str)
gameObject.SetActive(true);
GetComponent<Text>().text = str;
广播后的结果:text文本显示:你被监听了
//四个参数
private void Show2(string str,string str1,float a,int b,bool c)
gameObject.SetActive(true);
if (c)
GetComponent<Text>().text = str+str1+a+b+c;
广播后的结果:text文本显示:你被监听了对是你12.02f36true
这些脚本的方法调用,与脚本绑定还要在研究实践
以上脚本内容均采用Siki学院教学视频,制作成笔记供自己学习
Spring事件监听机制源码解析
参考技术A 1.Spring事件监听体系包括三个组件:事件、事件监听器,事件广播器。事件:定义事件类型和事件源,需要继承ApplicationEvent。
事件监听器:用来监听某一类的事件,并且执行具体业务逻辑,需要实现ApplicationListener 接口或者需要用@ListenerEvent(T)注解。好比观察者模式中的观察者。
事件多播器:负责广播通知所有监听器,所有的事件监听器都注册在了事件多播器中。好比观察者模式中的被观察者。Spring容器默认生成的是同步事件多播器。可以自定义事件多播器,定义为异步方式。
创建 AnnotationConfigApplicationContext 的过程中,会执行refresh()中的initApplicationEventMulticaster()方法。该方法先获取bean工厂,然后判断工厂是否包含了beanName 为 applicationEventMulticaster的bean。如果包含了,则获取该bean,赋值给applicationEventMulticaster 属性。如果没有,则创建一个 SimpleApplicationEventMulticaster 对象,并且赋值给 applicationEventMulticaster 。实现了源码如下:
监听器的注册有两种,通过实现 ApplicationListener接口或者添加@EventListener注解。
注册的逻辑实现在refresh()中的registerListeners()方法里面。第一步,先获取当前ApplicationContext中已经添加的 applicationListeners(SpringMVC源码中有用到),遍历添加到多播器中。第二步,获取实现了ApplicationListener接口的listenerBeanNames集合,添加至多播器中。第三步,判断是否有早期事件,如果有则发起广播。
思考一下,上面的代码中第二步为啥添加的是listenerBeanName?
如果监听器是懒加载的话(即有@Lazy 注解)。那么在这个时候创建监听器显然是不对的,这个时候不能创建监听器。所以添加监听器到多播器的具体逻辑放在初始化具体的监听器之后。通过 BeanPostProcessor 的接口实现。具体的实现类是 ApplicationListenerDetector 。这个类是在 refreah()中prepareBeanFactory()方法中添加的。代码如下:
在创建 AnnotationConfigApplicationContext 的构造方法中,会执行org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object) 方法。这个方法中会添加两个 beanDefs, 代码如下:
EventListenerMethodProcessor:事件监听器的BeanFactory后置处理器,在前期会创建 DefaultEventListenerFactory ,后期在创建好Bean之后,根据 EventListener 属性,调用DefaultEventListenerFactory创建具体的 ApplicationListenerMethodAdapter 。
DefaultEventListenerFactory:监听器的创建工厂,用来创建 ApplicationListenerMethodAdapter 。
EventListenerMethodProcessor 的类继承图如下:
在refreash的invokeBeanFactoryPostProcessors()中会调用 org.springframework.context.event.EventListenerMethodProcessor#postProcessBeanFactory方法,获取EventListenerFactory 类型的 Bean。代码如下:
在 org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons 方法中,创建完所有的单例Bean 之后,会遍历所有Bean是否实现了 SmartInitializingSingleton 接口。如果实现接口会执行该 Bean 的 afterSingletonsInstantiated() 方法。代码如下:
org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated 中会调用私有方法 processBean()进行 ApplicationEventAdatper 的创建。代码如下:
可以通过调用 org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType) 方法进行事件的调用。代码如下:
SimpleApplicationEventMulticaster 中的 multicasEvent,invokeListener,doInvokeListener 三个方法代码如下:
SpringMVC中就是通过Spring的事件机制进行九大组件的初始化。
监听器定义在FrameworkServlet类中,作为内部类。代码如下:
监听器的添加在org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext 中进行。通过SourceFilteringListener进行包装。添加代码如下:
在refresh中的registerListeners方法进行添加,代码如下:
在refresh中的finishRefresh()方法中,会调用publishEvnet(new ContextRefreshedEvent(this))发布事件。进行多播器广播,代码如下
最终会调到FrameworkServlet.this.onApplicationEvent(event)。
以上是关于Unity C# 事件监听和广播的主要内容,如果未能解决你的问题,请参考以下文章