[Unity3D 实践] 使用迭代器实现一个最最简陋的Unity协程(Coroutine)

参考资料

  • Unity协程(Coroutine)原理深入剖析
  • Unity协程(Coroutine)原理深入剖析再续
  • Coroutine,你究竟干了什么?
  • Coroutine,你究竟干了什么?(小续)
  • 匹夫细说C#:庖丁解牛迭代器,那些藏在幕后的秘密

Unity协程是什么?

Unity协程有什么用?

Unity协程(Coroutine)并不是真正的协程。简单来说,协程实现了游戏中的计时器功能,让大家可以很方便的写一段延迟执行某某函数的代码。

 

Unity协程的基本实现原理

Unity的协程是由 C#的迭代器(IEnumerator) + Unity的Coroutine类 来实现的。

在我的理解中,协程实现函数延迟执行的关键就在于迭代器函数中的语句都是延迟执行的,每次仅执行到yield语句之前的内容。而Coroutine类则是迭代器的容器和控制器,决定迭代器中每一个以yield语句开头的代码块在什么时间执行。

IEnumerator CoroutineFunction()
{
    Debug.Log("代码块1");                    //代码块1,开启协程时执行

    yield return new WaitForSeconds(3.2f);  // 代码块2,延迟3.2秒执行
    Debug.Log("代码块2");

    yield return null;                      // 代码块3,下一帧执行
    Debug.Log("代码块3");

    yield break;                            //代码块4,不执行
    Debug.Log("代码块4");
}

 

Coroutine类的功能就是保存迭代器并Monobehavior循环中自动控制迭代器的执行。所谓自动控制就是,根据迭代器函数中yield语句来控制啥时候执行迭代器的下一步(MoveNext方法)或者停止执行迭代器。

yield return语句可以返回null,也可以返回WaitForSeconds等对象,Coroutine类就是对这个返回值做了特殊处理来实现延迟执行迭代器的功能。根据我的脑补,实现了下面这个简陋的协程:

在最最简陋的游戏循环中实现一个简陋的协程

using System.Collections;

namespace Program
{
    class Program
    {
        static void Main(string[] attries)
        {
            //协程对象,本质就是个迭代器,这里只实现了一个协程,Unity里应该是用了List之类的数据结构存储很多个协程,以便进行批量处理
            IEnumerator myEnumrator;
            //协程注册
            myEnumrator = TestEnumerator();
            //协程控制器,实现协程暂停的功能,标识是否应该继续执行协程,一个协程对象对应一个协程控制器对象,null代表继续执行
            WaitForFrameCount waitForCount = null;

            bool isGaming = true;
            int frameCount = 0;
            //游戏主循环
            while (isGaming) {
                frameCount++;
                Console.WriteLine($"-----游戏帧-{frameCount.ToString()}-----");

                //处理协程等待计数器
                if (waitForCount != null && !waitForCount.IsDone())
                {
                    Console.WriteLine("计数器更新");
                    waitForCount.AddCount();
                }

                //根据waitForCount是否为null,判断协程是否继续执行
                if (waitForCount == null)
                {
                    Console.WriteLine("协程执行下一步");
                    if (myEnumrator.MoveNext())
                    {
                        waitForCount = myEnumrator.Current as WaitForFrameCount;
                    }
                    else
                    {
                        isGaming = false;
                    }
                }
                else
                {
                    if (waitForCount.IsDone())
                    {
                        waitForCount = null;
                    }
                }


                Console.WriteLine("---------------------");
            }

            Console.ReadLine();
        }

        //可以任意编写的协程函数
        static IEnumerator TestEnumerator()
        {
            Console.WriteLine("Do 1");
            yield return new WaitForFrameCount(3);
            Console.WriteLine("Do 2");
            yield return null;
            Console.WriteLine("Do 3");
            yield return new WaitForFrameCount(2);
            Console.WriteLine("Do 4");
        }

        
        //协程控制器类,对应Unity里的WaitForSeconds之类的类
        class WaitForFrameCount
        {
            int max = 0;
            int count = 0;

            public WaitForFrameCount(int maxCount)
            {
                max = maxCount;
            }

            public void AddCount()
            {
                count++;
            }

            public bool IsDone()
            {
                if (count >= max)
                {
                    count = 0;
                    return true;
                }
                return false;
            }
        }
    }
}

这个简陋的协程例子包含3个部分

  • Main函数,游戏主循环以及控制迭代器的逻辑,在每次帧循环中更新协程控制器,并判断协程是否应该继续执行,如果协程执行完毕则结束游戏循环
  • 迭代器函数,也就是协程函数,可以任意编程,与Unity中的协程函数格式相同
  • 协程控制器类WaitForFrameCount,仅包含3个方法的计数器,用于yield return语句返回给主循环中的协程控制器变量。

这个实现是个人的脑补,如果有错误或问题欢迎在评论里留言指正。

 

使用协程的注意事项

1.协程的生命周期

协程会在MonoBehavior脚本被Destroy或者gameObject被SetActive(false)时中断

2.避免重复开启协程带来的问题

  • 同一个协程可以同时多次开启,重复开启的协程在共同修改同一份数据时可能会带来问题,所以再开启协程时应该中断之前的协程再进行开启:unity协程,频繁调用时,只执行最后一次
  • 中断协程的方法:①关闭Unity Coroutine的正确姿势,最佳方案就是保存StartCoroutine()方法返回的IEnumrator对象,用StopCoroutine(IEnumrator对象)方法来中断协程;②目标Monobihavior脚本所在的gameObject.SetActive(false),PS:仅设置脚本组件MonoBehaviour.enabled = false 是不会中断协程的;

3.协程更新的时间

在Unity的每个Monobehavior循环中,协程的处理位置是固定的,具体参见各Event Functions的执行顺序。

http://wiki.unity3d.com/index.php?title=CoroutineScheduler

你可能感兴趣的:(Unity3D,个人实践)