IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity快速入门之三 脚本与事件 -> 正文阅读

[游戏开发]Unity快速入门之三 脚本与事件

Unity快速入门之一 3D基础概念、Camera、Canvas RenderMode的几种方式对比_翕翕堂

Unity快速入门之二 GUI Transform 详解_翕翕堂

Unity快速入门之三 脚本与事件_翕翕堂

资源管理待定……

3D模型待定……

……

这一篇主要是从代码了,代码内容是纯示例内容,可以有更多更好的思路扩展。

本篇幅会比较长,涉及到比较多的内容,请坐好准备发车~~~~

前置准备就需要自己准备好了:

1、准备 visual studio?2017 IDE。

2、安装 vs for unity 插件。

3、C#语法基础或其他语言基础。


目录

C# Script(脚本)

脚本创建

挂载脚本

前置场景与知识准备

Unity内置事件

Unity按钮(Button)点击(OnClick)事件

文本(Text)触发(Trigger)事件

自定义(Custom)触发(Trigger)事件

自定义事件系统

铺垫

EventManagerUnity(UnityEvent && UnityAction)

EventManagerDelegate

EventManagerFunc

EventManagerAction

?EventManagerFunc

事件系统总结


C# Script(脚本)

脚本创建

Project视图下,Assets及其子目录,右键菜单->Create->C# Script:

创建C#脚本方式

建一个Script文件夹,创建一个C#脚本,默认脚本名字 New Behaviour Script,点击脚本,可以在 Inspector中可以显示脚本内容:

第一个默认脚本

挂载脚本

将创建的脚本当成组件挂载到3D对象身上

Hierarchy视图中选中一个3D对象,在其Inspector视图中,最下方点击 Add Componet 按钮,输入框中搜索刚才创建的脚本 NewBehaviourScript,就可以看到它了,点击挂在到这个对象上:

脚本被挂载了

先用 vs2017打开这个脚本,双击上方图片中黄色位置即可(需要配置好环境,不展开了):

vs2017下脚本

前置场景与知识准备

由于按钮是最通用的控件,从它开始切入。首先创建一个基础场景:

  • 按钮控件:button event
  • 文本控件:text event
  • 文本控件:custom trigger
  • 3D Cube:Cube
一个按钮、两个文本、一个cube

创建一个脚本 ButtonEvent,并放到 button event控件 上,先介绍几个脚本的基本内容:

MonoBehaviour:Unity对象的基础类,里面拥有很完整的生命周期,继承于这个类的脚本都可以当成组件挂载到具体的对象身上。

void Start():首次加载以及对象再次激活的时候会被调用,这次我们只要用到它就好了。

gameObject:此脚本组件说挂载的对象,由于我们要使用Button及其点击事件,Button也是一个独立组件,要由它来获取。

Unity内置事件

Unity按钮(Button)点击(OnClick)事件

流程上:

  • 创建点击处理函数
  • 找到按钮
  • 绑定处理函数到按钮

按钮点击函数上代码实现方式,常见的四种:

  • 成员函数
  • 匿名委托
  • 实例委托
  • lambda

这些都是基于C#语法的,语法规则就不做展开了。

下面开始说 Button怎么绑定点击响应事件,首先,保留脚本最简形式:

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

public class ButtonEvent : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {

    }
}

增加一个响应我们点击事件的成员函数作为处理函数:

public class ButtonEvent : MonoBehaviour
{
    void Start()...

    public void OnClickFunction00()
    {
        Debug.Log("AddListener - 委托成员函数 - OnClickFunction00");
    }
}

在Start中找到我们要的按钮,并将处理函数绑定到按钮上:

// 使用控件需要添加的命名空间
using UnityEngine.UI;

public class ButtonEvent : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // AddListener - 成员函数 - OnClickFunction
        gameObject.GetComponent<Button>().onClick.AddListener(OnClickFunction00);
    }

    public void OnClickFunction00()...
}

由于使用了Butoon,这个属于 UnityEngine.UI 之下,所以开头要使用 using?UnityEngine.UI

获取组件通过 gameObject.GetComponent<T>() 接口,我们这用的Button,所以是gameObject.GetComponent<Button>() ,下一步要绑定处理函数到按钮上,上面已经写出来了,就是? onClick.AddListener(OnClickFunction00),这是怎么来的,可以翻官网或度娘:Unity - Manual: UnityEventshttps://docs.unity3d.com/Manual/UnityEvents.html

也可以看下它的实现,我们点开Button看看:

Unity UI Button

可以看到Unity的UI Button,有一个OnClick,来自于ButtonClickedEvent,而它又来自于UnityEvent,另外Button继承于几个xxxHandler 接口

我们继续点开UnityEvent看看:

UnityEvent

看到 Reflection、MethodInfo、Invoke,大概就知道,这东西是靠反射来实现的,这里就不继续往 UnityEventBase展开了,但是可以给没接触过反射的朋友大概说下,反射(Reflection)就是通过拿到目标类及其类下所有的变量、函数等信息,做额外存储,在运行时可以通个这些额外存储的信息以及类的实例对象进行调用,但是需要占用额外内存,性能也比较低。

好了,我们继续。

它的目标函数怎么绑定的,这个很明显:public void AddListener(UnityAction call);

那么UnityAction是什么玩意儿,点进去看看:public delegate void UnityAction();

UnityAction

一个委托(Delegate),类似于C++的函数指针形式,但是使用的时候实际上是对象。

掌握了这些信息,我们可以继续拓展我们的事件了。(对于上面 反射、委托不懂想要深究的,可以找其他资料进行了解)

首先看下上面的结果:

Debug窗口信息

没毛病,点击按钮,确实输出了我们需要的消息。既然 UnityAction 是个 delegate,那么我们应该可以直接使用委托匿名函数来作为处理函数。

{
    // Start is called before the first frame update
    void Start()
    {
        ...

        // AddListener - 委托匿名函数 - delegate
        gameObject.GetComponent<Button>().onClick.AddListener(delegate
        {
            Debug.Log("AddListener - 委托匿名函数 - delegate");
        });
    }

    public void OnClickFunction00()...
}

