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# 事件监听和广播的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Unity C# 中将 OnPointerDown 侦听器添加到滑块句柄? [复制]

unity打包webgl程序和js键盘监听事件冲突的问题。

Unity启动事件-监听:InitializeOnLoad

Unity中的事件监听

unity的C#学习——委托事件和匿名方法

我想写个unity3D C#脚本用鼠标拖动物体,该怎么办呀,