新用户注册入口 老用户登录入口

[转载]Unity 协程探究

文章作者:转载 更新时间:2023-11-24 16:50:42 阅读数量:388
文章标签:UnityUnity
本文摘要:Unity中的协程 Coroutine 作为一种便捷工具,用于处理随时间推进的任务。通过在 IEnumerator 类型的函数中使用 yield return 关键字,可实现协程的暂停与恢复执行。MonoBehaviour 组件提供 StartCoroutine 和 StopCoroutine 方法启动和停止协程。yield return 后的不同表达式类型影响着协程的暂停与恢复时机,如 null、数字、布尔值、字符串以及实现了 IEnumerator 接口或继承自 YieldInstruction 类的对象等。Unity 协程基于 C# 的迭代器模式实现,在引擎内部按需驱动异步逻辑执行,确保了游戏对象的行为能够灵活地跨越多个帧更新周期进行管理。
转载文章

本篇文章为转载内容。原文链接:https://blog.csdn.net/NRatel/article/details/102870744。

该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。

作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。

如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。

一、官方手册中的描述

1、Manual/Coroutines

函数在调用时, “从调用到返回” 都发生在一帧之内,想要处理 “随时间推移进行的事务”, 相比Update,使用协程来执行此类任务会更方便。

协程在创建时,通常是一个 “返回值类型 为 IEnumerator”、“函数体中包含 yield return 语句 ” 的函数。

yiled return 可以暂停协程的执行,并在恰当时候恢复。具体在何时恢复,由 yield 的返回值决定。

启动协程,必须使用 MonoBehaviour 的 StartCoroutine 方法。

停止协程,可以使用 MonoBehaviour 的 StopCoroutine 方法 或 StopAllCoroutine 方法。

注意:以下情况也可能使协程停止
   1)、销毁启动协程的组件(GameObject.Destory(component);) ==> 协程停止
   2)、禁用启动协程的组件(component.enabled = false;)==> 协程不停止
   3)、销毁启动协程的组件所在的物体(GameObject.Destory(gameobject);) ==> 协程停止
   4)、隐藏启动协程的组件所在的物体(gameobject.SetActive(false);) ==> 协程停止

2、MonoBehaviour.StartCoroutine

StartCoroutine 方法总是立刻返回一个 Coroutine 对象(同步返回)。

无法保证协同程序按其启动顺序结束,即使他们在同一帧中完成也是如此(异步无序完成)。

可以在一个协程中启动另一个协程(支持协程嵌套)。

二、Unity中的 yield 语句类型

1、yield break;    //打断协程运行

2、yield return null;    //挂起协程,并从下一帧继续

3、yield return + “任意数字”;    //挂起协程,并从下一帧继续

4、yield return + “bool值”;    //挂起协程,并从下一帧继续

5、yield return + “任意字符串”;    //挂起协程,并从下一帧继续

6、yield return + “普通Object”;    //挂起协程,并从下一帧继续

7、yield return + “任意实现了 IEnumerator 接口的对象”。重要!(可嵌套)

    Unity 中,常见的、直接或间接实现了 IEnumerator 接口的类有:
        ------------------------------------------------------------------------------------------------
        CustomYieldInstruction (abstarct)   ——|>   IEnumerator (interface)
       
------------------------------------------------------------------------------------------------
        WaitUnitil (sealed)   ——|>   CustomYieldInstruction
        WaitWhile (sealed)
  ——|>   CustomYieldInstruction
        WaitForSecondsRealtime (非sealed,但未发现子类) 
——|>   CustomYieldInstruction
        WWW (非sealed,但未发现子类) 
——|>   CustomYieldInstruction
       
------------------------------------------------------------------------------------------------
        随着Unity更新或在一些可选的Package中,可能有更多。。。
        ------------------------------------------------------------------------------------------------

