Unity协程

34 篇文章 0 订阅

1. 协程

  在Unity 3D中,我们刚开始写脚本的时候肯定会遇到类似下面这样的需求:每隔3秒发射一个烟花、怪物死亡后20秒再复活之类的。刚开始的时候喜欢把这些东西都塞到Update里面去,就像下面这样写。

复制代码
 1 float nowTime = 3.0f;
 2 bool isDead = true;
 3 float deadTime = 20.0f;
 4     
 5 void startFireworks()
 6 {
 7     // 放烟花
 8 }
 9 
10 void revival()
11 {
12     // 复活
13 }
14 
15 void Update () 
16 {
17     if (nowTime <= 0)
18     {
19         startFireworks();
20         nowTime = Random.Range(2.5f, 3.5f);
21     }
22     nowTime -= Time.deltaTime;
23     if (isDead)
24     {
25         if (deadTime <= 0)
26         {
27             revival();
28             isDead = false;
29             deadTime = 30.0f;
30         }
31         deadTime -= Time.deltaTime;
32     }
33 }
复制代码

  当这样的需求多起来时,Update中凌乱不堪,如果有需求需要添加或者修改,将显得非常麻烦。尤其是类似死亡后复活这种需求,只是在死亡后等待30秒重新复活,其他时间根本不需要去执行,这样放在Update里面还需要每一帧去判断,显得很累赘。

好在Unity 3D支持yield协程,不懂没关系,先看看下面用协程实现上面的功能。

复制代码
 1 void Start()
 2 {
 3     StartCoroutine(Fireworks());
 4 }
 5     
 6 void deadHandle()
 7 {
 8     StartCoroutine(Revival());
 9 }
10 
11 IEnumerator Fireworks()
12 {
13     while (true)
14     {
15          startFireworks();
16         yield return new WaitForSeconds(Random.Range(2.5f, 3.5f));
17     }
18 }
19 
20 IEnumerator Revival()
21 {
22     yield return new WaitForSeconds(30.0f);  
        
        //yield return null;
23      revival();
24 }
复制代码

  上面代码中,以IEnumerator作为返回值的函数就是协程,调用StartCoroutine()开始协程,在Start函数中调用StartCoroutine(Fireworks());,说明在开始时就开始执行协程Fireworks(),在deadHandle()中调用StartCoroutine(Revival());说明是在怪物死亡时开始执行协程。

  现在再来看看协程Fireworks()和Revival()中带有yield return的语句,yield return new WaitForSeconds(30.0f);表示现在从return这个语句处中断执行,在30秒后继续执行后面的代码。

  如果想在下一帧继续执行,就应该这样写 yield return null,这样语句就会在return这里中断,等待下一帧继续执行后面的代码。就像在Fireworks()里面写的,return之后,继续进行while判断,为true则继续循环,遇到yield return中断执行,等待,反复这样运行,就像Update一样。当然while中的判断条件可以自己指定,在需要中断的时候,在外面将while中的判断条件置为false即可。

