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循环内的东西。