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内置事件
流程上:
按钮点击函数上代码实现方式,常见的四种:
这些都是基于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: UnityEvents https://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.0 https://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 是个Entry 的List 。
Entry 又是由触发类型和触发事件组成,最后,触发事件又是来自于UnityEvent 。
所以实质上用法是一样的,可以看到EventTrigger 也是继承于 XXXHandler 。
自定义(Custom)触发(Trigger)事件
其他UI控件的响应就不做展开了,因为可以通过上面类似的办法处理,下面说说自定义触发事件,通过观察Button和EventTrriger,可以看到,都是通过UnityEvent、XXXHandler的方式实现的,所以我们自己来实现一个。
XXXHandler有哪些呢,官方链接:
Supported Events | Unity UI | 1.0.0 https://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
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 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。 ?