复制代码
 1 bool isContinue = true;
 2 void stopFireworks()
 3 {
 4     isContinue = false;
 5 }
 6 IEnumerator Fireworks()
 7 {
 8     while (isContinue)
 9     {
10         startFireworks();
11         yield return new WaitForSeconds(Random.Range(2.5f, 3.5f));
12     }
13 }
复制代码

  当然也可以通过有StopCoroutine来中止协程的执行,不过这个函数是有条件的,具体可以去查阅unity文档或者网上搜索一下,有很多资料,这里只是告诉大家有这么个东西可以用。

  有了协程,写起脚本来真是方便了很多。协程和Update一样,也是系统在每帧会去检测调用,因此在协程中也是可以使用Time.deltaTime的。关于协程与Update之类的执行顺序,没有测试过,网上也有一些资料,大家可以参考,不同的Unity 3D版本具体的实现可能有出入,如果某些功能确实需要知道执行顺序,那么到时候可以亲测一下。

  需要注意的一点是:WaitForSeconds是受到Time.timeScale影响的,如果将其置为0,那么协程就无法执行下去了。不过yield return null不会受到影响,因为每帧会执行,只是Time.deltaTime为0了。

  2. 消息传递

  在游戏开发中,消息传递必不可少。通常有三种方式:保存别的对象的引用、Unity自带的SendMessage和C#中的事件。

  例如一个暂停,我需要通知玩家,暂停了,不要响应键盘鼠标操作了;通知UI,显示一个暂停面板;通过所有怪物,不要动了,暂停了,休息一下。 

  第一种方式是刚开始写脚本时常用的,保存所有对象的引用,这是很麻烦的事情,我需要获取玩家、UI和所有的怪物对象,然后调用其相应的暂停函数,这在程序规模变大之后,添加、修改和删除是一个很大的工作量。而且很多对象之间相互引用,耦合对也很高,用起来比较麻烦。

  第二种方式是Unity 3D提供的Messages消息机制,不过网上说这种方法有很大的缺陷,而且只能通知一个父子关系的对象,不同对象之间的消息无法传递。没有用过这个机制,所以也不是很清楚是不是像上面说的那样。

  第三种方式是C#中的委托和事件,这个方法对于消息传递来说非常好用,从设计模式的角度上来说,就是一个典型的观察者模式。如果你用过EasyTouch摇杆,那你就应该知道在OnEnable()中使用EasyJoystick.On_JoystickMove += OnJoystickMove;注册自己的Move函数,在这里就是OnJoystickMove。其实EasyTouch这个就用到了C#事件,使用+=添加自己的响应函数,当发生摇杆移动时,就会调用你自己指定的OnJoystickMove函数。具体可以参考下面给出的参考资料的链接。

  今天就写到这里,这些都是简介性质的,详细资料网上都有很多,我这些只是告诉初学者Unity 3D中有这些东西,很可能是你需要的,可以少走一些弯路。

  关于协程和C#事件,是Unity 3D中强力推荐的两个机制,它们真的非常重要,一定要善用,大家可以体会一下。

  参考资料1:【吐血推荐】简要分析unity3d中剪不断理还乱的yield

  参考资料2:C# 事件和Unity3D


===========       协程疑问     ==============

1、定义

       协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。经过测试验证,协程至少是每帧的LateUpdate()后去运行。

2、如何停止协程?

     通过设置MonoBehaviour脚本的enabled对协程是没有影响的,即使在Inspector把gameObject 激活还是没有继续执行。也就说协程虽然是在MonoBehvaviour启动的(StartCoroutine)但是协程函数的地位完全是跟MonoBehaviour是一个层次的,不受MonoBehaviour的状态影响。

      但如果 gameObject.SetActive(false)则已经启动的协程则完全停止了。

3、协程原理?

        协程其实就是一个IEnumerator(迭代器),IEnumerator 接口有两个方法 Current 和 MoveNext() ,这两个方法对协程进行了管理,只有当MoveNext()返回 true时才可以访问 Current,否则会报错。迭代器方法运行到 yield return 语句时,会返回一个expression表达式并保留当前在代码中的位置。 当下次调用迭代器函数时执行从该位置重新启动。

(简单理解:只有当这个对象的 MoveNext() 返回 false 时,即该 IEnumertator 的 Current 已经迭代到最后一个元素了,才会执行 yield return 后面的语句。

        Unity在每帧做的工作就是:调用 协程(迭代器)MoveNext() 方法,如果返回 true ,就从当前位置继续往下执行。


4、yield关键字

      不允许不安全块。

  方法、运算符或访问器的参数不能是 ref 或 out。

  yield 语句不能出现在匿名方法中。

  当和 expression_r 一起使用时,yield return 语句不能出现在 catch 块中或含有一个或多个 catch 子句的 try 块中。

  yield return 提供了迭代器一个比较重要的功能,即取到一个数据后马上返回该数据,不需要全部数据装入数列完毕,这样有效提高了遍历效率。


5、结论

       函数内有多少个 yield return 在对应的 MoveNext() 就会返回多少次 true (不包含嵌套)。另外非常重要的一点的是:同一个函数内的其他代码(不是 yield return 语句)会被移到 MoveNext 中去,也就是说,每次 MoveNext 都会顺带执行同一个函数中 yield return 之前,之后 和两个 yield return 之间的代码。


6、协程与线程区别?

协程和线程的区别是:

协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值