Unity 协程详解

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

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);        
                 }        
          }
    }
}        

  每一个迭代器对象一共就三个属性,Current,MoveNext,Reset。

  这里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.协程的延时执行原理

   以下是一个与Unity类似的实现。主要就是实现一个WaitForSeconds来实现延时执行。WaitForSeconds内部其实也是一个计时器的功能。所有的协程都由CoroutineManager进行管理,只有当计时结束才会执行MoveNext,协程内部才会向后继续运行。

IEnumerator IMyWaitForSecond(float time)
{
    Debug.Log("Wait Start");
    yield return new MyWaitForSeconds(time);
    Debug.Log("Wait Stop");
}    

public interface IWait
{
    bool Tick();
}

public class MyWaitForSeconds : IWait
{
    float _seconds = 0f;
    public MyWaitForSeconds(float seconds)
    {
        _seconds = seconds;
    }

    public bool Tick()
    {
        _second -= Time.deltaTime;
        return _seconds<=0;
    }
}

public  class CoroutineManager
{
    private static CoroutineManager _instance = null;
    public static CoroutineManager Instance() = { _instance = this;}
    private LinkedList<IEnumerator> coroutineList = new LinkedList<IEnumerator>();
    public void StartCoroutine(IEnumerator ie)
    {
        coroutineList.AddList(ie);
    }
    public void StopCoroutine(IEnumerator ie)
    {
        coroutineList.Remove(ie);
    }

public void UpdateCoroutine()
{
    var node = coroutineList.First;
    while(node!=null)
    {
        IEnumerator ir = node.Value;
        bool ret = true;
        if(ie.Current is IWait)
        {
            IWait wait = (IWait)ie.Current;
            if(wait.Tick())
            {
                ret = ie.MoveNext();
            }
        }else
        {
            ret = ie.MoveNext();
        }
        if(!ret)
        {
            coroutineList.Remove(ndoe);
        }
        node = node.Next;
    }
}

 

 

 

 

 

 

 

  

posted @ 2023-07-14 17:47  CatSevenMillion  阅读(577)  评论(0编辑  收藏  举报