Unity3D 学习笔记(三):协程

发表于2017-09-30
评论0 3.3k浏览

一、协程的概述:

1.定义:

官方的定义是:

       A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.

       即协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。

       Unity在每一帧(Frame)都会去处理对象上的协程。Unity主要是在Update后去处理协程(检查协程的条件是否满足),但也有些特例。


2.作用:

        使用协程的作用一共有两点:

1)延时(等待)一段时间执行代码;

2)等某个操作完成之后再执行后面的代码——通常用于控制运动,序列,以及对象的行为(充当状态机)

        总结起来就是一句话:控制代码在特定的时机执行。

注:协程不是线程,也不是异步执行的。协程和 MonoBehaviour 的 Update函数一样也是在MainThread中执行的。使用协程你不用考虑同步和锁的问题。经过测试验证,协程至少是每帧的LateUpdate()后去运行。

以下就是MonoBehaviour函数的执行流程图:


二、使用方式:

       其实协程最简单的调用就是用StartCoroutine去开始一个协程处理函数,这个函数可以是带参也可是无参的,但是其返回值类型必须是IEnumerator类型的。

  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class NewBehaviourScript : MonoBehaviour {  
  5.   
  6.     public Transform fish;  
  7.     // Use this for initialization  
  8.       
  9.    void Start () {  
  10.         StartCoroutine(Move(100));  
  11.       
  12.     }  
  13.   
  14.     IEnumerator Move(int speed)  
  15.     {  
  16.         float ut = 0;  
  17.         while (true)  
  18.         {  
  19.             ut  = Time.deltaTime;  
  20.             if (ut >= 2)  
  21.             {  
  22.                 ut = 0;  
  23.                var angles = fish.transform.localEulerAngles;  
  24.                angles.z  = 180;  
  25.                fish.transform.localEulerAngles = angles;  
  26.             }  
  27.             fish.Translate(speed * Time.deltaTime, 0, 0);  
  28.             yield return 0;  
  29.         }  
  30.     }  
  31.   
  32. }  
        协程允许你的代码执行到一半的时候,先去做完别的事情再回来,这样处理许多事情就会变得简单。比如做俄罗斯方块的时候,但算法满足消去一行的时候,你需要播放一个动画,再消去,通常的做法就是把游戏状态转到动画状态,这样才能保证消去的时候暂停其他动作,比如继续下落方块。如果用协程就直接把权限交给协程就ok。


1.yield语句:

        是一个特殊的返回类型,它确保函数从yield语句的下一行继续执行。

  1. while(true) {    
  2.         // 做步骤0    
  3.         yield return 0;    
  4.          // 等待一帧    
  5.         // 做步骤1    
  6.         yield return 2;    
  7.          // 等待两帧    
  8.         // ...    
  9. }  

        你也可以传递时间值到yield语句,Update函数会在yield结束后执行下一语句:

  1. // do something    
  2.   yield return WaitForSeconds  (5.0);    
  3.   //等待5秒    
  4.   // do something more...  

        不能在Update或FixedUpdate函数内使用yield,但是你能使用 StartCoroutine开始一个函数。yield return可以看做是一种特殊的return,会返回到父类继续执行,但是yield return后面的类型或方法会有一个执行条件,当条件满足时会回调包含yield的子函数。例如:

  1. void Start () {    
  2.         print("Starting:"   Time.time);    
  3.         StartCoroutine(WaitAnPrint(2.0F));    
  4.         print("Before WaiAndPrint:"   Time.time);    
  5.     }    
  6. IEnumerator WaitAndPrint(float waitTime)    
  7.     {    
  8.         yield return new WaitForSeconds(waitTime);    
  9.         print("WaitAndPrint:"   Time.time);        
  10.     }  

        在执行yield return new WaitForSeconds(waitTime)时暂停的条件没有满足,故返回到start函数中继续执行,直到满足条件后再回调WaitAndPrint,所以输出为:

  1. Starting:0  
  2. Before WaiAndPrint:0  
  3. WaitAndPrint:2.12291  

        但是,假如yield return语句所在的函数已经是顶级函数,即不存在父类,则此时程序会堵塞等待yield return条件满足之后再继续执行语句之后的内容。