?也可以使用委托实例,这里显示的调用了 UnityAction,需要增加 using UnityEngine.Events

// 使用UnityAction需要增加这个
using UnityEngine.Events

public class ButtonEvent : MonoBehaviour{
    // Start is called before the first frame update
    void Start()
    {
        ...

        // AddListener - 委托实例 - OnClickFunction01
        gameObject.GetComponent<Button>().onClick.AddListener(
            new UnityAction(OnClickFunction01)
        );
    }

    public void OnClickFunction00()...

    public void OnClickFunction01()
    {
        Debug.Log("实例委托 - OnClickFunction02");
    }
}

委托lambda:?

{
    // Start is called before the first frame update
    void Start()
    {
        ...

        // AddListener - 委托lambda表达式 - lambda
        gameObject.GetComponent<Button>().onClick.AddListener(
            () => Debug.Log("lambda表达式委托 - lambda")
        );
    }

    public void OnClickFunction00()...
}

?由于UnityAction是委托,所以可以不调用AddListener,直接使用多路广播,最后一次性绑定:

{
    //创建UnityAction成员变量
    public UnityAction m_untiyAction;

    // Start is called before the first frame update
    void Start()
    {
        // 多路广播,委托函数
        m_untiyAction += OnClickFunction01;

        // 委托匿名函数
        m_untiyAction += delegate
        {
            Debug.Log("多路广播 - 匿名函数委托 - delegate");
        };

        // 委托实例 - OnClickFunction01
        m_untiyAction += new UnityAction(OnClickFunction01);

        // 委托lambda表达式
        m_untiyAction += () => { Debug.Log("多路广播 - lambda表达式委托 - lambda"); };

        // AddListener - UnityAction - delegate
        // 将UnityAction绑定到AddListener
        gameObject.GetComponent<Button>().onClick.AddListener(m_untiyAction);
    }

    public void OnClickFunction00()...
}

?放个完整的ButtonEvent代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;

public class ButtonEvent : MonoBehaviour
{
    //https://docs.unity3d.com/Manual/UnityEvents.html
    public UnityAction m_untiyAction;

    // Start is called before the first frame update
    void Start()
    {
        // AddListener - 成员函数 - OnClickFunction
        gameObject.GetComponent<Button>().onClick.AddListener(OnClickFunction00);

        // AddListener - 委托匿名函数 - delegate
        gameObject.GetComponent<Button>().onClick.AddListener(delegate
        {
            Debug.Log("AddListener - 委托匿名函数 - delegate");
        });

        // AddListener - 委托实例 - OnClickFunction01
        gameObject.GetComponent<Button>().onClick.AddListener(new UnityAction(OnClickFunction01));

        // AddListener - 委托lambda表达式 - lambda
        gameObject.GetComponent<Button>().onClick.AddListener(
            () => Debug.Log("lambda表达式委托 - lambda")
        );

        // 多路广播,委托函数
        m_untiyAction += OnClickFunction01;

        // 委托匿名函数
        m_untiyAction += delegate
        {
            Debug.Log("多路广播 - 匿名函数委托 - delegate");
        };

        // 委托实例 - OnClickFunction01
        m_untiyAction += new UnityAction(OnClickFunction01);

        // 委托lambda表达式
        m_untiyAction += () => { Debug.Log("多路广播 - lambda表达式委托 - lambda"); };

        // AddListener - UnityAction - delegate
        gameObject.GetComponent<Button>().onClick.AddListener(m_untiyAction);
    }

    public void OnClickFunction00()
    {
        Debug.Log("委托成员函数 - OnClickFunction00");
    }

    public void OnClickFunction01()
    {
        Debug.Log("实例委托 - OnClickFunction02");
    }
}

文本(Text)触发(Trigger)事件

上面已经讲了Button,但是如果文本我们也需要处理事件,可以按照上面的方法去查看,可以发现,是不存在类似于OnClick类似事件的。这里可以使用 EventTrigger:

Event Trigger | Unity UI | 1.0.0https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/script-EventTrigger.html创建一个TextEvent脚本,挂载到 text event 文本控件上,直接放代码,可以看完后面解释再回过来看代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public class TextEvent : MonoBehaviour
{
    //https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/script-EventTrigger.html

    // Start is called before the first frame update
    void Start()
    {
        // 创建事件触发的响应实例
        EventTrigger.Entry entry = new EventTrigger.Entry();
        entry.eventID = EventTriggerType.PointerClick;

        // 添加事件响应的处理函数到响应实例,PS:此处也是AddListener,可以参考ButtonEvent,用多种方式注册监听函数
        entry.callback.AddListener(delegate (BaseEventData data)
        {
            Debug.Log("文本被点击了 delegate");
        });

        entry.callback.AddListener(data =>
        {
            Debug.Log("文本被点击了 lambda");
        });

        // 给需要监听事件的对象,添加事件触发组件才能监听到事件
        EventTrigger trigger = gameObject.AddComponent<EventTrigger>();

        // 将响应实例添加到监听触发组件,PS:可以添加多个,比如下面重复加了两次,就触发两次。
        trigger.triggers.Add(entry);
        trigger.triggers.Add(entry);
    }
}

可以看到,通过 EventTrigger.Entry 取代了 ButtonEvent 中的 Button 来作为事件绑定的宿主。

  • 创建?EventTrigger.Entry
  • 绑定处理函数到?EventTrigger.Entry
  • 给文本添加 EventTrigger 组件
  • 将?EventTrigger.Entry 添加到?EventTrigger 组件。

看下结果:

Debug窗口信息

?同样的我们可以进入到EventTrigger中看看:

EventTrigger

可以看到,这里提示 delegates不要用了,推荐使用下面的triggers,而triggers是个EntryList

Entry又是由触发类型和触发事件组成,最后,触发事件又是来自于UnityEvent

所以实质上用法是一样的,可以看到EventTrigger也是继承于 XXXHandler

自定义(Custom)触发(Trigger)事件

