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

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

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

Unity快速入门之四 - Unity模型动画相关_翕翕堂-CSDN博客

资源管理待定……

……

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

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

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

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 updatevoid 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 updatevoid Start(){// AddListener - 成员函数 - OnClickFunctiongameObject.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 updatevoid Start(){...// AddListener - 委托匿名函数 - delegategameObject.GetComponent<Button>().onClick.AddListener(delegate{Debug.Log("AddListener - 委托匿名函数 - delegate");});}public void OnClickFunction00()...
}

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

// 使用UnityAction需要增加这个
using UnityEngine.Eventspublic class ButtonEvent : MonoBehaviour{// Start is called before the first frame updatevoid Start(){...// AddListener - 委托实例 - OnClickFunction01gameObject.GetComponent<Button>().onClick.AddListener(new UnityAction(OnClickFunction01));}public void OnClickFunction00()...public void OnClickFunction01(){Debug.Log("实例委托 - OnClickFunction02");}
}

委托lambda

{// Start is called before the first frame updatevoid Start(){...// AddListener - 委托lambda表达式 - lambdagameObject.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 updatevoid Start(){// 多路广播,委托函数m_untiyAction += OnClickFunction01;// 委托匿名函数m_untiyAction += delegate{Debug.Log("多路广播 - 匿名函数委托 - delegate");};// 委托实例 - OnClickFunction01m_untiyAction += new UnityAction(OnClickFunction01);// 委托lambda表达式m_untiyAction += () => { Debug.Log("多路广播 - lambda表达式委托 - lambda"); };// AddListener - UnityAction - delegate// 将UnityAction绑定到AddListenergameObject.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.htmlpublic UnityAction m_untiyAction;// Start is called before the first frame updatevoid Start(){// AddListener - 成员函数 - OnClickFunctiongameObject.GetComponent<Button>().onClick.AddListener(OnClickFunction00);// AddListener - 委托匿名函数 - delegategameObject.GetComponent<Button>().onClick.AddListener(delegate{Debug.Log("AddListener - 委托匿名函数 - delegate");});// AddListener - 委托实例 - OnClickFunction01gameObject.GetComponent<Button>().onClick.AddListener(new UnityAction(OnClickFunction01));// AddListener - 委托lambda表达式 - lambdagameObject.GetComponent<Button>().onClick.AddListener(() => Debug.Log("lambda表达式委托 - lambda"));// 多路广播,委托函数m_untiyAction += OnClickFunction01;// 委托匿名函数m_untiyAction += delegate{Debug.Log("多路广播 - 匿名函数委托 - delegate");};// 委托实例 - OnClickFunction01m_untiyAction += new UnityAction(OnClickFunction01);// 委托lambda表达式m_untiyAction += () => { Debug.Log("多路广播 - lambda表达式委托 - lambda"); };// AddListener - UnityAction - delegategameObject.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 updatevoid 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是个Entry的List。

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.htmlpublic 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 updatevoid 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下注册的所有处理函数。

由于UnityAction是 public 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 updatevoid 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 updatevoid 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));elsedataEventTriggersRet[id] += action;Debug.Log(string.Format("注册 {0} 到 sendEventTriggersRet send:", id.ToString()));}else{if (!dataEventTriggersRet.ContainsKey(id))dataEventTriggersRet.Add(id, new DataEventFunctionRet(action));elsedataEventTriggersRet[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 updatevoid 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 updatevoid 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));elsedataEventTriggersRet[id] += func;Debug.Log(string.Format("注册 {0} 到 sendEventTriggersRet send:", id.ToString()));}else{if (!dataEventTriggersRet.ContainsKey(id))dataEventTriggersRet.Add(id, new Func<object[], object>(func));elsedataEventTriggersRet[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 updatevoid 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 updatevoid 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 updatevoid 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 updatevoid 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个intgameObject.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-sharpI 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。

Unity快速入门之三 脚本与事件相关推荐

  1. Unity快速入门之四 - Unity模型动画相关

    最近要给公司的小伙伴做Unity入门,针对几个常用的知识进行快速入门介绍. Unity快速入门之一 3D基础概念.Camera.Canvas RenderMode的几种方式对比_翕翕堂 Unity快速 ...

  2. Unity快速入门之二 GUI Transform 详解

    Unity快速入门之一 3D基础概念.Camera.Canvas RenderMode的几种方式对比_翕翕堂 Unity快速入门之二 GUI Transform 详解_翕翕堂 Unity快速入门之三 ...

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

    最近要给公司的小伙伴做Unity入门,针对几个常用的知识进行快速入门介绍. Unity快速入门之一 3D基础概念.Camera.Canvas RenderMode的几种方式对比_翕翕堂 Unity快速 ...

  4. 视频教程-Unity快速入门系列课程(第1部)-Unity3D

    Unity快速入门系列课程(第1部) 二十多年的软件开发与教学经验IT技术布道者,资深软件工程师.具备深厚编程语言经验,在国内上市企业做项目经理.研发经理,熟悉企业大型软件运作管理过程.软件架构设计理 ...

  5. 视频教程-Unity快速入门系列课程(第2部)-Unity3D

    Unity快速入门系列课程(第2部) 二十多年的软件开发与教学经验IT技术布道者,资深软件工程师.具备深厚编程语言经验,在国内上市企业做项目经理.研发经理,熟悉企业大型软件运作管理过程.软件架构设计理 ...

  6. Pandas高级数据分析快速入门之三——数据挖掘与统计分析篇

    Pandas高级数据分析快速入门之一--Python开发环境篇 Pandas高级数据分析快速入门之二--基础篇 Pandas高级数据分析快速入门之三--数据挖掘与统计分析篇 Pandas高级数据分析快 ...

  7. Unity快速入门教程-创建并启用c#脚本

    提示:本篇文章主要提供新手入门学习,初次发文,多多指教 文章目录 前言 一.创建脚本 二.启用脚本 三.简单脚本示例 总结 前言 unity通过c#脚本构建项目逻辑关系,本篇介绍c#脚本创建,启用及其 ...

  8. Unity快速入门教程-详解预制体(Prefab)及其实例化Instantiate

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.预制体(Prefab)是什么? 1.1预制体简介 1.2预制体是什么样子的? 1.3预制体作用与用途 二.制作一个 ...

  9. Unity快速入门之傻瓜小鸟“Flappy Bird”(三)

    目的:掌握基本2D游戏开发技巧 知识点: 1.学习UI界面的开发(UGUI) 2.循环往复简单算法 3.脚本之间的常用数据传值 4.2D游戏开发环境 5.2D精灵动画与层的设置 开发步骤 建立良好的目 ...

最新文章

  1. 禁止显示“You have new mail in /var/spool/mail/root”
  2. qq邮箱格式的Java代码_Java实现QQ邮件发送
  3. mysql根据父级编码得到父级内容_在mysql查询中通过父级获取所有子级
  4. Linux上安装dotnetcore2.0
  5. ubuntu 21.04安装OBS Studio录屏软件
  6. Node.js实现Excel转JSON
  7. Spring Cloud 学习笔记(一) 之服务治理模块Spring Cloud Eureka 搭建注册中心
  8. python开发基础戴歆第四章_第一阶段:Python开发基础 day04 课后练习
  9. batchplot插件用法_Batchplot怎么安装及使用?Batchplot的安装方法及使用方法介绍
  10. 移动硬盘拒绝访问找到数据的法子
  11. stylus vue 报错_带你玩转webpack 从零构建Vue工程
  12. 牛客网Python笔试技巧、单行多行输入方法以及代码调试技巧
  13. oracle中字符串长度计算,根据 oracle 标准计算超长字符串的长度
  14. Axure 教程 |中级电子商务网站设计
  15. MIUI11Android系统耗电,小米MIUI系统升级11,网友表示很费电,学习这个省电方法够你用三天!...
  16. 什么软件可以测试电池充电次数,iPhone电池循环次数查询软件
  17. 100倍分析性能提升 清华冠军团队用图数据震惊世界
  18. PS制作红色拟物化时钟icon图标
  19. php怎么获取html span标签的值_如何获取PHP中所有html元素的列表?
  20. Java 16 新特性:record类

热门文章

  1. Android调用手机新浪微博客户端分享
  2. 1.6 x86读取smbios信息
  3. 关于mysql时间比较 -- date和datetime
  4. 【PYTHON,EXCEL】利用python进行EXCEL处理1 打开,读取数据的方法
  5. 开盘大涨22%,名创优品的成功秘笈是什么?
  6. 什么副业能赚钱,分享五个在家就能做的副业兼职
  7. MATLAB算法实战应用案例精讲-【深度学习】多尺度特征提取
  8. 携程--数据运营、数据分析
  9. Python爬取新版猫眼Top100电影系列数据,并保存到csv文件
  10. 价值1000元的稀有二开版的无限坐席在线客服系统源码+教程