Unity协程的一些理解(一)

本文详细探讨了Unity中协程的使用与特性,包括继承MonoBehaviour的要求、协程执行时机、常见等待方法及其受TimeScale影响的情况。同时,介绍了如何利用协程实现定时器和回调功能,并提供了一个自定义协程迭代器的实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Unity协程的一些理解

使用Unity开发游戏过程中,难免会使用到协程。现在总结下使用过程中的一些理解及使用过程中遇到的一些坑。
1. 使用协成必须继承MonoBehaviour。 开发过程中我们会单独写个协程类,来使用协程的功能。常见的写法:

  public static class CoroutineManager
    	{
    	    private static CoroutineTask mTask;
    	    static CoruntineManager()
    	    {
    	        GameObject go = new GameObject("CoroutineManager");
    	        mTask = go.AddComponent<CoroutineTask>();
    	    }
    	    public static Coroutine StartCoroutine(IEnumerator co)
    	    {
    	       return mTask.StartCoroutine(co);
    	    }
    	}
    	//当我们将this.gameObject.SetActive(false)时协程也随之终止。
    	//我们不可以通过SetActive(true)来恢复协程
    	public class CoroutineTask : MonoBehaviour
    	{
    	}

2.协程是在LateUpdate后执行,且waitforSecond 受到TimeScale的影响。 我们都知道Time.time 和Time.deltaTime 是受到TimeScale的影响。当timeScale 变大,上面两个值也变大。我们用下面一段代码测试:

    /*我们还知道TimeScale会影响FixUpdate每秒的执行次数,
    却不会影响Update和LateUpdate的执行次数。当TimeScale为0时。
    FixUpdate就会停止执行。对于协程,我们测试结果如下:
    [SerializeField] private float timeScale;
    private IEnumerator TestTimeScale()
    {
    ****协程常见的几个wait方法
       // 1.yield return new waitfor second 受到TimeScale影响
       //2. 一直到下一次FixUpdate执行 受到TimeScale影响
            yield return new WaitForFixedUpdate();
       // 3. 游戏真实过去的时间 不受TimeScale 影响
         yield return new WaitForSecondsRealtime(5f);
       //4. Waits until the end of the frame after all cameras and GUI is rendered, just
           before displaying the frame on screen.不受TimeScale影响
         yield return new WaitForEndOfFrame();
       // 5.  直到返回会True时 才开始往下执行
        yield return new WaitUntil(() =>
        {
            if (Input.GetKeyDown(KeyCode.A))
            {
                return true;
            }
            return false;
        });
     // 6.直到委托方法返回的时false 往下执行
        yield return new WaitWhile(() =>
        {
            if (Input.GetKeyDown(KeyCode.B))
            {
                return false;
            }
            return false;
        });
      //  yield return new WaitForSeconds(2f);
         yield return null;
        Debug.LogError(" two Secod Later");
    }
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Time.timeScale = timeScale;
            CoroutineManager.StartCoroutine(TestTimeScale());
        }
    }

3.协程其实就是一个IEnumerator(迭代器),IEnumerator 接口有两个方法 Current 和 MoveNext() ,只有当MoveNext()返回 true时才可以访问 Current,否则会报错。迭代器方法运行到 yield return 语句时,会返回一个expression表达式并保留当前在代码中的位置。 当下次调用迭代器函数时执行从该位置重新启动。 Unity在每帧做的工作就是:调用 协程(迭代器)MoveNext() 方法,如果返回 true ,就从当前位置继续往下执行。

 //利用协程的特点 我们可以实现简单的小工具
 //1.时间计时器
  //指定时间后回掉
    static IEnumerator CountDown(float delayTime, Action co)
    {
        yield return new WaitForSeconds(delayTime);
        if (co != null)
        {
            co();
        }
    }
    //每一帧回掉
    static IEnumerable CallBackEveryFrame(float time, Action<bool> callBack, bool ingoreTimeScale = true)
    {
        float lastTime = Time.realtimeSinceStartup;
        for (float deltaTime = time; deltaTime > 0;)
        {

            yield return null;
            float delta = ingoreTimeScale ? (Time.realtimeSinceStartup - lastTime) : Time.timeScale;
            deltaTime = deltaTime - delta;
            lastTime = Time.realtimeSinceStartup;
            bool lastFrame = deltaTime <= 0;
            if (callBack != null)
            {
                callBack(lastFrame);
            }
        }
    }
    public static void CallBack(float totalTime,float interVal,Action callBack)
    {
        mTask.StartCoroutine(CallBackEveryInterval(totalTime,interVal, callBack));
    }
    //每隔一定时间回掉一次(实现这个功能还可以新写个类继承CustomYieldInstruction 这里就不再写了)
    static IEnumerator CallBackEveryInterval(float totalTime,float interVal,Action callBack)
    {
        for (float deltaTime = totalTime; deltaTime > 0;deltaTime-=interVal)
        {
            yield return new WaitForSeconds(interVal);
            if (callBack != null)
            {
                callBack();
            }
        }
    }


4.上面说到协程实际上是一个迭代器,现在我们用 C# 我们简单实现一个迭代器:

    class Iteration : IEnumerable
    {
        public object[] someObjects;
        public int startPoint = -1;
        public IEnumerator GetEnumerator()
        {
            //return new IterationEnumerator(this);
            **//在C#2中提供了迭代块和yield return 语句 可以完全用下面的方法来实现GetEnumerator方法 而不必写IterationEnumerator类
            for (int index = 0; index < this.someObjects.Length; index++)
            {
                yield return someObjects[(index + startPoint)%someObjects.Length];
            }**
        }

        public Iteration(Object[] someObjects, int startPoint)
        {
            this.someObjects = someObjects;
            this.startPoint = startPoint;
        }

    }

    class IterationEnumerator : IEnumerator
    {
        //迭代器对象
        private Iteration iteration;
        //当前的游标位置
        private int postion = -1;

        internal IterationEnumerator(Iteration iteration)
        {
            this.iteration = iteration;
            postion = -1;
        }
        //当调用完MoveNext之后 才会调用GetCurrent
        public bool MoveNext()
        {
            if (postion != iteration.someObjects.Length)
            {
                postion++;
            }
            //超过长度 返回false
            return postion < iteration.someObjects.Length;
        }

        public void Reset()
        {
            postion = -1;
        }

        public object Current
        {
            get
            {
                if (postion == -1 || postion == iteration.someObjects.Length)
                {
                    throw new InvalidOperationException();
                }
                int index = postion + iteration.startPoint;
                index = index % iteration.someObjects.Length;
                return iteration.someObjects[index];
            }
        }
    }
    //调用
    static void Main(string[] args)
        {
             string [] text = new string[]{"0","1","2","3"};
            Iteration a =new Iteration(text,2);
            IEnumerator n = a.GetEnumerator();
            while (true)
            {
                bool result = n.MoveNext();
                if(!result)
                    break;
                Console.WriteLine(n.Current);
            }
            Console.ReadKey();
        }

