16.MonoBehavior中的重要内容-协同程序原理
16.1 知识点
协程的本质
- 协程可以分成两部分
协程函数本体
协程调度器
协程本体就是一个能够中间暂停返回的函数
协程调度器是Unity内部实现的,会在对应的时机帮助我们继续执行协程函数
Unity只实现了协程调度部分
协程的本体本质上就是一个 C#的迭代器方法
通俗理解
- 协程函数本体就是我们自己写的协程函数 这个要我们自己写逻辑
- 我们写的协程函数实际上是个迭代器方法
- 我们把我们写的协程函数丢到 MonoBehaviour中开启协程的方法 StartCoroutine 中
- 可以理解为 就是把一个迭代器方法的返回值 返回给Unity内部的一个协程管理器里面存储起来
- 通过协程管理器对应着我们写协程函数的逻辑和啥时候yield return 能在对应时机帮我们继续调用协程函数
协程本体是迭代器方法的体现
测试类
public class TestClass
{
public int testNum;
public TestClass(int testNum)
{
this.testNum = testNum;
}
}
声明迭代器函数
//测试的迭代器(协程)函数
IEnumerator Test()
{
print("第一次执行");
yield return 1;
print("第二次执行");
yield return 999;
print("第三次执行");
yield return "666";
print("第四次执行");
yield return new TestClass(250);
}
协程函数本体
//如果我们不通过 开启协程方法执行协程 直接调用协程的话 没有作用
Test();//没有作用
//Unity的协程调度器是不会帮助我们管理协程函数的 我们可以用一个迭代器变量把我们的协程函数装起来
IEnumerator ieTest = Test();
////但是我们可以自己执行手动迭代器函数内容
//ieTest.MoveNext();//第一次执行
////会执行函数中内容遇到 yield return为止的逻辑
//print(ieTest.Current);//1
////得到 yield return 返回的内容 这个Current是Object类型
//ieTest.MoveNext();//第二次执行
//print(ieTest.Current);//999
//ieTest.MoveNext();//第三次执行
//print(ieTest.Current);//666
//ieTest.MoveNext();//第四次执行
//TestClass tc = ieTest.Current as TestClass;//可以把Current对象as成对应的类
//print(tc.testNum);//250
////MonoBehaviour中开启协程的方法StartCoroutine
////其实就是得到我们的迭代器对象 再根据Unity自己的规则一步一步的执行协程函数
////像上面这样一步一步写其实不太好 写把上面的注释了 用while逐个执行
//MoveNext 代表执行下一个yield return之前的逻辑 并且有返回值 返回值代表着字符迭代器是否到了结尾(这个迭代器函数 是否执行完毕)
while (ieTest.MoveNext())
{
print(ieTest.Current);
}
协程调度器
继承MonoBehavior后 开启协程
- 相当于是把一个协程函数(迭代器)放入Unity的协程调度器中帮助我们管理进行执行
- 具体的yield return 后面的规则 也是Unity定义的一些规则
协程本体是迭代器方法的体现总结
- 你可以简化理解迭代器函数
- C#看到迭代器函数和yield return语法糖
- 就会把原本是一个的函数变成”几部分”
- 我们可以通过迭代器 从上到下遍历这 “几部分” 进行执行
- 就达到了将一个函数中的逻辑分时执行的目的
而协程调度器就是 利用迭代器函数返回的内容来进行之后的处理
- 比如Unity中的协程调度器
- 根据yield return返回的内容 决定了下一次在何时继续执行迭代器函数中的”下一部分”
理论上来说 我们可以利用迭代器函数的特点 自己实现协程调度器来取代Unity自带的调度器
总结
- 协程的本质 就是利用
- C#的迭代器函数”分步执行”的特点
- 加上 Unity自己定义的一些协程调度逻辑
- 实现的一套分时执行函数的规则
16.2 知识点代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//测试类
public class TestClass
{
public int testNum;
public TestClass(int testNum)
{
this.testNum = testNum;
}
}
public class Lesson16_MonoBehavior中的重要内容_协同程序原理 : MonoBehaviour
{
//测试的迭代器(协程)函数
IEnumerator Test()
{
print("第一次执行");
yield return 1;
print("第二次执行");
yield return 999;
print("第三次执行");
yield return "666";
print("第四次执行");
yield return new TestClass(250);
}
void Start()
{
#region 知识点一 协程的本质
//协程可以分成两部分
//1.协程函数本体
//2.协程调度器
//协程本体就是一个能够中间暂停返回的函数
//协程调度器是Unity内部实现的,会在对应的时机帮助我们继续执行协程函数
//Unity只实现了协程调度部分
//协程的本体本质上就是一个 C#的迭代器方法
//通俗理解
//协程函数本体就是我们自己写的协程函数 这个要我们自己写逻辑
//我们写的协程函数实际上是个迭代器方法
//我们把我们写的协程函数丢到 MonoBehaviour中开启协程的方法 StartCoroutine 中
//可以理解为 就是把一个迭代器方法的返回值 返回给Unity内部的一个协程管理器里面存储起来
//通过协程管理器对应着我们写协程函数的逻辑和啥时候yield return 能在对应时机帮我们继续调用协程函数
#endregion
#region 知识点二 协程本体是迭代器方法的体现
//1.协程函数本体
//如果我们不通过 开启协程方法执行协程 直接调用协程的话 没有作用
Test();//没有作用
//Unity的协程调度器是不会帮助我们管理协程函数的 我们可以用一个迭代器变量把我们的协程函数装起来
IEnumerator ieTest = Test();
////但是我们可以自己执行手动迭代器函数内容
//ieTest.MoveNext();//第一次执行
////会执行函数中内容遇到 yield return为止的逻辑
//print(ieTest.Current);//1
////得到 yield return 返回的内容 这个Current是Object类型
//ieTest.MoveNext();//第二次执行
//print(ieTest.Current);//999
//ieTest.MoveNext();//第三次执行
//print(ieTest.Current);//666
//ieTest.MoveNext();//第四次执行
//TestClass tc = ieTest.Current as TestClass;//可以把Current对象as成对应的类
//print(tc.testNum);//250
////MonoBehaviour中开启协程的方法StartCoroutine
////其实就是得到我们的迭代器对象 再根据Unity自己的规则一步一步的执行协程函数
////像上面这样一步一步写其实不太好 写把上面的注释了 用while逐个执行
//MoveNext 代表执行下一个yield return之前的逻辑 并且有返回值 返回值代表着字符迭代器是否到了结尾(这个迭代器函数 是否执行完毕)
while (ieTest.MoveNext())
{
print(ieTest.Current);
}
//2.协程调度器
//继承MonoBehavior后 开启协程
//相当于是把一个协程函数(迭代器)放入Unity的协程调度器中帮助我们管理进行执行
//具体的yield return 后面的规则 也是Unity定义的一些规则
//协程本体是迭代器方法的体现总结
//你可以简化理解迭代器函数
//C#看到迭代器函数和yield return 语法糖
//就会把原本是一个的 函数 变成"几部分"
//我们可以通过迭代器 从上到下遍历这 "几部分"进行执行
//就达到了将一个函数中的逻辑分时执行的目的
//而协程调度器就是 利用迭代器函数返回的内容来进行之后的处理
//比如Unity中的协程调度器
//根据yield return 返回的内容 决定了下一次在何时继续执行迭代器函数中的"下一部分"
//理论上来说 我们可以利用迭代器函数的特点 自己实现协程调度器来取代Unity自带的调度器
#endregion
#region 总结
//协程的本质 就是利用
//C#的迭代器函数"分步执行"的特点
//加上 Unity自己定义的一些协程调度逻辑
//实现的一套分时执行函数的规则
#endregion
}
}
16.3 练习题
请不使用Unity自带的协程协调器开启协程,通过迭代器函数实现每隔一秒执行函数中的一部分逻辑
声明一个协程函数
IEnumerator MyTestCoroutine()
{
print("1");
// 如果是用Unity自带的协程协调器开启协程 那么数字代表等待1帧继续执行之后的内容
// 如果是自己的协程协调器可以自己自定义规则
yield return 1;
print("2");
yield return 2;
print("3");
yield return 3;
print("4");
}
声明用于记录当前协程函数和返回时间的类
//用于记录当前协程函数和返回时间的类
public class NowIEnumeratorAndYieldReturnTime
{
//记录 下次还要执行的 迭代器接口
public IEnumerator ie;
//记录 下次执行的时间点
public float time;
}
声明自己写的协程管理器,搞成单例,创建开启协程方法,声明用于记录当前协程函数和返回时间的类,在声明这个类的容器,最后实现Update里判断时间的逻辑。
//我的协程管理器
public class MyCoroutineMgr : MonoBehaviour
{
private static MyCoroutineMgr instance;
public static MyCoroutineMgr Instance => instance;
//申明存储 迭代器函数对象的 容器 用于 一会继续执行
private List<NowIEnumeratorAndYieldReturnTime> list = new List<NowIEnumeratorAndYieldReturnTime>();
void Awake()
{
instance = this;
}
//我的开启协程的方法
public void MyStartCoroutine(IEnumerator ie)
{
//来进行 分步走 分时间执行的逻辑
//传入一个 迭代器函数返回的接口 那么应该一来就执行它
//一来就先执行第一步 执行完了 如果返回的true 证明 后面还有步骤
if(ie.MoveNext())
{
//判断 如果yield return返回的是 数字 是一个int类型 那就证明 是需要等待n秒继续执行
if(ie.Current is int)
{
//进来这里说明yield return返回已经是int类型了 按我们自己的规则要过一会执行协程
//按思路 应该把 这个迭代器函数 和它下一次执行的时间点 记录下来
//然后不停检测 时间 是否到达了 下一次执行的 时间点 然后就继续执行它
//这样我们不妨声明一个类
//这个类的数据结构是有一个IEnumerator变量用于存储协程函数和float变量用于存储协程要执行的时间
//创建协程存储类
NowIEnumeratorAndYieldReturnTime nowIEAndTime = new NowIEnumeratorAndYieldReturnTime();
//记录迭代器接口
nowIEAndTime.ie = ie;
//记录迭代器要执行的时间
nowIEAndTime.time = Time.time + (int)ie.Current;
//把记录的信息 记录到数据容器当中 因为可能有多个协程函数 开启 所以 用一个 list来存储
list.Add(nowIEAndTime);
}
}
}
void Update()
{
//为了避免在循环的时候 从列表里面移除内容 我们可以倒着遍历
for (int i = list.Count - 1; i >= 0; i--)
{
//判断 当前该迭代器函数 是否到了下一次要执行的时间
//如果到了 就需要执行下一步了
if( list[i].time <= Time.time )
{
//判断还能不能往后继续执行
//注意:只要调用了MoveNext其实就执行了yield return之前的逻辑 只不过假如没有yield return就返回false
if (list[i].ie.MoveNext())
{
//如果是true 那还需要对该迭代器函数 进行处理
//如果是 int类型 证明是按秒等待
if(list[i].ie.Current is int)
{
list[i].time = Time.time + (int)list[i].ie.Current;
}
//如果不是int类型 是别的类型 可以自己加if else 判断是啥类型 可能要声明新的容器存进去 这里就不写了
else
{
//该list 只是存储 处理时间相关 等待逻辑的 迭代器函数的
//如果是别的类型 就不应该 存在这个list中 应该根据类型把它放入别的容器中
list.RemoveAt(i);
}
}
else
{
//后面已经没有可以等待和执行的了 证明已经执行完毕了逻辑
list.RemoveAt(i);
}
}
}
}
}
16.4 练习题代码
Lesson16_练习题
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson16_练习题 : MonoBehaviour
{
#region 练习题一
//请不使用Unity自带的协程协调器开启协程,通过迭代器函数实现每隔一秒执行函数中的一部分逻辑
void Start()
{
//Unity自带的协程协调器 开启协程函数(迭代器函数)
//StartCoroutine(MyTestCoroutine());
//我们自己写协程协调器 开启协程函数(迭代器函数)
MyCoroutineMgr.Instance.MyStartCoroutine(MyTestCoroutine());
}
IEnumerator MyTestCoroutine()
{
print("1");
//如果是用Unity自带的协程协调器开启协程 那么数字代表等待1帧继续执行之后的内容
//如果是自己的协程协调器可以自己自定义规则
yield return 1;
print("2");
yield return 2;
print("3");
yield return 3;
print("4");
}
#endregion
}
MyCoroutineMgr
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//用于记录当前协程函数和返回时间的类
public class NowIEnumeratorAndYieldReturnTime
{
//记录 下次还要执行的 迭代器接口
public IEnumerator ie;
//记录 下次执行的时间点
public float time;
}
//我的协程管理器
public class MyCoroutineMgr : MonoBehaviour
{
private static MyCoroutineMgr instance;
public static MyCoroutineMgr Instance => instance;
//申明存储 迭代器函数对象的 容器 用于 一会继续执行
private List<NowIEnumeratorAndYieldReturnTime> list = new List<NowIEnumeratorAndYieldReturnTime>();
void Awake()
{
instance = this;
}
//我的开启协程的方法
public void MyStartCoroutine(IEnumerator ie)
{
//来进行 分步走 分时间执行的逻辑
//传入一个 迭代器函数返回的接口 那么应该一来就执行它
//一来就先执行第一步 执行完了 如果返回的true 证明 后面还有步骤
if(ie.MoveNext())
{
//判断 如果yield return返回的是 数字 是一个int类型 那就证明 是需要等待n秒继续执行
if(ie.Current is int)
{
//进来这里说明yield return返回已经是int类型了 按我们自己的规则要过一会执行协程
//按思路 应该把 这个迭代器函数 和它下一次执行的时间点 记录下来
//然后不停检测 时间 是否到达了 下一次执行的 时间点 然后就继续执行它
//这样我们不妨声明一个类
//这个类的数据结构是有一个IEnumerator变量用于存储协程函数和float变量用于存储协程要执行的时间
//创建协程存储类
NowIEnumeratorAndYieldReturnTime nowIEAndTime = new NowIEnumeratorAndYieldReturnTime();
//记录迭代器接口
nowIEAndTime.ie = ie;
//记录迭代器要执行的时间
nowIEAndTime.time = Time.time + (int)ie.Current;
//把记录的信息 记录到数据容器当中 因为可能有多个协程函数 开启 所以 用一个 list来存储
list.Add(nowIEAndTime);
}
}
}
void Update()
{
//为了避免在循环的时候 从列表里面移除内容 我们可以倒着遍历
for (int i = list.Count - 1; i >= 0; i--)
{
//判断 当前该迭代器函数 是否到了下一次要执行的时间
//如果到了 就需要执行下一步了
if( list[i].time <= Time.time )
{
//判断还能不能往后继续执行
//注意:只要调用了MoveNext其实就执行了yield return之前的逻辑 只不过假如没有yield return就返回false
if (list[i].ie.MoveNext())
{
//如果是true 那还需要对该迭代器函数 进行处理
//如果是 int类型 证明是按秒等待
if(list[i].ie.Current is int)
{
list[i].time = Time.time + (int)list[i].ie.Current;
}
//如果不是int类型 是别的类型 可以自己加if else 判断是啥类型 可能要声明新的容器存进去 这里就不写了
else
{
//该list 只是存储 处理时间相关 等待逻辑的 迭代器函数的
//如果是别的类型 就不应该 存在这个list中 应该根据类型把它放入别的容器中
list.RemoveAt(i);
}
}
else
{
//后面已经没有可以等待和执行的了 证明已经执行完毕了逻辑
list.RemoveAt(i);
}
}
}
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com