最近遇到个问题,是在阅读别人代码的时候产生的。简单来说就是如何自定义一个类型可以让Unity协程识别并且阻塞。这个问题一开始不知道怎么去查,就直接查协程,结果查一大堆告诉我yield return null,yield return new waitForSecond()怎么怎么用。这个对新手确实很重要,需要总结出来,但是不是我想要的答案。
后来历经千辛万苦。。。。好吧也没有千辛万苦,查了一下午。终于找到了相关文章。我根据自己在项目中看到的用法,和自己的亲身实验,在众多文章中挑选了一篇我觉得符合我理解的:https://blog.csdn.net/lrfleroy/article/details/89219965。是一个系列文章,感兴趣的可以看看。
我就总结一下大概原理和用法。
1.原理
Unity的协程是借助迭代器的原理,在StartCoroutine(IEnumerator)传入一个迭代器就可以开启一个协程。而迭代器主要是依靠bool MoveNext()和object current,来确定当前执行的对象和是否可以继续执行下去,下面是IEnumerator接口代码:
namespace System.Collections
{
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
}
关于迭代器内部的实现原理,可以看这篇文章:https://www.jianshu.com/p/ddd023c1a886
其实Unity利用传入的协程函数中的 yield return 的这些返回值做了特殊处理来实现阻塞迭代器执行的目的。
首先我们调用StartCoroutine传入的函数本身就是一个迭代器,配合yield关键字这个语法糖(链接:https://www.cnblogs.com/blueberryzzz/p/8678700.html)会让我们每一次执行这个迭代器时获得到它return的一个个元素(其实内部控制了一个指针和list,当调用movenext的时候,指针往前移动,判断下面是否还有元素可访问,如果可以则moveNext为true,返回当前元素Current给外部,reset则就把指针位置重置)。
而协程函数这个迭代器中返回的又是一个IEnumerator的新的迭代器,Coroutine类就是对这个返回值做了特殊处理,当访问到这个返回值,判断其中的MoveNext是否为false(false就意味着这个迭代器没有元素了,不可继续执行了),然后来控制是否阻塞迭代器的执行。
举个例子,我们写一个new waitforSecond(),在new对象的时候记住当前时间,然后内部不停访问这个movenext()方法实时判断是否超过5秒,不就能够控制它在5秒后再执行了吗。
2.用法
用法非常简单,只需要继承IEnumerator接口,控制好movenext方法就好。一个等待固定时间的例子:
public class BaseCoroutineOperation : IEnumerator
{
float startTime;
float waitTime;
public BaseCoroutineOperation(float waitTime)
{
this.waitTime = waitTime;
startTime = Time.time;
}
public object Current => null;
public bool MoveNext()
{
return !isDone();
}
public void Reset()
{
}
public bool isDone()
{
return Time.time - startTime > waitTime;
}
}
public class CoroutineTestDemo : MonoBehaviour
{
void Start()
{
//Coroutine coroutine = StartCoroutine(CouroutineTest());
//StopCoroutine(coroutine);
Coroutine coroutine = StartCoroutine("CouroutineTest");
StopCoroutine("CouroutineTest");
Debug.LogError(444);
}
IEnumerator CouroutineTest()
{
Debug.Log(111);
yield return new BaseCoroutineOperation(2);
Debug.Log(222);
yield break;
Debug.Log(333);
}
}
以上例子我已经成功测试过。
另外
发现一个细节,看上面例子,当使用stopCoroutine(IEnumerator inumerator)的时候,并不能如期待的那样终止协程,如果上面程序执行,应该不会等待2秒就会终止掉才对,但是却还是2秒后把222打印了出来。但是当我使用stopCoroutine(string methodName),同时也使用startCoroutine(string methodName)的时候,却成功终止了。
后来找到篇资料:https://blog.csdn.net/as467884505/article/details/85069048
发现如果使用startCoroutine(IEnumerator inumerator)时想要终止协程,可以将startCoroutine返回的Coroutine对象传入stopCoroutine(Coroutine coroutine)进行终止。
也可以参考下面文章: