作为源生的C#程序员,可能已经非常了解委托(delegate)、行动(Action)以及C#的事件了,不过作为一个半道转C#的程序员而言,这些东西可能还是有些陌生的,虽然委托并非是C#独创,亦非是首创,C++的函数指针就完全类似于委托的功能,但很多东西没有委托的话实现起来还是很伤脑筋的。
本文主要介绍委托与unity协程之间组合开发的便利,实质上也是对平常的学习和工作中学到的东西做个记录。
一、委托
定义委托:public delegate void MyDelegate(int _num);
//定义一个委托MyDelegate,如同定义一个类一样,此时的委托没有经过实例化是无法使用的,而他的实例化必须接收一个返回值和参数都与他等同的函数,此处的委托MyDelegate只能接收返回值为void,参数为一个int的函数
实例化委托:MyDelegate _MyDelegate=new MyDelegate(TestMod);
//以TestMod函数实例化一个MyDelegate类型的委托_MyDelegate,此处TestMod函数的定义就应如下:
public void TestMod(int _num);
//之后调用_MyDelegate(100)时就完全等同于调用TestMod(100)
二、协程
定义协程:public IEnumerator MyCoroutine(){ };
//定义一个协程MyCoroutine,不同于定义类或委托,此时的协程是可以直接使用的,Unity的每一个执行周期里都会包含属于协程的那一部分,只要是场景中属于激活状态的物体(active为true),如同update那般,Unity在渲染的每一帧都会去查找该物体上是否存在协程部分的代码,若存在则加入该物体协程的执行周期,协程部分的代码会如同update那样被Unity每帧执行,当然不同于update的是,大部分协程内部不存在延时的话,一次执行之后便会跳出了,而且既然是处于Unity规定的生命周期里的模块,协程如同update一样当他们所在的物体属于未激活的话(active为false),该物体上所有脚本中包含的协程代码都是不会被执行的。
使用协程:StartCoroutine(MyCoroutine());
//使用StartCoroutine函数将协程MyCoroutine加入此脚本所在物体的协程执行周期内,如果在渲染的下一帧此物体依旧是处于激活状态的话,那么协程MyCoroutine中的代码便会得到Unity执行。
三、委托+协程
其实网上关于这个的例子很多,我在这里只是做个比较系统的归纳。
还记得Unity的Invoke("test",2)吗(延时2秒后执行函数test)?不得不说这是个很大的坑,test函数不能赋予参数不说,还必须得是在当前类里面的方法,而对于延时执行等控制时间轴的操作,这在任何一个项目中肯定都是不可少的,索性有协程,我们完全可以替换掉Invoke的作用。
我们可以自己写一个延时控制器:
/// <summary> /// 延时执行 /// </summary> /// <param name="action">执行的委托</param> /// <param name="delaySeconds">延时等待的秒数</param> public IEnumerator DelayToInvokeDo(Action action, float delaySeconds) { yield return new WaitForSeconds(delaySeconds); action(); } /// <summary> /// 使用例子 /// </summary> StartCoroutine(DelayToInvokeDo(delegate() { task.SetActive(true); task.transform.position = Vector3.zero; task.transform.rotation = Quaternion.Euler(Vector3.zero); task.doSomethings(); },1.5f));
我们可以看到这里的例子是在1.5秒之后执行一个匿名委托,该委托的内容是将游戏物体task激活,并设置其位置与旋转属性,然后可以做更多的事情。
不过好像有些缺陷,task这个变量是哪里来的?Unity能否识别?事实上无论task是本类的全局静态变量还是普通单例变量,甚至只是一个与此部分协程代码处在同一域中的局部变量,加入到协程执行周期以后,短期内他是不会被释放的,也就不用担心Unity在后续执行到task的SetActive的时候会报空引用的错了。
只不过我们确实可以将之完善一下,或许我想更好的控制task。
/// <summary> /// 延时执行 /// </summary> /// <param name="action">执行的委托</param> /// <param name="obj">委托的参数</param> /// <param name="delaySeconds">延时等待的秒数</param> public IEnumerator DelayToInvokeDo(Action<GameObject> action, GameObject obj,float delaySeconds) { yield return new WaitForSeconds(delaySeconds); action(obj); } /// <summary> /// 使用例子 /// </summary> StartCoroutine(DelayToInvokeDo(delegate(GameObject task) { task.SetActive(true); task.transform.position = Vector3.zero; task.transform.rotation = Quaternion.Euler(Vector3.zero); task.doSomethings(); },GameObject.Find("task1"),1.5f));
1.5秒之后执行一个匿名委托,该委托的内容是将游戏物体task激活,并设置其位置与旋转属性,然后可以做更多的事情,这里的task是我们从场景中获取的name为task1的物体,将之作为参数传入了委托中。
后续你可能需要更多的参数,更多的种类,不过那只需要在此基础上扩展就可以了,最后将协程DelayToInvokeDo放在一个全局脚本内,定义一个该脚本的静态变量,那么就可以在项目的任何位置加入这个协程了。