其他UI控件的响应就不做展开了,因为可以通过上面类似的办法处理,下面说说自定义触发事件,通过观察Button和EventTrriger,可以看到,都是通过UnityEvent、XXXHandler的方式实现的,所以我们自己来实现一个。

XXXHandler有哪些呢,官方链接:

Supported Events | Unity UI | 1.0.0https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/SupportedEvents.html同样的创建一个脚本 CustomTrigger,直接先放代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

// 参考EventTrriget,里面已经列下了大部分Unity : https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/script-EventTrigger.html
// 17种可以自定义扩展实现的事件:https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/SupportedEvents.html

public class CustomEventTrigger : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler, IPointerClickHandler, IInitializePotentialDragHandler, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler, IScrollHandler, IUpdateSelectedHandler, ISelectHandler, IDeselectHandler, IMoveHandler, ISubmitHandler, ICancelHandler
{
    public void OnPointerClick(PointerEventData eventData)
    {
        Debug.Log("\n1 CustomEventTrigger OnPointerClick--->"+ eventData.ToString());
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        Debug.Log("\n2 CustomEventTrigger OnPointerEnter:--->" + eventData.ToString());
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        Debug.Log("\n3 CustomEventTrigger OnPointerExit:--->" + eventData.ToString());
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        Debug.Log("\n4 CustomEventTrigger OnPointerDown:--->" + eventData.ToString());
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        Debug.Log("\n5 CustomEventTrigger OnPointerUp:--->" + eventData.ToString());
    }

    public void OnInitializePotentialDrag(PointerEventData eventData)
    {
        Debug.Log("\n6 CustomEventTrigger OnInitializePotentialDrag:--->" + eventData.ToString());
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        Debug.Log("\n7 CustomEventTrigger OnBeginDrag:--->" + eventData.ToString());
    }

    private int _countDrag;
    public void OnDrag(PointerEventData eventData)
    {
        if (_countDrag == 0)
            Debug.Log("\n8 CustomEventTrigger OnDrag:--->" + eventData.ToString());

        ++_countDrag;
        //if (++_countDrag == 15)
        //    _countDrag = 0;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        Debug.Log("\n9 CustomEventTrigger OnEndDrag:--->" + eventData.ToString());
    }

    public void OnDrop(PointerEventData eventData)
    {
        Debug.Log("\n10 CustomEventTrigger OnDrop--->" + eventData.ToString());
    }

    public void OnScroll(PointerEventData eventData)
    {
        Debug.Log("\n11 CustomEventTrigger OnScroll:--->" + eventData.ToString());
    }

    private int _countSelectedUpdate;
    public void OnUpdateSelected(BaseEventData eventData)
    {
        if (_countSelectedUpdate == 0)
            Debug.Log("\n12 CustomEventTrigger OnUpdateSelected:--->" + eventData.ToString());

        ++_countSelectedUpdate;
        //if (++_countSelectedUpdate == 1000)
        //    _countSelectedUpdate = 0;
    }

    public void OnSelect(BaseEventData eventData)
    {
        Debug.Log("\n13 CustomEventTrigger OnSelect:--->" + eventData.ToString());
    }

    public void OnDeselect(BaseEventData eventData)
    {
        Debug.Log("\n14 CustomEventTrigger OnDeselect:--->" + eventData.ToString());
    }

    public void OnMove(AxisEventData eventData)
    {
        Debug.Log("\n15 CustomEventTrigger OnMove:--->" + eventData.ToString()
            //+ ":currentInputModule(" + eventData.currentInputModule.ToString()
            + "),moveDir(" + eventData.moveDir.ToString()
            + "),moveVector(" + eventData.moveVector.ToString()
            + "),selectedObject(" + eventData.selectedObject.ToString());
    }

    public void OnSubmit(BaseEventData eventData)
    {
        Debug.Log("\n16 CustomEventTrigger OnSubmit:--->" + eventData.ToString());
    }

    public void OnCancel(BaseEventData eventData)
    {
        Debug.Log("\n17 CustomEventTrigger OnCancel:--->" + eventData.ToString());
    }

    // EventTrigger中做了封装,这里我们直接拆分实现
    public class BaseEventTrigger : UnityEvent<BaseEventData> { }
    BaseEventTrigger baseEventTrigger = new BaseEventTrigger();

    public class PointEventTrigger : UnityEvent<PointerEventData> { }
    PointEventTrigger pointEventTrigger = new PointEventTrigger();

    public class AxisEventTrigger : UnityEvent<AxisEventData> { }
    AxisEventTrigger axisEventTrigger = new AxisEventTrigger();

    // Start is called before the first frame update
    void Start()
    {
        pointEventTrigger.AddListener(OnPointerClick);
        pointEventTrigger.AddListener(OnPointerEnter);
        pointEventTrigger.AddListener(OnPointerExit);
        pointEventTrigger.AddListener(OnPointerDown);
        pointEventTrigger.AddListener(OnPointerUp);
        pointEventTrigger.AddListener(OnInitializePotentialDrag);
        pointEventTrigger.AddListener(OnBeginDrag);
        pointEventTrigger.AddListener(OnDrag);
        pointEventTrigger.AddListener(OnEndDrag);
        pointEventTrigger.AddListener(OnDrop);
        pointEventTrigger.AddListener(OnScroll);
        pointEventTrigger.AddListener(OnUpdateSelected);
        pointEventTrigger.AddListener(OnSelect);
        pointEventTrigger.AddListener(OnDeselect);
        axisEventTrigger.AddListener(OnMove);
        pointEventTrigger.AddListener(OnSubmit);
        pointEventTrigger.AddListener(OnCancel);

        EventSystem.current.SetSelectedGameObject(gameObject);
    }
}

目前来说共有17中XXXHandler,需要挨个实现,而UI能直接响应的是:

移入、移出、点击、按下、抬起、初始化拖动、开始拖动、拖动、结束拖动、拖入、滑动。

而下面的 更新选中、选中、移动、提交、取消 这些我们需要先设置下对象,使用:

EventSystem.current.SetSelectedGameObject(gameObject);