8、yield return + “任意继承了 YieldInstruction 类 ([UsedByNativeCode],源码C#层中无具体实现) 的对象”。重要!(可嵌套)

    Unity 中,常见的、直接或间接继承了 YieldInstruction 类的类有:
        ------------------------------------------------------------------------------------------------
        WaitForSeconds (sealed)   ——|>   YieldInstruction
        Coroutine (sealed)  ——|>   YieldInstruction (Coroutine 是 StartCoroutine方法的返回值,意味着协程中可嵌套协程)
        WaitForEndOfFrame (sealed) ——|>   YieldInstruction
        WaitForFixedUpdate (sealed)  ——|>   YieldInstruction
        AsyncOperation ——|>   YieldInstruction
       
------------------------------------------------------------------------------------------------
         AssetBundleCreateRequest (非sealed,但未发现子类) ——|>   AsyncOperation
        
AssetBundleRecompressOperation (非sealed,但未发现子类) ——|>   AsyncOperation
        
AssetBundleRequest (非sealed,但未发现子类) ——|>   AsyncOperation
        
ResourceRequest (非sealed,但未发现子类) ——|>   AsyncOperation
        
UnityEngine.Networking.UnityWebRequestAsyncOperation (非sealed,但未发现子类) ——|>   AsyncOperation
        
UnityEngine.iOS.OnDemandResourcesRequest (sealed) ——|>   AsyncOperation
        ------------------------------------------------------------------------------------------------
        随着Unity更新或在一些可选的Package中,可能有更多。。。
        ------------------------------------------------------------------------------------------------

***测试验证 第2、3、4、5、6条 如下:

using System.Collections;
using UnityEngine;public class Test : MonoBehaviour
{void Start(){StartCoroutine(Func1());}IEnumerator Func1(){Debug.Log("Time.frameCount: " + Time.frameCount);yield return null;Debug.Log("Time.frameCount: " + Time.frameCount);yield return 0;Debug.Log("Time.frameCount: " + Time.frameCount);yield return 1;Debug.Log("Time.frameCount: " + Time.frameCount);yield return 99; //其他整数Debug.Log("Time.frameCount: " + Time.frameCount);yield return 0.5f; //浮点数值Debug.Log("Time.frameCount: " + Time.frameCount);yield return false; //bool值Debug.Log("Time.frameCount: " + Time.frameCount);yield return "Hi NRatel!";  //字符串Debug.Log("Time.frameCount: " + Time.frameCount);yield return new Object();  //任意对象Debug.Log("Time.frameCount: " + Time.frameCount);}
}

***测试验证 第7条 如下:

using System.Collections;
using UnityEngine;public class Test : MonoBehaviour
{void Start(){StartCoroutine(Func1());}IEnumerator Func1(){Debug.Log("Func1");yield return Func2();}IEnumerator Func2(){Debug.Log("Func2");yield return Func3();}IEnumerator Func3(){Debug.Log("Func3");yield return null;}
}

三、Unity协程实现原理

1、C# 的迭代器。

现在已经知道:协程肯定与IEnumerator有关,因为启动协程时需要一个 IEnumerator 对象。
而 IEnumerator 是C#实现的 迭代器模式 中的 枚举器(用于迭代的游标)。

迭代器相关接口定义如下:

namespace System.Collections
{//可枚举(可迭代)对象接口public interface IEnumerable{IEnumerator GetEnumerator();}//迭代游标接口public interface IEnumerator{object Current { get; }bool MoveNext();void Reset();}
}

参考 MSDN C#文档中对于 IEnumerator、IEnumerable、迭代器 的描述。

利用 IEnumerator 对象,可以对与之关联的 IEnumerable 集合 进行迭代:

    1)、通过 IEnumerator 的 Current 方法,可以获取集合中位于枚举数当前位置的元素。

    2)、通过 IEnumerator 的 MoveNext 方法,可以将枚举数推进到集合的下一个元素。如果 MoveNext 越过集合的末尾, 则枚举器将定位在集合中最后一个元素之后, 同时 MoveNext 返回 false。 当枚举器位于此位置时, 对 MoveNext 的后续调用也将返回 false 。如果最后一次调用 MoveNext 时返回 false,则 Current 未定义(结果为null)。

    3)、通过 IEnumerator 的 Reset 方法,可以将“迭代游标” 设置为其初始位置,该位置位于集合中第一个元素之前。

