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

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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity协程的底层实现是基于C#的迭代器实现的。在C#中,使用yield关键字可以将方法转换为迭代器,通过迭代器可以实现协程的效果。Unity中的协程也是基于这个原理实现的。 如果要自己实现一个协程,可以按照以下步骤进行: 1. 定义一个委托,用于表示协程执行体。 ```csharp public delegate IEnumerator CoroutineDelegate(); ``` 2. 定义一个协程类,保存协程执行体和当前执行状态。 ```csharp public class Coroutine { private CoroutineDelegate m_CoroutineDelegate; private IEnumerator m_Enumerator; private bool m_IsDone; public bool IsDone { get { return m_IsDone; } } public Coroutine(CoroutineDelegate coroutineDelegate) { m_CoroutineDelegate = coroutineDelegate; m_Enumerator = m_CoroutineDelegate(); m_IsDone = false; } public void Update() { if (m_Enumerator != null && !m_IsDone) { if (!m_Enumerator.MoveNext()) { m_IsDone = true; } } } } ``` 3. 在需要使用协程的地方,创建一个协程对象并添加到一个协程管理器中。 ```csharp public class CoroutineManager : MonoBehaviour { private static CoroutineManager m_Instance; private List<Coroutine> m_Coroutines = new List<Coroutine>(); public static CoroutineManager Instance { get { if (m_Instance == null) { m_Instance = new GameObject("CoroutineManager").AddComponent<CoroutineManager>(); } return m_Instance; } } private void Update() { for (int i = m_Coroutines.Count - 1; i >= 0; i--) { Coroutine coroutine = m_Coroutines[i]; coroutine.Update(); if (coroutine.IsDone) { m_Coroutines.RemoveAt(i); } } } public Coroutine StartCoroutine(CoroutineDelegate coroutineDelegate) { Coroutine coroutine = new Coroutine(coroutineDelegate); m_Coroutines.Add(coroutine); return coroutine; } } ``` 4. 在协程中使用yield关键字来实现挂起和恢复。 ```csharp private IEnumerator MyCoroutine() { Debug.Log("Start Coroutine"); yield return null; Debug.Log("Wait One Frame"); yield return new WaitForSeconds(1.0f); Debug.Log("Wait One Second"); yield return new WaitForEndOfFrame(); Debug.Log("Wait End Of Frame"); } ``` 以上就是一个简单的协程实现。注意,实际应用中还需要考虑协程的取消、异常处理等问题,需要根据具体需求进行扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值