PS:可以试试将这个脚本挂在到之前创建的Cube上,可以看到Cube对选中到取消相关的事件也能产生反应。


自定义事件系统

铺垫

上面的大部分都是使用Unity内置的,对UGUI有用,但是我们的事件通知触发这种形式绝不限于UI的使用,所以需要对事件有个统一管理的地方。这一部分的目的,是为了熟悉C#常用的几个主要语法和事件系统设计思路。

四个主要部分

  • 事件注册:响应者处理事件的函数,需要注册到事件系统中,等待发起者发送事件来响应。
  • 事件反注册:可以解除响应者的处理函数。
  • 事件通知(多对多):多个发起者可以发送同一个事件,有多个响应者可以处理这个事件,但是不需要返回值,发起者后面继续该干嘛干嘛。
  • 事件调用(多对一):多个发起者可以发送同一个事件,但是只有一个响应者可以处理这个事件,且会有一个处理结果,发起者会根据处理结果决定需要干什么。

实现方式,有很多种,此篇介绍四种:

  • UnityEvent && UnityAction
    • 使用UnityEvent和UnityAction实现一个全局可以的脱离UI的事件系统,这个事件系统没有事件调用(事件调用是指通知后可以在通知方)。
  • Delegate
    • 使用委托为核心实现事件系统。
  • Func || Action
    • 使用Func或者Action为核心实现事件系统。
  • Reflection
    • 使用反射为核心实现事件系统。

使用设计模式:

  • 单例模式
  • 中介者模式

事件ID定义EVENT_ID

// 事件注册ID
public enum EVENT_ID
{
    ED_UNKNOW,
    ED_NO_PARAM_NO_RETURN,  // 无参无返回值事件
    ED_PARAM_NO_RETURN,     // 有参无返回值事件
    ED_NO_PARAM_RETURN,     // 无参有返回值事件
    ED_PARAM_RETURN,        // 有参有返回值事件
}

EventManagerUnity(UnityEvent && UnityAction)

场景搭建

  • 3D对象:Cube(挂载 Sample_02_Unity_Cube01.cs 脚本,响应者,注册处理事件)
  • Canvas:Canvas(挂载 Sample_02_Unity_UI.cs 脚本,发起者,发起事件通知)
  • 按钮控件:点击发送Cube事件1 (对应事件 ED_NO_PARAM_NO_RETURN
  • 按钮控件:点击发送Cube事件2 (对应事件 ED_PARAM_NO_RETURN
Game视图

事件系统:EventMangerUnity.cs

  • RegEvent 事件注册,通过object[]来传递不定参数。
  • UnRegEvent 事件反注册,通过EVENT_ID来解除所有注册了的处理函数。
  • SendEvent 事件通知,通知对应EVENT_ID下注册的所有处理函数。

由于UnityActionpublic delegate void UnityAction<T0>(T0 arg0); 由Unity定义的,只能是void返回值,所以无法很好的处理事件调用,这里就不考虑扩展事件调用了。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;

public class EventManagerUnity
{
    private static EventManagerUnity me = new EventManagerUnity();
    public static EventManagerUnity GetMe()
    {
        return me;
    }

    // 有参无返回值事件
    Dictionary<EVENT_ID, UnityEvent<object[]>> dataEventTriggers = new Dictionary<EVENT_ID, UnityEvent<object[]>>();

    // 注册事件
    public void RegEvent(EVENT_ID id, UnityAction<object[]> action)
    {
        if (!dataEventTriggers.ContainsKey(id))
            dataEventTriggers.Add(id, new UnityEvent<object[]>());

        dataEventTriggers[id].AddListener(action);
        Debug.Log(string.Format("注册 {0} 到 dataEventTriggers:", id.ToString()));
    }
    // 根据ID反注册事件
    public void UnRegEvent(EVENT_ID id)
    {
        if (dataEventTriggers.ContainsKey(id))
        {
            Debug.Log(string.Format("从 dataEventTriggers 反注册 {0}:", id.ToString()));
            dataEventTriggers.Remove(id);
            return;
        }
    }
    // 触发事件
    public void SendEvent(EVENT_ID id, params object[] values)
    {
        if (dataEventTriggers.ContainsKey(id))
        {
            Debug.Log(string.Format("从 dataEventTriggers 发送 {0}:", id.ToString()));
            dataEventTriggers[id].Invoke(values);
        }
    }
}

响应者:Sample_02_Unity_Cube01.c

这里使用lambda来实现事件处理。

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

public class Sample_02_Unity_Cube01 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        EventManagerUnity.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN,
        (object[] data) =>
        {
            Debug.Log("响应 无参无返回值事件"); 
        });

        EventManagerUnity.GetMe().RegEvent(EVENT_ID.ED_PARAM_NO_RETURN,
        (object[] data) =>
        {
            Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", data[0], data[1]));
        });
    }
}

发起者:Sample_02_Unity_UI.cs?

由于我们发起者的脚本是挂载Canvas上的,所以这里需要使用?gameObject.transform.Find("控件名称") 来找到对应的子按钮控件。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

public class Sample_02_Unity_UI : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        gameObject.transform.Find("点击发送Cube01事件1").GetComponent<Button>().onClick.AddListener(
        ()=>
        {
            Debug.Log("点击了【点击发送Cube01事件1】");
            EventManagerUnity.GetMe().SendEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
        });

        gameObject.transform.Find("点击发送Cube01事件2").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件2】");
            EventManagerUnity.GetMe().SendEvent(EVENT_ID.ED_PARAM_NO_RETURN, "Val Input", 1);
        });

        gameObject.transform.Find("销毁Cube01点击事件").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【销毁Cube01点击事件】");
            EventManagerUnity.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
            EventManagerUnity.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_NO_RETURN);
        });
    }
}

点击测试

依次点击?点击发送Cube01事件1、点击发送Cube01事件2、销毁Cube01点击事件。

然后再来各点击一次。查看输出结果:

Debug窗口信息

?结果很完全符合逻辑,在反注册后,按钮也只响应了点击,并没有触发事件。

EventManagerDelegate

