Unity协程简述(简单用法,简易分析)

协程的简单用法

简述

  • 协程实际上就是一个迭代器(IEnumerator),内部可以定义一些Yield Return挂起函数,判断协程函数内部执行的逻辑(通过Yield Return 延时,跳帧,等待下载或加载等操作将函数进行分步操作)
  • 协程是基于生命周期的(下面会将执行顺序),是同步的,不是异步的(同一时间只会执行一个协程)
  • 协程函数会被编译成一个状态机,每个状态都会关联下一个状态直至下一个为空

函数

  • 按常用排序
  • WaitForSeconds
  • WaitForEndOfFrame
  • WaitForFixedUpdate
  • WaitForSecondsRealtime
  • WaitUnil
  • WaitWhile
//WaitUntil:当返回值为True时则继续执行后面的内容
StartCoroutine(WaitUntilFunc());
//WaitForSeconds:延迟几秒后执行,时间单位为Float
StartCoroutine(WaitForSecondsFunc());
//WaitForSeconds:延迟几秒后执行(不受缩放影响),时间单位为Float
StartCoroutine(WaitForSecondsRealtimeFunc());
//WaitWhile:当返回值为False时则继续执行后面的内容
StartCoroutine(WaitWhileFunc());
//WaitForEndOfFrame:当前帧结束前调用
StartCoroutine(WaitForEndOfFrameFunc());
//当开启的协程结束时调用
StartCoroutine(WaitForStartCoroutine());

IEnumerator WaitUntilFunc()
{
    Debug.Log("WaitUntilFunc Start~");
    //10帧后执行
    yield return new WaitUntil(() => _frame> 10);
    Debug.Log("WaitUntilFunc End~");
}
IEnumerator WaitForSecondsFunc()
{
    Debug.Log("WaitForSecondsFunc Start~");
    //延迟两秒后执行(时间受缩放影响)
    yield return new WaitForSeconds(2f);
    Debug.Log("WaitForSecondsFunc End~");
}

IEnumerator WaitForSecondsRealtimeFunc()
{
    Debug.Log("WaitForSecondsRealtimeFuncStart~");
    //延迟两秒后执行(时间不受缩放影响)
    yield return new WaitForSecondsRealtime(2f);
    Debug.Log("WaitForSecondsRealtimeFuncEnd~");
}

IEnumerator WaitWhileFunc()
{
    Debug.Log("WaitWhileFunc Start~");
    //10帧后执行
    yield return new WaitWhile(()=> !(_frame>10));
    Debug.Log("WaitWhileFunc End~");
}

IEnumerator WaitForEndOfFrameFunc()
{
    Debug.Log("WaitForEndOfFrameFunc Start~");
    //当前帧结束前后调用
    yield return new WaitForEndOfFrame();
    Debug.Log("WaitForEndOfFrameFunc End~");
}

IEnumerator WaitForAsyncOperation()
{
    Debug.Log("WaitForAsyncOperation Start~");
    //当场景加载完毕后调用
    AsyncOperation async = null; 
    //异步加载完场景时完后调用
    async = SceneManager.LoadSceneAsync("SampleScene");
    //异步加载完AB时调用
    async = AssetBundle.LoadFromFileAsync("Path");
    //异步加载完资源时调用
    async = Resources.LoadAsync("Path");
    //异步下载完后执行
    var request = UnityWebRequest.Get(new Uri("www.baidu.com"));
    async = request.SendWebRequest();
    yield return async;
    Debug.Log("WaitForAsyncOperation End~");
}

IEnumerator WaitForStartCoroutine()
{
    Debug.Log("WaitForStartCoroutine Start~");
    //当开启的协程结束时调用(不是被挂起时噢,而是完成了协程)
    yield return StartCoroutine(WaitForSecondsFunc());
    Debug.Log("WaitForStartCoroutine End~");
}

协程的执行顺序

官网更加详细点这里哟
执行顺序

  • FixedUpdate
  • yield WaitForFixedUpdate
  • Update
  • yield null
  • yield WaitForSeconds
  • yield WWW
  • yield StartCoroutine
  • LateUpdate
  • yield WaitForEndOfFrame

