Unity3d学习笔记之(一)——Delegate与Event
引子
这两天在做一个背包系统。其中一个小的步骤是实现鼠标点击拿起物品功能。 话说点击背包格子之后,传递格子中的物品信息给到鼠标图片,让鼠标图片显示这个物品,同时删除格子中的物品信息。经由这个过程实现拿起物品的功能。
整个过程涉及到了两个对象,背包格子对象和鼠标图片对象。然后需要传递背包格子中的物品信息给到鼠标图片。同时还要对背包格子和鼠标图片中是否已有物品进行判断。 Unity3d中有几种方法来实现这个功能:
首先是用Unity3d 提供的Button控件自带的功能,我们把传递信息和判断的语句写在BagSlotClick()方法里,然后把这个方法放在背包格子对象中,经过拖拽,给到背包格子自己的Onclick()事件中。 为了方便实现,我们需要把鼠标图片对象单例化,方便背包格子对象直接引用。 本来这个简单的方法就可以用了,但是我固执的希望应用所谓MenuManager单例来管理所有的菜单对象,因此不希望单例鼠标图片。 所以在我的实际应用中变成了背包格子对象需要引用MenuManger单例,再通过MenuManager来引用鼠标图片对象,进行赋值,判断等操作。因此在背包格子对象里,看到的BagSlotClick()方法很繁琐。等于对象A->对象B->对象C的嵌套引用。太难看了。
因此想到把BagSlotClick()方法挪到MenuManager里,还是通过botton onclick() 来调用, 这样最多是对象A引用对象B, 对象B引用对象C。这样子对于鼠标图片的引用就直接多了。但是,这种方法涉及到了背包格子中的物品信息传递问题。
而能够传递参数的方法就是下面几种了:
SendMessage(String “Method Name”, <T> parameter)
DelegateUnitevent
unityaction
看之前前辈的研究,说SendMessage在执行速度方面比Delegate慢了一个数量级。对我而言,速度倒不是什么问题,反正我们做的都是小游戏,不会有很多计算。而在我看来,SendMessage 方法最主要的问题就是传递的方法名称是字符串,像我这种手残党很容易敲错字母 (平时都是利用IDE的自动提示功能选择函数的。。。。)。因此就放弃了应用SendMessage的想法,直接应用Delegate。
Delegate与Event
OK, 那么开始闲话这个Delegate
public delegate void MyDelegateNameXXX(parameter1, parameter2,...); public MyDelegateNameXXX myDelegateName;
我接触到的资料都把这个Delegate翻译成为了“委托”或者“代理”,而在我看来,造成这个技术点晦涩难懂的关键点之一就是这个翻译。“委托”和“代理”又是什么意思?求人办事吗?我是个宅男,万事不求人,全靠左手,我不懂怎么求人啊。我认为应该把它翻译为"空函数“/”空白函数“或者“函数变量”。这个myDelegateName就是一个可以随时把其他函数塞进来的空白函数(当然这个例子中只能塞进来无返回值,参数为parameter1,parameter2....这样子的函数)。(还有种翻译方法是函数指针,借用c的指针概念,但我同样认为“指针”也不是一个好的翻译,指针是什么针?能缝线吗?算了,不吐槽了。)那么这个MyDelegateNameXXX又是什么鬼,并且声明这个空白函数这么简单的事竟然写了两行!!有什么必要?的确没有必要(其实从更深层次应该还是有必要的,但是我不懂,所以。。。),所以从.net framework 2.0 (同时也是c# 2.0)开始引入了预先封装好的Action方法,简化了这个声明过程:
1 public Action <parameter1, parameter2> myDelegateName;
使用Action关键字代替delegate,实际上是.net framework 2.0帮我们先写了一行:public delegate void Action<>,我们就不需要去声明那个莫名奇妙的MyDelegateNameXXX 了(这里面写成了Action),直接上我们的空白函数名字myDelegateName就好了,参数写在了<>里面。当然.net framework 2.0不只引入了这一个方法,其实还有Func,Predicate。他们的区别就是Action用来声明无返回值(void)的空白函数,Func用来声明有返回值的,Predicate返回bool。 (其实更早的.net framework 1.x时代就封装过EventHandler(object, EventArgs)这个方法,类似的,也简化了声明过程,但是相应的EventArgs参数改写太麻烦,还不如直接声明,同时另一个object参数类型对应到Unity3d的gameObject类型各种诡异,所以我就不用这个方法了。好吧,我承认我不会用)。
另外就是有些例子中大家用了Static关键字,
1 1 public static Action <parameter1, parameter2> myDelegateName;
这样就方便在各处使用这个空白函数了,但是由于Unity3d单利化非常方便,我更喜欢使用单利的模式,所以只要把相应的类单例化,就没有必要静态了,所以我不用。
接下来,我们怎么塞函数进来呢?
private void myFunctionA(parameter1, parameter2,...) { //对parameter1做不可描述之事。。。。 //对parameter2做不可描述之事。。。。 //。。。。 } myDelegateName+=myFunctionA;
假如你有一个myFunctionA函数,只要用+=操作符把他塞给myDelegateName空白函数就好了。其他的函数,比如myFunctionB, C, D...都可以接着用+=塞进来,这个空白函数胃口是很大的,不怕撑坏。当然,有时候我们也会看到另外的一些操作方式:
1 myDelegateName+= new MyDelegateNameXXX(myFunctionA);
这种new的方法其实就是c# 1.x早期的方法,用来保证类型安全的,后来由于c#2.0引入了泛型的概念,所以可以直接+=而不用new了。
1 myDelegateName+= delegate(parameter1, parameter2。。。) { //对他们做不可描述之事}; 2 //C# 2.0 3 4 myDelegateName+=(parameter1, paramter2....)=> { //对他们做不可描述之事}; 5 //C# 3.0
还有时候会见到这两种,其实就是c#2.0引入了匿名的概念,就用一个delegate关键字写在函数体之前,不用写函数名字了。c#3.0引入了Lamda表达式,就连delegate关键字也不用写了。
那么为什么要用+=操作符呢,用=不可以吗?当然是可以的,但是用=有一个很大的问题。假如你之前已经把myFunctionA,B,C都塞进去myDelegateName空白函数里面去了,这时候用=把myFucntionD也塞进来,那么就等于覆盖/删除了之前的myFunctionA,B,C只剩D了,之前的工作就都浪费了。为了避免这种错误的发生,c# 1.0 定义了event关键字,用在我们的例子里就是:
1 public event Action <parameter1, parameter2> myDelegateName;
在Action前面加上event之后,当我们在其他地方(其他类中)要把其他函数塞进来时,就只能使用+=操作符,而不能使用=操作符,避免错误发生。所以event并不是什么“事件”,他就是一个关键字,操作符,它把除了+=和-=之外的操作全部都封装起来了,让其他类无法调用,这样就保护了这个空白函数以及里面装的内容。哎,这个该死的翻译,误导了我们这么多年。
好了,现在我们有了空白函数,里面也塞满了我们想塞的函数,什么时候调用这个函数呢?那就是我们希望发送消息的时候:
1 myDelegateName (paramter1, parameter2,...);
当我们带上参数执行这个空白函数的时候,之前塞进来的一系列函数就按顺序都执行了。就等于把这些参数传给了之前塞进来的一系列函数,等于他们收到了这些参数作为消息。但有一个地方需要注意,如果我们之前用了event关键字,这个调用或者执行的过程就只能在这个类(声明代理的)的内部来用。对于我来讲,我喜欢把这些空白函数的声明都放在一个管理类单利中,(毕竟在一个项目中好多个消息要发送,放在一起方便管理)也就是写一个Manager,单利化,然后声明这些空白函数。但是调用或者执行这些函数的情况会发生在其他类中,如果用event关键字,我就没法在外部调用了,所以我不用event关键字。同时也自己规定不用=来塞函数进去,只用+=,这样避免犯错。
UnityAction与UnityEvent
下面来说说Unity3d里面的UnityEvent和UnityAction
先说UnityAction:
1 public delegate void UnityAction<T0,T1>(T0 arg0, T1 arg1); //Unity3d 2 public delegate void Action<T1, T2>(T1 arg1, T2 arg2) //c#
从定义就可以看出,UnityAction和C# Action并没有什么不同,只是加了Unity几个字母而已。估计连Unity自己的工程师都觉得太过鸡肋,在官方文档里解释条目就那么潦草的一行字。没了。所以他们的意思很简单:你们爱用不用,反正我不用。
再说UnityEvent,这个东西比较诡异,他是一个类,本质上讲C#里的delegate关键字是一样的,都是类,我把他理解成为unity自己的delegate关键字。并且基于这个UnityEvent,Unity3d搭建了整个UGUI的Eventsystem。
1 Public UnityEvent myDelegateName; 2 myDelegateName.addListenner(myFunctionA); 3 myDelegateName.involk();
使用这个UntiyEvent 声明空白函数也只用一行就好了,添加函数进去不能用+=,要用addListenner()这个方法,而执行这个空白函数要用Invoke()方法,不能直接调用。然后就是这个空白函数类似自带event关键字,在别处无法执行。但貌似他继承了父类的removeall Listenner方法,可以在别处执行类似c#的=null的操作,一样可以清空空白函数,看起来没有c#event关键字安全。详细介绍的话,这个主题可以单独写一份东西来研究。 我只是希望使用空白函数而已,那么这个东西的问题是什么呢?对我来讲,如果空白函数不带参数的话,是没有什么用处的,所以必须要带参数,但是,如果真要带参数,UnityEvent<T0,T1>必须要重写:
1 public class myUnityEvent:UnityEvent<parameter1, parameter2>{};
而有这个重写的功夫我已经写了好多个Action了。所以我需要空白函数来发送消息的时候不用这个东西,只是在涉及到unity3d自己的Eventsystem的时候才用部分功能。
项目例子
OK,基础知识已经整理的差不多了,回到我们的实际项目:背包系统。下面就是我的具体实现方式,利用c#封装的Action,没有event关键字,没有Static 关键字,简单方便。
代码很简介,效果很好,不是吗?
代码部分 (未完待续)
结语
写本文的目的有两个:
1, 整理自己的思路,记录自己对于这个知识点的理解,以防一段时间之后忘记了。
2,帮助其他朋友以最简单的方法理解这个知识点。
所以,一旦大家发现:
1,我的理解有错误,
2,还是无法理解我所说的东西,
请帮忙在评论区里写出来告诉我,我收到之后就会来修改这篇笔记。谢谢大家。
另一个方面是很重要的,我是一个初学者,上面这些理解和体会其实都是从之前各位前辈的文章里学到的,现在我把参考资料罗列下来,也对他们表示感谢:
1,c# 1.0 Lauguage specification: http://download.microsoft.com/download/a/9/e/a9e229b9-fee5-4c3e-8476-917dee385062/CSharp%20Language%20Specification%20v1.0.doc
2,c# 2.0 Lauguage specification: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf
2, Action in .net framwork : https://msdn.microsoft.com/zh-cn/library/018hxwa8(v=vs.110).aspx
,(未完待续)
原文:https://www.cnblogs.com/wng0/p/9132742.html