场景搭建

  • 3D对象:Cube(挂载 Sample_02_Delegate_Cube01.cs 脚本,响应者,注册处理事件)
  • Canvas:Canvas(挂载 Sample_02_Delegate_UI.cs 脚本,发起者,发起事件通知)
  • 按钮控件:点击发送Cube事件1 (对应事件 ED_NO_PARAM_NO_RETURN
  • 按钮控件:点击发送Cube事件2 (对应事件 ED_PARAM_NO_RETURN
  • 按钮控件:点击发送Cube事件3 (对应事件 ED_NO_PARAM_RETURN
  • 按钮控件:点击发送Cube事件4 (对应事件 ED_PARAM_RETURN
Game视图

事件系统:EventManagerDelegate.cs

  • RegEvent 事件注册,通过object[]来传递不定参数,通过 isSend 决定是通知还是调用。
  • UnRegEvent 事件反注册,通过EVENT_ID来解除所有注册了的处理函数。
  • SendEvent 事件通知,通知对应EVENT_ID下注册的所有处理函数。
  • Invoke 事件调用,调用对应EVENT_ID下注册的唯一处理函数,并处理返回结果。

这里我们考虑通知和调用的区分。

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

public class EventManagerDelegate
{
    private static EventManagerDelegate me = new EventManagerDelegate();
    public static EventManagerDelegate GetMe()
    {
        return me;
    }
   
    public delegate object DataEventFunctionRet(object[] data);

    Dictionary<EVENT_ID, DataEventFunctionRet> dataEventTriggersRet = new Dictionary<EVENT_ID, DataEventFunctionRet>();

    public void RegEvent(EVENT_ID id, DataEventFunctionRet action, bool isSend = true)
    {
        if (isSend)
        {
            if (!dataEventTriggersRet.ContainsKey(id))
                dataEventTriggersRet.Add(id, new DataEventFunctionRet(action));
            else
                dataEventTriggersRet[id] += action;
            Debug.Log(string.Format("注册 {0} 到 sendEventTriggersRet send:", id.ToString()));
        }
        else
        {
            if (!dataEventTriggersRet.ContainsKey(id))
                dataEventTriggersRet.Add(id, new DataEventFunctionRet(action));
            else
                dataEventTriggersRet[id] = action;
            Debug.Log(string.Format("注册 {0} 到 dataEventTriggersRet invoke:", id.ToString()));
        }
        
    }

    public void UnRegEvent(EVENT_ID id)
    {
        if (dataEventTriggersRet.ContainsKey(id))
        {
            dataEventTriggersRet.Remove(id);
            Debug.Log(string.Format("从 dataEventTriggersRet 反注册 {0}:", id.ToString()));
        }
    }

    // 触发事件
    public void SendEvent(EVENT_ID id, params object[] values)
    {
        if (dataEventTriggersRet.ContainsKey(id))
        {
            if (dataEventTriggersRet.ContainsKey(id))
            {
                Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0}:", id.ToString()));
                dataEventTriggersRet[id].Invoke(values);
            }
        }
    }

    // 使用代理可以不用区分输入和输出数据结构,用一个函数既可以发送事件,也可以得到返回值
    public object Invoke(EVENT_ID id, params object[] values)
    {

        if (dataEventTriggersRet.ContainsKey(id))
        {
            Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0}:", id.ToString()));
            return dataEventTriggersRet[id].Invoke(values);
        }

        return null;
    }
}

响应者:Sample_02_Delegate_Cube01.c

这里ED_NO_PARAM_NO_RETURN事件注册了两次来示例。

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

public class Sample_02_Delegate_Cube01 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        EventManagerDelegate.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN,
        (object[] data) =>
        {
            Debug.Log("响应 无参无返回值事件01");
            return null;
        });

        EventManagerDelegate.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN,
        (object[] data) =>
        {
            Debug.Log("响应 无参无返回值事件02");
            return null;
        });

        EventManagerDelegate.GetMe().RegEvent(EVENT_ID.ED_PARAM_NO_RETURN,
        (object[] data) =>
        {
            Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", data[0], data[1]));
            return null;
        });

        EventManagerDelegate.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_RETURN,
        (object[] data) =>
        {
            Debug.Log(string.Format("响应 无参有返回值【{0}】事件", "Ret Output"));
            return "Ret Output";
        }, false);

        EventManagerDelegate.GetMe().RegEvent(EVENT_ID.ED_PARAM_RETURN,
        (object[] data) =>
        {
            Debug.Log(string.Format("响应 有参【{0},{1}】有返回值【{2}】事件", data[0], data[1], "Ret Output"));
            return "Ret Output";
        }, false);
    }
}

发起者:Sample_02_Delegate_UI.cs?

需要注意这里Invoke时候的返回结果也需要处理打印出来。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

public class Sample_02_Delegate_UI : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        gameObject.transform.Find("点击发送Cube01事件1").GetComponent<Button>().onClick.AddListener(
        ()=>
        {
            Debug.Log("点击了【点击发送Cube01事件1】");
            EventManagerDelegate.GetMe().SendEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
        });

        gameObject.transform.Find("点击发送Cube01事件2").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件2】");
            EventManagerDelegate.GetMe().SendEvent(EVENT_ID.ED_PARAM_NO_RETURN, "Val input", 1);
        });

        gameObject.transform.Find("点击发送Cube01事件3").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件3】");
            Debug.Log(string.Format("收到无参有返回值调用结果:{0}", EventManagerDelegate.GetMe().Invoke(EVENT_ID.ED_NO_PARAM_RETURN)));
        });

        gameObject.transform.Find("点击发送Cube01事件4").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件4】");
            Debug.Log(string.Format("收到有参有返回值调用结果:{0}", EventManagerDelegate.GetMe().Invoke(EVENT_ID.ED_PARAM_RETURN, "Val input", 2)));
        });

        gameObject.transform.Find("销毁Cube01点击事件").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【销毁Cube01点击事件】");
            EventManagerDelegate.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
            EventManagerDelegate.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_NO_RETURN);
            EventManagerDelegate.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_RETURN);
            EventManagerDelegate.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_RETURN);
        });
    }
}

点击测试

依次点击?点击发送Cube01事件1、点击发送Cube01事件2、点击发送Cube01事件3、点击发送Cube01事件4、销毁Cube01点击事件。

然后再来各点击一次。查看输出结果:

Debug窗口信息

EventManagerFunc

场景搭建(同 EventManagerDelegate)

  • 3D对象:Cube(挂载 Sample_02_Unity_Cube01.cs 脚本,响应者,注册处理事件)
  • Canvas:Canvas(挂载 Sample_02_Unity_UI.cs 脚本,发起者,发起事件通知)
  • 按钮控件:点击发送Cube事件1 (对应事件 ED_NO_PARAM_NO_RETURN
  • 按钮控件:点击发送Cube事件2 (对应事件 ED_PARAM_NO_RETURN
  • 按钮控件:点击发送Cube事件3 (对应事件 ED_NO_PARAM_RETURN
  • 按钮控件:点击发送Cube事件4 (对应事件 ED_PARAM_RETURN
Game视图

事件系统:EventManagerFunc.cs(同 EventManagerDelegate)

  • RegEvent 事件注册,通过object[]来传递不定参数,通过 isSend 决定是通知还是调用。
  • UnRegEvent 事件反注册,通过EVENT_ID来解除所有注册了的处理函数。
  • SendEvent 事件通知,通知对应EVENT_ID下注册的所有处理函数。
  • Invoke 事件调用,调用对应EVENT_ID下注册的唯一处理函数,并处理返回结果。

除了?dataEventTriggersRet??由于语法的不同,会导致流程上模板Func委托(Delegate)会有微弱的不同,其余一样。

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

// Func是包含返回值的,可以和delegate差不多
public class EventManagerFunc
{
    private static EventManagerFunc me = new EventManagerFunc();
    public static EventManagerFunc GetMe()
    {
        return me;
    }

    // public delegate TResult Func<in T, out TResult>(T arg);
    Dictionary<EVENT_ID, Func<object[], object>> dataEventTriggersRet = new Dictionary<EVENT_ID, Func<object[], object>>();

    public void RegEvent(EVENT_ID id, Func<object[], object> func, bool isSend = true)
    {
        if (isSend)
        {
            if (!dataEventTriggersRet.ContainsKey(id))
                dataEventTriggersRet.Add(id, new Func<object[], object>(func));
            else
                dataEventTriggersRet[id] += func;
            Debug.Log(string.Format("注册 {0} 到 sendEventTriggersRet send:", id.ToString()));
        }
        else
        {
            if (!dataEventTriggersRet.ContainsKey(id))
                dataEventTriggersRet.Add(id, new Func<object[], object>(func));
            else
                dataEventTriggersRet[id] = func;
            Debug.Log(string.Format("注册 {0} 到 dataEventTriggersRet invoke:", id.ToString()));
        }
    }

    public void UnRegEvent(EVENT_ID id)
    {
        if (dataEventTriggersRet.ContainsKey(id))
        {
            dataEventTriggersRet.Remove(id);
            Debug.Log(string.Format("从 dataEventTriggersRet 反注册 {0}:", id.ToString()));
        }
    }

    // 触发事件
    public void SendEvent(EVENT_ID id, params object[] values)
    {
        if (dataEventTriggersRet.ContainsKey(id))
        {
            Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0}:", id.ToString()));
            dataEventTriggersRet[id].Invoke(values);
        }
    }

    // 使用代理可以不用区分输入和输出数据结构,用一个函数既可以发送事件,也可以得到返回值
    public object Invoke(EVENT_ID id, params object[] values)
    {

        if (dataEventTriggersRet.ContainsKey(id))
        {
            Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0}:", id.ToString()));
            return dataEventTriggersRet[id].Invoke(values);
        }

        return null;
    }
}

响应者:Sample_02_F_A_Cube01.c

EventManagerDelegate 使用完全一致:

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

public class Sample_02_F_A_Cube01 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        EventManagerFunc.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN,
        (object[] data) =>
        {
            Debug.Log("响应 无参无返回值事件");
            return null;
        });

        EventManagerFunc.GetMe().RegEvent(EVENT_ID.ED_PARAM_NO_RETURN,
        (object[] data) =>
        {
            Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", data[0], data[1]));
            return null;
        });

        EventManagerFunc.GetMe().RegEvent(EVENT_ID.ED_NO_PARAM_RETURN,
        (object[] data) =>
        {
            Debug.Log(string.Format("响应 无参有返回值【{0}】事件", "Ret Output"));
            return "Ret Output";
        }, false);

        EventManagerFunc.GetMe().RegEvent(EVENT_ID.ED_PARAM_RETURN,
        (object[] data) =>
        {
            Debug.Log(string.Format("响应 有参【{0},{1}】有返回值【{2}】事件", data[0], data[1], "Ret Output"));
            return "Ret Output";
        }, false);
    }
}

发起者:Sample_02_F_A_UI.cs?

EventManagerDelegate 使用完全一致:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

public class Sample_02_F_A_UI : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        gameObject.transform.Find("点击发送Cube01事件1").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件1】");
            EventManagerFunc.GetMe().SendEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
        });

        gameObject.transform.Find("点击发送Cube01事件2").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件2】");
            EventManagerFunc.GetMe().SendEvent(EVENT_ID.ED_PARAM_NO_RETURN, "Val input", 1);
        });

        gameObject.transform.Find("点击发送Cube01事件3").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件3】");
            Debug.Log(string.Format("收到无参有返回值调用结果:{0}", EventManagerFunc.GetMe().Invoke(EVENT_ID.ED_NO_PARAM_RETURN)));
        });

        gameObject.transform.Find("点击发送Cube01事件4").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件4】");
            Debug.Log(string.Format("收到有参有返回值调用结果:{0}", EventManagerFunc.GetMe().Invoke(EVENT_ID.ED_PARAM_RETURN, "Val input", 2)));
        });

        gameObject.transform.Find("销毁Cube01点击事件").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【销毁Cube01点击事件】");
            EventManagerFunc.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
            EventManagerFunc.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_NO_RETURN);
            EventManagerFunc.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_RETURN);
            EventManagerFunc.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_RETURN);
        });
    }
}