2.IEnumerator(迭代器):

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

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


Hijack

        这里在介绍一个协程的交叉调用类 Hijack(参见附件):

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using UnityEngine;  
  5. using System.Collections;  
  6.    
  7. [RequireComponent(typeof(GUIText))]  
  8. public class Hijack : MonoBehaviour {  
  9.    
  10.     //This will hold the counting up coroutine  
  11.     IEnumerator _countUp;  
  12.     //This will hold the counting down coroutine  
  13.     IEnumerator _countDown;  
  14.     //This is the coroutine we are currently  
  15.     //hijacking  
  16.     IEnumerator _current;  
  17.    
  18.     //A value that will be updated by the coroutine  
  19.     //that is currently running  
  20.     int value = 0;  
  21.    
  22.     void Start()  
  23.     {  
  24.         //Create our count up coroutine  
  25.         _countUp = CountUp();  
  26.         //Create our count down coroutine  
  27.         _countDown = CountDown();  
  28.         //Start our own coroutine for the hijack  
  29.         StartCoroutine(DoHijack());  
  30.     }  
  31.    
  32.     void Update()  
  33.     {  
  34.         //Show the current value on the screen  
  35.         guiText.text = value.ToString();  
  36.     }  
  37.    
  38.     void OnGUI()  
  39.     {  
  40.         //Switch between the different functions  
  41.         if(GUILayout.Button("Switch functions"))  
  42.         {  
  43.             if(_current == _countUp)  
  44.                 _current = _countDown;  
  45.             else  
  46.                 _current = _countUp;  
  47.         }  
  48.     }  
  49.    
  50.     IEnumerator DoHijack()  
  51.     {  
  52.         while(true)  
  53.         {  
  54.             //Check if we have a current coroutine and MoveNext on it if we do  
  55.             if(_current != null && _current.MoveNext())  
  56.             {  
  57.                 //Return whatever the coroutine yielded, so we will yield the  
  58.                 //same thing  
  59.                 yield return _current.Current;  
  60.             }  
  61.             else  
  62.                 //Otherwise wait for the next frame  
  63.                 yield return null;  
  64.         }  
  65.     }  
  66.    
  67.     IEnumerator CountUp()  
  68.     {  
  69.         //We have a local increment so the routines  
  70.         //get independently faster depending on how  
  71.         //long they have been active  
  72.         float increment = 0;  
  73.         while(true)  
  74.         {  
  75.             //Exit if the Q button is pressed  
  76.             if(Input.GetKey(KeyCode.Q))  
  77.                 break;  
  78.             increment =Time.deltaTime;  
  79.             value  = Mathf.RoundToInt(increment);  
  80.             yield return null;  
  81.         }  
  82.     }  
  83.    
  84.     IEnumerator CountDown()  
  85.     {  
  86.         float increment = 0f;  
  87.         while(true)  
  88.         {  
  89.             if(Input.GetKey(KeyCode.Q))  
  90.                 break;  
  91.             increment =Time.deltaTime;  
  92.             value -= Mathf.RoundToInt(increment);  
  93.             //This coroutine returns a yield instruction  
  94.             yield return new WaitForSeconds(0.1f);  
  95.         }  
  96.     }  
  97.    
  98. }  

        上面的代码实现是两个协程交替调用。


3.协程暂停:

       由于协程与MonoBehaviour一样,也是绑在对象Object上的,所以协程的运行和停止与绑定对象的active有关MonoBehaviour.enabled = false 协程会照常运行,但 gameObject.SetActive(false) 后协程却全部停止,即使在Inspector把  gameObject 激活还是没有继续执行。

       也就是说:协程虽是在MonoBehaviour启动的(使用StartCoroutine启动),但是协程函数的地位完全跟MonoBehaviour是同一个层次的,不受MonoBehaviour的状态影响,但是跟MonoBehaviour一样受到绑定的gameObject控制,也应该是和MonoBehaviour脚本一样每帧“轮询”yield的条件是否满足

http://blog.csdn.net/linshuhe1/article/details/51095185

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引