协程替我们做了什么

Yleid Return

简述
yield return 用来判断协程内部是否挂起或继续执行的语句

支持类型

  • IEnumerator: 通过MoveNext函数来判断是否继续挂起函数
  • CustomYieldInstruction:内部通过判断keepWaiting的值来判断是否继续执行协程,true为挂起,false为继续执行
  • YieldInstruction:Coroutine就是继承该对象,当yield return Coroutine时就是等待开启的协程执行结束再往后执行
  • AsyncOperation:等待加载完毕再往后执行
  • Coroutine:开启协程的返回值,仅仅引来引用协程,不具有任何暴露的属性和函数(当yield return coroutine时,当Coroutine为Null时协程继续往后执行)

yield return做了什么操作
可以看到yield return 修改了 current 与 state的值。而current 表示当前迭代对象的值,而state在执行代码前会被修改一次(表示正在执行),而代码执行完毕后会被修改一次(表示下一个迭代器的标识)

执行前修改了 state值(表示正在执行)
IL_0025: stfld        int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'

// [95 5 - 95 6]
IL_002a: nop

// [96 9 - 96 51]
IL_002b: ldstr        "WaitForEndOfFrameFunc Start~"
IL_0030: call         void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
IL_0035: nop

// [98 9 - 98 46]
IL_0036: ldarg.0      // this
IL_0037: newobj       instance void [UnityEngine.CoreModule]UnityEngine.WaitForEndOfFrame::.ctor()
修改了当前current对象
IL_003c: stfld        object TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>2__current'
IL_0041: ldarg.0      // this
IL_0042: ldc.i4.1
执行完 debug.log 后再次修改state值表示结束以及能继续迭代下一个对象
IL_0043: stfld        int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'

如何进行跳帧,延迟,等待的操作

从IL的角度分析

当协程内部只定义了一个yield return挂起函数时,不会直接编译成switch,而是成编译成类似if else语句,而当出现两个yield return或以上时就会编译成一个switch语句将代码分割。

IL语言

--C#--
IEnumerator WaitForEndOfFrameFunc()
{
     Debug.Log("WaitForEndOfFrameFunc Start~");
     //当前帧结束前后调用
     yield return new WaitForEndOfFrame();
     Debug.Log("WaitForEndOfFrameFunc End~");
     yield return new WaitForEndOfFrame();
     Debug.Log("WaitForEndOfFrameFunc End1~");
 }

--IL部分--
.method private final hidebysig virtual newslot instance bool
  MoveNext() cil managed
{
  .override method instance bool [netstandard]System.Collections.IEnumerator::MoveNext()
  .maxstack 2
  .locals init (
    [0] int32 V_0
  )

  IL_0000: ldarg.0      // this
  IL_0001: ldfld        int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
  IL_0006: stloc.0      // V_0
  IL_0007: ldloc.0      // V_0
  IL_0008: switch       (IL_001b, IL_001d, IL_001f)
  IL_0019: br.s         IL_0021
  IL_001b: br.s         IL_0023
  IL_001d: br.s         IL_004a
  IL_001f: br.s         IL_0070
  IL_0021: ldc.i4.0
  IL_0022: ret
  IL_0023: ldarg.0      // this
  IL_0024: ldc.i4.m1
  IL_0025: stfld        int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'

  // [95 5 - 95 6]
  IL_002a: nop

  // [96 9 - 96 51]
  IL_002b: ldstr        "WaitForEndOfFrameFunc Start~"
  IL_0030: call         void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
  IL_0035: nop

  // [98 9 - 98 46]
  IL_0036: ldarg.0      // this
  IL_0037: newobj       instance void [UnityEngine.CoreModule]UnityEngine.WaitForEndOfFrame::.ctor()
  IL_003c: stfld        object TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>2__current'
  IL_0041: ldarg.0      // this
  IL_0042: ldc.i4.1
  IL_0043: stfld        int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
  IL_0048: ldc.i4.1
  IL_0049: ret

  IL_004a: ldarg.0      // this
  IL_004b: ldc.i4.m1
  IL_004c: stfld        int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'

  // [99 9 - 99 49]
  IL_0051: ldstr        "WaitForEndOfFrameFunc End~"
  IL_0056: call         void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
  IL_005b: nop

  // [100 9 - 100 46]
  IL_005c: ldarg.0      // this
  IL_005d: newobj       instance void [UnityEngine.CoreModule]UnityEngine.WaitForEndOfFrame::.ctor()
  IL_0062: stfld        object TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>2__current'
  IL_0067: ldarg.0      // this
  IL_0068: ldc.i4.2
  IL_0069: stfld        int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'
  IL_006e: ldc.i4.1
  IL_006f: ret

  IL_0070: ldarg.0      // this
  IL_0071: ldc.i4.m1
  IL_0072: stfld        int32 TestCor/'<WaitForEndOfFrameFunc>d__12'::'<>1__state'

  // [101 9 - 101 50]
  IL_0077: ldstr        "WaitForEndOfFrameFunc End1~"
  IL_007c: call         void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
  IL_0081: nop

  // [102 5 - 102 6]
  IL_0082: ldc.i4.0
  IL_0083: ret

} // end of method '<WaitForEndOfFrameFunc>d__12'::MoveNext