点击测试

EventManagerDelegate 使用完全一致

Debug窗口信息

EventManagerAction

模板Action与Func用法完全一样,不同的是,Func模板最后一个参数是返回值,而Action没有返回值:

Func:public delegate TResult Func<in T, out TResult>(T arg);

Action:public delegate void Action<in T>(T obj);

由于没有返回值只有Send,不详述了,按照UnityAction只写个EventManagerAction示例:

// Action基本与Func雷同,只是Action不能有返回值,如果扩展的话可以像UnityAction一样
public class EventManagerAction
{
    private static EventManagerAction me = new EventManagerAction();
    public static EventManagerAction GetMe()
    {
        return me;
    }

    // public delegate void Action<in T>(T obj);
    Dictionary<EVENT_ID, Action<object[]>> dataEventTriggers;

    public void RegEvent(EVENT_ID id, Action<object[]> action)
    {
        if (!dataEventTriggers.ContainsKey(id))
            dataEventTriggers.Add(id, action);

        dataEventTriggers[id] += action;
    }

    public void UnRegEvent(EVENT_ID id)
    {
        if (dataEventTriggers.ContainsKey(id))
        {
            dataEventTriggers.Remove(id);
            return;
        }
    }

    public void SendEvent(EVENT_ID id, params object[] values)
    {
        if (dataEventTriggers.ContainsKey(id))
            dataEventTriggers[id].Invoke(values);
    }
}

?EventManagerFunc

场景搭建(同 EventManagerDelegate)

  • 3D对象:Cube(挂载 Sample_02_Unity_Cube01.cs 脚本,响应者,注册处理事件)
  • Canvas:Canvas(挂载 Sample_02_Unity_UI.cs 脚本,发起者,发起事件通知)
  • 按钮控件:点击发送Cube事件1 (对应事件 ED_NO_PARAM_NO_RETURN
  • 按钮控件:点击发送Cube事件2 (对应事件 ED_PARAM_NO_RETURN
  • 按钮控件:点击发送Cube事件3 (对应事件 ED_NO_PARAM_RETURN
  • 按钮控件:点击发送Cube事件4 (对应事件 ED_PARAM_RETURN
Game视图

事件系统:EventManagerReflection.cs

  • RegEvent 事件注册,这里需要使用到 Attribute(RegEvent?)?来对处理函数进行注册。另外这里RegEvent我改为了直接对类进行反射搜索。
  • UnRegEvent 事件反注册,通过EVENT_ID来解除所有注册了的处理函数。
  • SendEvent 事件通知,通知对应EVENT_ID下注册的所有处理函数。
  • Invoke 事件调用,调用对应EVENT_ID下注册的唯一处理函数,并处理返回结果。
  • MethodData 事件信息:处理函数及其处理对象实例。

这里增加了 RegEvent 和 MethodData,并且事件注册其实改为了对象注册。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Reflection;
using System.Text;

// 自定义特性
public class RegEvent : Attribute
{
    public RegEvent(EVENT_ID id, bool isSend)
    {
        _id = id;
        _isSend = isSend;
    }
    public EVENT_ID _id;
    public bool _isSend;
}

public class EventManagerReflection
{
    // 内部管理的函数数据,用于记录注册的函数和对象
    private class MethodData
    {
        public MethodData(MethodInfo method, object obj)
        {
            _method = method;
            _obj = obj;
        }

        public MethodInfo _method;
        public object _obj;
    }

    private static EventManagerReflection me = new EventManagerReflection();
    public static EventManagerReflection GetMe()
    {
        return me;
    }

    // public delegate void Action();
    Dictionary<EVENT_ID, List<MethodData>> dataEventTriggersRet = new Dictionary<EVENT_ID, List<MethodData>>();

    public void RegEvent(object obj)
    {
        Type type = obj.GetType();

        MethodInfo[] methodInfos = type.GetMethods();
        foreach (MethodInfo method in methodInfos)
        {
            RegEvent reg = method.GetCustomAttribute<RegEvent>();
            if (reg != null)
            {
                if (!dataEventTriggersRet.ContainsKey(reg._id))
                    dataEventTriggersRet.Add(reg._id, new List<MethodData>());

                if (reg._isSend)
                {
                    dataEventTriggersRet[reg._id].Add(new MethodData(method, obj));
                    Debug.Log(string.Format("注册 {0} 到 dataEventTriggersRet send:", reg._id.ToString()));
                }
                else
                {
                    dataEventTriggersRet[reg._id].Clear();
                    dataEventTriggersRet[reg._id].Add(new MethodData(method, obj));
                    Debug.Log(string.Format("注册 {0} 到 dataEventTriggersRet invoke:", reg._id.ToString()));
                }
            }
        }
    }

    public void UnRegEvent(EVENT_ID id)
    {
        if (dataEventTriggersRet.ContainsKey(id))
        {
            dataEventTriggersRet.Remove(id);
            Debug.Log(string.Format("从 dataEventTriggersRet 反注册 {0}:", id.ToString()));
            return;
        }
    }

    public object SendEvent(EVENT_ID id, params object[] values)
    {
        if (dataEventTriggersRet.ContainsKey(id))
        {
            foreach (MethodData methodData in dataEventTriggersRet[id])
            {
                Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0} 事件的 {1} 函数:", id.ToString(), methodData._method.Name));
                methodData._method.Invoke(methodData._obj, new object[] { values });
            }
        }

        return null;
    }

    public object Invoke(EVENT_ID id, params object[] values)
    {
        if (dataEventTriggersRet.ContainsKey(id))
        {
            MethodData methodData = dataEventTriggersRet[id][0];
            Debug.Log(string.Format("从 dataEventTriggersRet 调用 {0} 事件的 {1} 函数:", id.ToString(), methodData._method.Name));
            return methodData._method.Invoke(methodData._obj, new object[] { values });
        }

        return null;
    }
}

响应者:Sample_02_Reflection_Cube01.c

EventManagerDelegate 使用完全一致:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

public class Sample_02_Reflection_UI : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        gameObject.transform.Find("点击发送Cube01事件1").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件1】");
            EventManagerReflection.GetMe().SendEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
        });

        gameObject.transform.Find("点击发送Cube01事件2").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件2】");
            EventManagerReflection.GetMe().SendEvent(EVENT_ID.ED_PARAM_NO_RETURN, "Val input", 1);
        });

        gameObject.transform.Find("点击发送Cube01事件3").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件3】");
            Debug.Log(string.Format("收到无参有返回值调用结果:{0}", EventManagerReflection.GetMe().Invoke(EVENT_ID.ED_NO_PARAM_RETURN)));
        });

        gameObject.transform.Find("点击发送Cube01事件4").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【点击发送Cube01事件4】");
            Debug.Log(string.Format("收到有参有返回值调用结果:{0}", EventManagerReflection.GetMe().Invoke(EVENT_ID.ED_PARAM_RETURN, "Val input", 2)));
        });

        gameObject.transform.Find("销毁Cube01点击事件").GetComponent<Button>().onClick.AddListener(
        () =>
        {
            Debug.Log("点击了【销毁Cube01点击事件】");
            EventManagerReflection.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN);
            EventManagerReflection.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_NO_RETURN);
            EventManagerReflection.GetMe().UnRegEvent(EVENT_ID.ED_NO_PARAM_RETURN);
            EventManagerReflection.GetMe().UnRegEvent(EVENT_ID.ED_PARAM_RETURN);
        });
    }
}