2、C# 的 yield 关键字。

C#编译器在生成IL代码时,会将一个返回值类型为 IEnumerator 的方法(其中包含一系列的 yield return 语句),构建为一个实现了 IEnumerator 接口的对象。

注意,yield 是C#的关键字,而非Unity定义!IEnumerator 对象 也可以直接用于迭代,并非只能被Unity的 StartCoroutine 使用!

using System.Collections;
using UnityEngine;public class Test : MonoBehaviour
{void Start(){IEnumerator e = Func();while (e.MoveNext()){Debug.Log(e.Current);} }IEnumerator Func(){yield return 1;yield return "Hi NRatel!";yield return 3;}
}

对上边C#代码生成的Dll进行反编译,查看IL代码:

3、Unity 的协程。

  Unity 协程是在逐帧迭代的,这点可以从 Unity 脚本生命周期 中看出。

可以大胆猜测一下,实现出自己的协程(功能相似,能够说明逐帧迭代的原理,不是Unity源码):

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Test : MonoBehaviour
{private Dictionary<IEnumerator, IEnumerator> recoverDict;   //key:当前迭代器 value:子迭代器完成后需要恢复的父迭代器private IEnumerator enumerator;private void Start(){//Unity自身的协程//StartCoroutine(Func1());//自己实现的协程StarMyCoroutine(Func1());}private void StarMyCoroutine(IEnumerator e){recoverDict = new Dictionary<IEnumerator, IEnumerator>();enumerator = e;recoverDict.Add(enumerator, null);  //完成后不需要恢复任何迭代器}private void LateUpdate(){if (enumerator != null){DoEnumerate(enumerator);} }private void DoEnumerate(IEnumerator e){object current;if (e.MoveNext()){current = e.Current;}else{//迭代结束IEnumerator recoverE = recoverDict[e];if (recoverE != null){recoverDict.Remove(e);}//恢复至父迭代器, 若没有则会至为nullenumerator = recoverE;return;}//null,什么也不做,下一帧继续if (current == null) { return; }Type type = current.GetType();//基础类型,什么也不做,下一帧继续if (current is System.Int32) { return; }if (current is System.Boolean) { return; }if (current is System.String) { return; }//IEnumerator 类型, 等待内部嵌套的IEnumerator迭代完成再继续if (current is IEnumerator){//切换至子迭代器enumerator = current as IEnumerator;recoverDict.Add(enumerator, e);return;}//YieldInstruction 类型, 猜测也是类似IEnumerator的实现if (current is YieldInstruction){//省略实现return;} }IEnumerator Func1(){Debug.Log("Time.frameCount: " + Time.frameCount);yield return null;Debug.Log("Time.frameCount: " + Time.frameCount);yield return "Hi NRatel!";Debug.Log("Time.frameCount: " + Time.frameCount);yield return 3;Debug.Log("Time.frameCount: " + Time.frameCount);yield return new WaitUntil(() =>{return Time.frameCount == 20;});Debug.Log("Time.frameCount: " + Time.frameCount);yield return Func2();Debug.Log("Time.frameCount: " + Time.frameCount);}IEnumerator Func2(){Debug.Log("XXXXXXXXX");yield return null;Debug.Log("YYYYYYYYY");yield return Func3();   //嵌套 IEnumerator}IEnumerator Func3(){Debug.Log("AAAAAAAA");yield return null;Debug.Log("BBBBBBBB");yield return null;}
}

对比结果,基本可以达成协程作用,包括 IEnumerator 嵌套。
但是 Time.frameCount 的结果不同,想来实现细节必然是有差别的。

四、部分Unity源码分析

1、CustomYieldInstruction 类

可以继承该类,并实现自己的、需要异步等待的类

