Unity协程解析——状态机实现的代码分步执行

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/DdogYuan/article/details/84535802

Unity协程的效果

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

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

协程跟Update()其实一样的,都是Unity每帧对会去处理的函数(如果有的话),至少是每帧的LateUpdate()后去运行。

在Unity的脚本声明周期中示意如下:
Unity脚本声明周期

Unity协程的声明和使用

示例如下:

using UnityEngine;
using System.Collections;
 
public class Example : MonoBehaviour {
    // 保持一个执行脚本的引用
    private IEnumerator coroutine;
 
    void Start () {
        print("Starting " + Time.time);
        //方法1:保存协程的引用,开启协程;
        {
            coroutine = WaitAndPrint(3.0f);
            StartCoroutine(coroutine);
        }
        //方法2:把协程的方法作为字符串,开启协程
        {
            StartCoroutine("WaitAndPrint",3.0f);
        }
        print("Done " + Time.time);
    }
 
    // 每隔3秒,打印一次
    // 由yield来挂起(暂停)函数
    public IEnumerator WaitAndPrint(float waitTime) {
        while (true) {
            yield return new WaitForSeconds(waitTime);
            print("WaitAndPrint " + Time.time);
        }
    }
}

可见,协程是通过IEnumerator实现的,每当yield return后就会挂起直到满足条件才继续执行后面的代码。

IEnumerator内部的实现

例如对于语句:

using System;  
using System.Collections;  
  
class Test  
{  
    static IEnumerator GetCounter()  
    {  
        for (int count = 0; count < 10; count++)  
        {  
            yield return count;  
        }  
    }  
}  

C#编译器会对应生成:

internal class Test  
{  
    // Note how this doesn't execute any of our original code  
    private static IEnumerator GetCounter()  
    {  
        return new <GetCounter>d__0(0);  
    }  
  
    // Nested type automatically created by the compiler to implement the iterator  
    [CompilerGenerated]  
    private sealed class <GetCounter>d__0 : IEnumerator<object>, IEnumerator, IDisposable  
    {  
        // Fields: there'll always be a "state" and "current", but the "count"  
        // comes from the local variable in our iterator block.  
        private int <>1__state;  
        private object <>2__current;  
        public int <count>5__1;  
  
        [DebuggerHidden]  
        public <GetCounter>d__0(int <>1__state)  
        {  
            this.<>1__state = <>1__state;  
        }  
  
        // Almost all of the real work happens here  
        private bool MoveNext()  
        {  
            switch (this.<>1__state)  
            {  
                case 0:  
                    this.<>1__state = -1;  
                    this.<count>5__1 = 0;  
                    while (this.<count>5__1 < 10)//这里针对循环处理  
                    {  
                        this.<>2__current = this.<count>5__1;  
                        this.<>1__state = 1;  
                        return true;  
                    Label_004B:  
                        this.<>1__state = -1;  
                        this.<count>5__1++;  
                    }  
                    break;  
  
                case 1:  
                    goto Label_004B;  
            }  
            return false;  
        } 
        ...//省略了一些方法
    }  
}  

状态机实现代码分步执行

如何实现“当下次再调用MoveNext()方法时,我们的方法会继续从上一个yield return语句处开始执行。”?

根据上部分C#编译器生成的代码我们可以总结到:通过状态机。

对于没有for、while等循环的语句,是通过构造多个switch case语句,通过状态切换来达到分部执行代码。

private bool MoveNext()
{
    switch(this.<>1_state)
    {
        case 0:
            this.<>1_state = -1;
            Console.WriteLine("第一个yield return 前的代码);
            this.<>2_current = 1;
            this.<>1_state = 1;
            
          case 1:
            this.<>1_state = -1;
            Console.WriteLine("第二个yield return 前的代码);
            this.<>2_current = 2;
            this.<>1_state = 2;
            
          case 2:
            this.<>1_state = -1;
            Console.WriteLine("第二个yield return 后的代码);
            break;
    }
    return false;
}

而对于循环语句,使用goto语句来实现状态切换:

private bool MoveNext()
{
    switch(this.<>1_state)
    {
        case 0:
            this.<>1_state = -1;
            this.<i>5_1 = 0;
            while(this.<i>5_1 < 10)
            {
                this.<>2_current = this.<i>5_1;
                this.<>1_state = 1;
                return true;
             Label_0046:
                 this.<>1_state = -1;
                 this.<i>5_1++;
            }
            break;
          
          case 1:
              goto Label_0046;
    }
    return false;
}

参考前文中的图Unity脚本生命周期,Unity在每帧都会调用某个脚本上的协程,即调用协程的MoveNext函数,当MoveNext函数返回false则结束协程,否则如果返回 true ,就从当前位置继续往下执行。

参考

  1. 《Unity 3D脚本编程 使用C#语言开发跨平台游戏》第八章
  2. Unity3D协程进阶-原理剖析: https://blog.csdn.net/yupu56/article/details/52791952

猜你喜欢

转载自blog.csdn.net/DdogYuan/article/details/84535802
今日推荐