发起者:Sample_02_Reflection_UI.cs?

这里与前面的差距就很大了:

  • 以特性的形式,对处理函数进行注册信息标记:[RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN, true)]
  • 对类进行注册:EventManagerReflection.GetMe().RegEvent(this),注册时再根据类的注册信息来注册函数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Sample_02_Reflection_Cube01 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        EventManagerReflection.GetMe().RegEvent(this);
    }

    [RegEvent(EVENT_ID.ED_NO_PARAM_NO_RETURN, true)]
    public object OnHandleClick01(object[] data)
    {
        Debug.Log("响应 无参无返回值事件");
        return null;
    }

    [RegEvent(EVENT_ID.ED_PARAM_NO_RETURN, true)]
    public object OnHandleClick02_0(object[] data)
    {
        Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", data[0], data[1]));
        return null;
    }

    [RegEvent(EVENT_ID.ED_NO_PARAM_RETURN, false)]
    public object OnHandleClick03(object[] data)
    {
        Debug.Log(string.Format("响应 无参有返回值【{0}】事件", "Ret Output"));
        return "Ret Output";
    }

    [RegEvent(EVENT_ID.ED_PARAM_RETURN, false)]
    public object OnHandleClick04(object[] data)
    {
        Debug.Log(string.Format("响应 有参【{0},{1}】有返回值【{2}】事件", data[0], data[1], "Ret Output"));
        return "Ret Output";
    }
}

点击测试

Debug窗口信息

问题记录:可变形参的使用考虑

/*  使用反射时遇到的可变形参处理问题记录:
 *
    [RegEvent(EVENT_ID.ED_PARAM_NO_RETURN)]
    public object OnHandleClick02(object[] data)
    {
        Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", data[0], data[1]));
        return null;
    }

    // 这里传入的是 1个string和1个int
    gameObject.transform.Find("点击发送Cub01事件2").GetComponent<Button>().onClick.AddListener(
        () => EventManagerReflection.GetMe().Invoke(EVENT_ID.ED_PARAM_NO_RETURN, "Val input", 1)
    );

    public object Invoke(EVENT_ID id, params object[] values)
    {
        ……
        // 这里会报 TargetParameterCountException: Number of parameters specified does not match the expected number.
        return methodData._method.Invoke(methodData._obj, values);
        ……
    }
    直接使用values会报错,找到一个答案:https://stackoverflow.com/questions/61855500/targetparametercountexception-c-sharp

    I you write: object[] Parms = new object[] { "oiad", "abdj", "i" };

    that means the args of method invo are: public void invo(string s1, string s2, string s3)

    if you have public void invo(object[] per)

    you have to write object[] Parms = new object[] { new object[]{ "oiad", "abdj", "i"}};

    所以有两种解决办法:
    1、修改响应函数形参为:
    [RegEvent(EVENT_ID.ED_PARAM_NO_RETURN, true)]
    public object OnHandleClick02_1(string datas, int datai)
    {
        Debug.Log(string.Format("响应 有参【{0},{1}】无返回值事件", datas, datai));
        return null;
    }
    2、修改调用方法为:
        public object Invoke(EVENT_ID id, params object[] values)
    {
        ……
        return methodData._method.Invoke(methodData._obj, new object[] { values });
        ……
    }
* */

事件系统总结

UnityEvent && UnityAction:
反射:内部实现使用的反射,不适合频繁的注册与反注册,内存和性能都比较低下。
模板:由于UnityEvent和UnityAction都是走模板,所以扩展比较呆板:
? ? ? 形参:想做到参数动态可变,需要特出处理传回的参数,比如object[]。
? ? ? 返回值:固定的void返回值,需要处理带返回值的函数,不是特别方便。
?

Delegate:
代理:代理本身性能要高于反射,类似于指针传递,也不用额外的反射数据。
? ? ? 形参:由于delegate也需要事先申明,需要特出处理传回的参数,比如object[]。
? ? ? 返回值:由于是自定义,也可以使用object来做到返回的通用性。
?

Func || Action:
Func和Delegate使用方式几乎可以做到一摸一样。
Action与Func相同,只是没有返回值。
?

Reflection:
反射:与UnityEvent反射问题雷同。
? ? ? 返回值:由于是自定义,也可以使用object来做到返回的通用性。
总结来说,UnityEvent和Reflection适合于Editor,delegate和Func适合于Runtime。
?

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-11-23 12:41:48  更:2021-11-23 12:43:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 22:40:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码