原理:
    当协程中 yield return “一个CustomYieldInstruction的子类”; 其实就相当于在原来的 迭代器A 中,插入了一个 新的迭代器B。
    当迭代程序进入 B ,如果 keepWaiting 为 true,MoveNext() 就总是返回 true。
    上面已经说过,迭代器在迭代时,MoveNext() 返回false 才标志着迭代完成!
    那么,B 就总是完不成,直到 keepWaiting 变为 false。
    这样 A 运行至 B处就 处于了 等待B完成的状态,相当于A挂起了。

 猜测 YieldInstruction 也是类似的实现。

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_Licenseusing System.Collections;namespace UnityEngine
{public abstract class CustomYieldInstruction : IEnumerator{public abstract bool keepWaiting{get;}public object Current{get{return null;} }public bool   MoveNext() { return keepWaiting; }   public void   Reset() {} }
}

2、WaitUntil 类

    语义为 “等待...直到满足...”
    继承自 CustomYieldInstruction,需要等待时让 m_Predicate 返回 false (keepWating为true)。

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_Licenseusing System;namespace UnityEngine
{public sealed class WaitUntil : CustomYieldInstruction{Func<bool> m_Predicate;public override bool keepWaiting { get { return !m_Predicate(); } }public WaitUntil(Func<bool> predicate) { m_Predicate = predicate; } }
}

3、WaitWhile 类

    语义为 “等待...如果满足...”
    继承自 CustomYieldInstruction,需要等待时让 m_Predicate 返回 true (keepWating为true)。
    与 WaitUntil 的实现恰好相反。

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_Licenseusing System;namespace UnityEngine
{public sealed class WaitWhile : CustomYieldInstruction{Func<bool> m_Predicate;public override bool keepWaiting { get { return m_Predicate(); } }public WaitWhile(Func<bool> predicate) { m_Predicate = predicate; } }
}

本篇文章为转载内容。原文链接:https://blog.csdn.net/NRatel/article/details/102870744。

该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。

作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。

如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。

相关阅读
文章标题:[转载][洛谷P1082]同余方程

更新时间:2023-02-18
[转载][洛谷P1082]同余方程
文章标题:[转载]webpack优化之HappyPack实战

更新时间:2023-08-07
[转载]webpack优化之HappyPack实战
文章标题:[转载]oracle 同时更新多表,在Oracle数据库中同时更新两张表的简单方法

更新时间:2023-09-10
[转载]oracle 同时更新多表,在Oracle数据库中同时更新两张表的简单方法
文章标题:[转载][Unity] 包括场景互动与射击要素的俯视角闯关游戏Demo

更新时间:2024-03-11
[转载][Unity] 包括场景互动与射击要素的俯视角闯关游戏Demo
文章标题:[转载]程序员也分三六九等?等级差异,一个看不起一个!

更新时间:2024-05-10
[转载]程序员也分三六九等?等级差异,一个看不起一个!
文章标题:[转载]海贼王 动漫 全集目录 分章节 精彩打斗剧集