总结

这篇文章很好的分析了IL和C#部分
这篇文章很好的解释了跳帧,延迟,等待的操作
看IL后发现,实际上协程函数最终会被MoveNext函数调用。在switch开始之前先去取 “<>1__state” 的值,然后再去进行switch进行判断,当switch找到对应的case时并执行完了代码块就会修改 “<>1__state” 的值以及 “<>2__current”,然后结束语句,直到Unity生命周期再次调用该协程,并继续进行switch直至 “<>1__state” 的值代表为null。

根据上述文章的分析,跳帧与延迟等操作应该是在MoveNext函数内部做了特殊操作,当Current对象为延迟或跳帧对象时,会进行额外的处理

自定义一个迭代器

就不一一展示用法了,代码太长,需要自己去尝试

    private void Start()
    {
        StartCoroutine(TestCor());
    }
    IEnumerator TestCor()
    {
        Debug.Log("TestCor Start~");
        yield return new CustomIEnumerator();
        yield return new Custom2IEnumerator();
        yield return new Custom3IEnumerator();
        yield return new Custom4IEnumerator();
        Debug.Log("TestCor End~");
    }
}
//继承了这四个对象则可以被yield reutrn 这样可以自定义一些挂起函数
public class CustomIEnumerator : IEnumerator
{
    private int _index = -1;
    private string[] strName = {"Alan", "Kino", "Bruce"};
    
    /// <summary>
    /// 每帧被调用
    /// </summary>
    /// <returns></returns>
    public bool MoveNext()
    {
        _index++;
        Debug.Log("MoveNext:" + (_index > strName.Length).ToString());
        return false;
        return _index < strName.Length;
    }
    public void Reset()
    {
        Debug.Log("Reset");
        _index = 0;
    }
    public object Current {
        get
        {
            Debug.Log("获取当前对象:" + strName[_index]);
            return strName[_index];
        }
    }
}
public class Custom2IEnumerator : CustomYieldInstruction
{
    public override bool keepWaiting { get; }
}
public class Custom3IEnumerator : YieldInstruction
{
    
}
public class Custom4IEnumerator : AsyncOperation
{
    
}

优化

  • 有些不需要每帧进行执行的任务可以使用协程延迟
    官网案例
IEnumerator DoCheck() 
{
	//循环调用
    for(;;) 
    {
        ProximityCheck();
        //延迟0.1s后执行
        yield return new WaitForSeconds(.1f);
    }
}
  • 缓存挂机函数
private WaitForSeconds delay = new WaitForSeconds(.1f);
IEnumerator TestCor() 
{
    //循环调用
    for(;;) 
    {
        //延迟0.1s后执行
        yield return delay;
    }
}

最后

简而言之,越深入学越知道自己的浅薄,希望各位同学能从本章中学到什么。也希望各位同学给予一些更好的观点~

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值