同样在Unity中我们也写个:

**//写个MyCoroutine实现IEnurator**
public class MyCoroutine : IEnumerator
{
    private Action callBack = null;
    private float endTime;

    public MyCoroutine(float time, Action callBack)
    {
        this.callBack = callBack;
        endTime = Time.realtimeSinceStartup + time;
    }

    public bool MoveNext()
    {
      //当return false时迭代器停止
        return Time.realtimeSinceStartup < endTime;
    }

    public void Reset()
    {
        throw new System.NotImplementedException();
    }
    public object Current
    {
        get
        {
        ,//每次迭代都会调用
            if (callBack != null)
            {
                callBack();
            }
            return null;
        }
    }
    **//在TestCoroutine中调用 也实现了一个计时回调**
    void Start () {
        MyCoroutine co = new MyCoroutine(10, () =>
        {
           Debug.Log("回调");
        });
        //① 这么写
	    StartCoroutine(co);
	    //② 也可以这么写  这样我就可以在 MyCoroutine中实现 new waitForSecod等 Unity API提供的功能。当然也可以自己写一些
	    StartCoroutine(Test())
	}
    private IEnumerator Test()
    {
        yield return new MyCoroutine(10, () =>
        {
            Debug.Log("TestCoroutine");
        });
        //十秒后才会输出下面的Log
           Debug.Log("-------------");
    }
	//输出结果:每一帧都会回调,知道MoveNext return为false。StartCoroutine 实现了上面Main函数中 While循环内的东西。
<think>好的,我需要解释C#协程Unity协程的区别。首先,我得确认两者的基本概念。 C#协程通常是基于IEnumerator的迭代器方法,通过yield return来控制流程。而Unity协程虽然也使用IEnumerator,但它是基于Unity自己的生命周期和帧循环来驱动的。 接下来,执行环境的区别。C#原生协程需要开发者自己管理执行,比如用MoveNext()来推进。而Unity协程由引擎自动管理,依赖于MonoBehaviour和StartCoroutine方法。 然后要考虑调度机制。Unity协程与游戏帧率绑定,比如yield return null会在下帧继续执行,而C#原生协程的执行机由用户控制,可能不依赖帧率。 生命周期管理方面,Unity协程和GameObject绑定,物体禁用或销毁协程终止。C#协程的生命周期需要手动控制,可能更灵活但也更复杂。 功能扩展性上,Unity提供了WaitForSeconds等定制指令,C#协程需要自己实现类似的功能,或者依赖其他库。 最后,适用场景不同。Unity协程主要用于游戏逻辑,如动画、延等,而C#协程可能用于更通用的异步处理,但需要更多的底层管理。 需要确保术语准确,比如Unity的Yield Instruction,还有例子是否恰当。可能需要检查是否有遗漏点,比如线程安全或者异常处理方面的差异。 总结应该分点说明,结构清晰,让用户容易理解两者的核心区别。</think>C#协程Unity协程的主要区别体现在以下几个方面: 1. **实现机制** - **C#原生协程**:基于`IEnumerator`迭代器,通过`yield return`语句实现控制流暂停/恢复。需要手动调用`MoveNext()`推进执行 - **Unity协程**:基于MonoBehaviour的生命周期系统,通过`StartCoroutine()`自动调度,与游戏主线程和帧循环深度集成 2. **执行环境** - **C#原生协程**:可在任意C#环境运行(控制台/WPF等),不依赖特定框架 - **Unity协程**:必须继承自MonoBehaviour,依赖Unity引擎的更新循环 3. **调度机制** - **C#原生协程**:执行机完全由开发者控制,可自定义恢复条件 ```csharp IEnumerator MyCoroutine() { yield return new WaitUntil(() => condition); // 需自定义等待条件 } ``` - **Unity协程**:内置丰富调度指令,自动与帧率同步 ```csharp yield return new WaitForSeconds(1); // 等待1秒(受Time.timeScale影响) yield return new WaitForFixedUpdate(); // 等待物理更新 ``` 4. **生命周期** - **C#原生协程**:需要手动管理协程对象的生命周期 - **Unity协程**:自动关联GameObject生命周期,当物体被禁用/销毁自动终止 5. **线程模型** - **C#原生协程**:本质是单线程的迭代器,不涉及多线程 - **Unity协程**:严格运行在主线程,与Unity API调用规则致 6. **应用场景** - **C#原生协程**:适用于通用异步流程控制(如分步加载、状态机等) - **Unity协程**:专为游戏开发设计(角色动画序列、延触发、资源分帧加载等) **典型代码对比** ```csharp // C#原生协程(控制台应用示例) IEnumerator CountToThree() { Console.WriteLine("One"); yield return null; // 简单暂停 Console.WriteLine("Two"); yield return new CustomWait(1000); // 需自定义等待逻辑 Console.WriteLine("Three"); } // Unity协程 IEnumerator FireProjectile() { while(true) { Instantiate(bulletPrefab); yield return new WaitForSeconds(0.5f); // 内置间等待 } } ``` **关键差异总结表** | 特性 | C#协程 | Unity协程 | |---------------------|-------------------|--------------------| | 依赖框架 | 无 | 必须依赖Unity引擎 | | 生命周期管理 | 手动控制 | 自动关联GameObject | | 内置等待指令 | 需自定义 | 提供丰富YieldInstruction | | 线程安全 | 需自行处理 | 强制主线程执行 | | 典型应用场景 | 通用异步逻辑 | 游戏对象行为控制 | 理解这些差异有助于避免在Unity开发中错误使用原生C#协程特性,同也能更好地利用Unity协程优化游戏逻辑实现。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值