更新时间:2024-01-12
[转载]海贼王 动漫 全集目录 分章节 精彩打斗剧集
名词解释
作为当前文章的名词解释,仅对当前文章有效。
协程(Coroutine)在Unity引擎中,协程是一种特殊的编程机制,它允许开发者定义一个可以暂停和恢复执行的函数。不同于常规函数一次性从头到尾执行完毕,协程通过返回IEnumerator类型并在函数内部使用yield return语句来控制其执行流程。在每一帧中,协程可以根据yield return后的表达式决定是继续执行、挂起等待下一帧还是结束运行。这种机制使得开发者能够更方便地处理随时间推移的任务,例如动画序列、网络请求或UI过渡效果。
IEnumerator接口IEnumerator是C#中的一个接口,用于实现迭代器模式,它是Unity协程的基础。在Unity中,启动一个协程时需要提供一个实现了IEnumerator接口的对象,这个对象通常是一个包含yield return语句的方法。IEnumerator接口提供了Current属性用于获取当前迭代元素,MoveNext方法推进迭代器至下一个元素,并通过返回值指示是否还有更多元素,以及Reset方法重置迭代器到初始状态。
YieldInstruction类在Unity中,YieldInstruction是一个抽象基类,它的子类如WaitForSeconds、WaitUntil、WaitWhile等,被广泛用于Unity协程中作为yield return的返回值,以控制协程的暂停与恢复时机。当协程遇到这些YieldInstruction类型的yield return语句时,将按照指定条件等待,比如等待一定秒数、等待某个条件满足或每帧等待等,然后在满足条件后恢复协程的执行。
延伸阅读
作为当前文章的延伸阅读,仅对当前文章有效。
在深入理解Unity协程的工作原理及其应用场景后,我们可以进一步探索协程在现代游戏开发中的最新实践和相关技术动态。近期,Unity官方持续优化协程功能,并在Unity 2021 LTS版本中引入了新的异步工作流API,如AsyncOperationHandle类,它提供了更强大的异步任务管理和资源加载能力,与协程机制相互补充,使得开发者能够更好地处理复杂的异步逻辑。
同时,在游戏性能优化方面,有开发者通过深入研究协程的执行机制,结合 Burst Compiler 和 Job System,实现更高效率的帧间任务调度。例如,通过自定义实现IEnumerator来配合协程进行数据预取和更新,以减少主线程负担,提升游戏流畅度。
此外,社区中有不少关于如何正确使用协程的最佳实践讨论,如避免滥用协程导致的内存泄漏问题,以及合理利用协程处理网络请求、动画序列、UI过渡等场景,这些实战经验对于Unity开发者来说具有很高的参考价值。
值得注意的是,随着C#语言的发展,.NET框架中对异步编程模型的支持也在不断加强,诸如async/await关键词的引入为Unity异步编程带来了更多可能。尽管Unity引擎目前并未原生支持async/await,但开发者可以通过一些第三方库或者巧妙转换,将async/await与协程相结合,构建出更为简洁高效的异步代码结构。
综上所述,Unity协程作为游戏开发中的重要工具,在实际项目中扮演着不可或缺的角色。紧跟技术前沿,掌握协程与其他异步编程技术的融合应用,是提高游戏开发效率和用户体验的关键所在。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
timeout duration command - 执行命令并在指定时间后终止它。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
可自定义logo的jQuery生成二维码插件 01-03 jquery每日签到日历插件 10-10 高度可定制的jQuery瀑布流网格布局插件 03-15 Consul中服务实例自动注销问题解析:健康检查、稳定性与Agent配置的影响及解决策略 01-22 怎么看mysql 的安装路径 12-31 jquery横向手风琴效果 12-23 蓝色数码电子产品销售HTML5网站模板 12-14 jQuery和CSS3汉堡包导航菜单打开动画特效 10-19 python模拟生存游戏 10-08 本次刷新还10个文章未展示,点击 更多查看。
jQuery.eraser-实现橡皮擦擦除功能的jquery插件 05-26 Netty中ChannelNotRegisteredException异常处理:理解原因与确保Channel注册状态的方法示例 05-16 响应式游戏开发类企业前端cms模板下载 05-02 精美的花甲美食网站HTML模板下载 03-09 soulmate粉色干净浪漫唯美婚礼单页响应式网站模板 03-07 Vue.js项目中proxyTable数据转发遭遇504错误:服务器响应时间与网络连接问题排查及解决方案 03-05 SpringCloud服务路由配置错误与失效:识别问题、排查步骤及组件解析这个涵盖了的核心内容,包括SpringCloud框架下的服务路由配置错误失效问题的识别,以及涉及到的服务注册中心、Gateway、Zuul等组件的功能解析和故障排查的具体步骤。同时,字数控制在了50个字以内,满足了要求。 03-01 css水平线长度设置 02-11 [转载]Proxy 、Relect、响应式 01-11 蓝色响应式软件营销代理公司网站静态模板 01-06 python正太分布校验 01-05
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"