Unity 协程详解

发布时间 2023-07-14 17:53:30作者: CatSevenMillion

  在程序开发时,光是了解协程怎么用是远远不够的,因为当程序出现一些有关于协程的错误时,理解协程的原理就十分有必要性了。

1.协程使用的一些问题

  我们知道如果在Unity中编写一个死循环,会造成运行游戏时整个Unity编辑器卡死,而协程函数在使用时好像是可以与Update函数并行不斥的,那如果在一个协程函数里面编写一个死循环会怎么用呢?答案也是程序会卡死。

  所以,我们知道了,协程函数并不是一个独立的执行单元,它与Update函数一样,被Unity依次执行。一旦有一个发生了死循环,游戏将会卡死。简而言之,协程不是线程。

  从上面可以看到,协程非常像一个自定义的Update函数,只不过内部含有延时逻辑。而有意思的是,Mono的生命周期函数例如Start和Update是可以支持协程调用的,只是不需要使用StartCoroutine就可以直接调用。

public class Coroutine : MonoBehavior{
    IEnumerator Start(){
          Debug.log(1);
          yield return new WaitForSeconds(1);
          Debug.log(2);
          yield return new WaitForSeconds(1);      
    }
}

  上述的Start方法并不是主动调用的,而是被Unity引擎识别并调用的,这里把Start的返回值void改成了IEnumerator,也同样被Unity识别了。

2.迭代器

  虽然协程是Unity提供的,但是IEnumrator则是C#的语法,所以我们要弄清楚协程就先要知道这两个玩意的机制。事实上,IEnumrator与yield配合实现了一种叫做“可重入函数”的机制,也就是函数可以被打断一会,之后再执行的机制。

public class Itr:MonoBehaviour{
    IEnumerator<int> HelloWorld(){
          transform.position = new Vector3(1,0,0);
          yield return 233;
          transform.position = new Vector3(2,0,0);
          yeild return 666;
    }

     void Start(){
          IEnumerator<int> e  = HelloWorld();
          while(){
                 if(!e.MoveNext()){
                     break;    
                 }else{
                           Debug.log("yield 返回值"+e.Current);
                           Debug.log("当前位置"+transform.position);        
                 }        
          }
    }
}        

  这里HelloWorld就是一个可重入函数,在初次执行时会在第一个yield处卡住,返回值是一个IEnumrator对象,之后程序会把他保存在e变量中。每次调用e.MoveNext()方法时会让函数继续执行到下一个yield处,执行到最后一个yield后,函数彻底执行完毕,并且返回false;

  每次执行一步,还可以从变量e中获取到中断时的返回值,即e.Current。这个返回值将非常有用。

  yield在生命周期的运行时机:我们知道要使用协程的脚本必须先继承自MonoBehaviour。其中一个原因就是在协程的yield也在Mono生命周期中,且在Update与LatedUpdate之间。以下是常用的

    yield return null; 暂停协程等待下一帧继续执行

    yield return 0或其他数字; 暂停协程等待下一帧继续执行

    yield return new WairForSeconds(时间); 等待规定时间后继续执行

    yield return StartCoroutine("协程方法名");开启一个协程(嵌套协程)

    yield return new WaitForFixedUpdate():等到下一个固定帧数更新

    yield return new WaitForEndOfFrame():等到所有相机画面被渲染完毕后更新

3.协程的延时执行原理

IEnumerator<int> HelloWorld(){
    float helloTime = 0;
    transform.position = new Vector3(1,0,0);
    helloTime = Time.time+1;
    while(Time.time<helloTime){
        // 返回值没用
        yield return 1;
    }
}

  上述代码中,Update函数每帧都会调用协程函数,而协程函数自身会用循环控制运行的进度,如果时间不到就立即中断,最终实现了定时运行的效果。这一实现方法从原理上与Unity中一致(Unity源码不开放)。