协程作为Unity中一个比较重要的特性,当前有很多种奇妙的特性。这次就简单介绍一下如何使用Coroutine写一个可以复用的计时器(Timer)。如果你对协程不是很熟悉,建议可以先阅读一下我之前写的一篇 协程(Coroutine)原理分析。
大部分情况下, 我们可以通过直接在Update(或FixedUpdate)中使用Time.deltaTime来获取上一帧经过的时间,这样通过一个变量累加该值就可以实现计时器的效果。大致代码如下:
using UnityEngine;
public class Timer : MonoBehaviour
{
public float waitTime = 1f;
private float elapseTime;
void Update()
{
elapseTime += Time.deltaTime;
if (elapseTime > waitTime)
{
print("Timer is done");
elapseTime = 0;
}
}
}
这种写法基本不会有什么问题,不过我觉得还是有两个缺点的。第一个是每次你使用的使用的时候都需要在Update中加入这段话,我只在Update()中做这一项操作的话还好,如果之后功能越添越多就会显得有点乱。第二个来说就是没有那么准确,基本每次timer的值都会大于waitTimer,帧数高的话这种误差倒还说得过去,帧数低就会有比较可观的累积误差。
接下来就介绍一下如何用协程实现计时器的功能。这里就需要用到Unity内置的YieldInstruction之一————WaitForSeconds,即悬停Coroutine指定的秒数然后继续执行。此外由于我们可以通过使用StartCoroutine和StopCoroutine手动地启动和停止协程,Timer的功能也会更加完善。
下面是可供参考的Timer的协程实现代码:
using UnityEngine;
//使用协程必须添加该命名空间,
//因为IEnumerator就位于该命名空间下
using System.Collections;
public class Timer : MonoBehaviour
{
public float waitTime = 1f;
private float elapseTime;
private void Start()
{
StartCoroutine(CustomeTimer());
}
IEnumerator CustomeTimer()
{
yield return new WaitForSeconds(waitTime);
print("Timer is done.");
//使用StartCoroutine()实现CustomerTimer()的迭代执行
StartCoroutine(CustomeTimer());
}
}
如你所见,使用协程可以在任何我们愿意的地方启动Timer, 并且可以随时通过调用StopCoroutine来暂停它们。然而这样做还是有一个弊端,那就是无法复用。当我们希望在另一个项目中使用的时候还是得把这些代码再写一遍,作为一个懒癌晚期的程序猿,这种情况简直不可原谅。所以我们需要再在现有代码的基础上把它改成一个可复用的库文件。
如果希望达到这个目的,有一个地方是必须被舍弃的,即StartCoroutine/StopCoroutine的使用。如果你仔细研究Unity Script API,那么你会发现 StartCoroutine/StopCoroutine属于MonoBehaviour的public functions。 简单地解释就是只有在MonoBehaviour对象里使用这两个函数才会有用。你可能会问我们使用MonoBehaviour啊,为什么可以用。那是因为所有C#脚本的默认模板都是继承自MonoBehaviour的,所以使用的时候StartCoroutine()其实等价于this.StartCoroutine()。那么这对我们编写可复用的脚本有哪些影响呢?最大的问题就是如果我们在库文件中使用这两个文件,那么就必须把库文件挂到某个游戏物体上才会生效,这显然很蠢。理想的做法还是我们在库文件里写好协程相关的代码(因为协程是C#的特性, 所以即使不继承自MonoBehaviour也可以使用), 然后在需要使用Timer的脚本中使用StartCoroutine/StopCoroutine引用。
下面给出可供参考的代码:
namespace liusuwanxia.Timer
{
using System;
using System.Collections;
using UnityEngine;
///
/// Ready to use timers for coroutines
///
///
/// Ready to use timers for coroutines
///
public class Timer
{
///
/// 简单的正计时器, 可以指定每次停顿时执行的动作
///
/// 停顿的间隔
/// 停顿时调用的函数
///
public static IEnumerator Start(float duration, Action callback)
{
return Start(duration, false, callback);
}
///
/// 简单的正计时器, 可以指定每次停顿时执行的动作
///
/// 每次停顿的间隔
/// 是否重复执行
/// 停顿时调用的函数
///
public static IEnumerator Start(float duration, bool repeat, Action callback)
{
do
{
yield return new WaitForSeconds(duration);
if (callback != null)
callback();
} while (repeat);
}
public static IEnumerator StartRealtime(float time, System.Action callback)
{
float start = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup < start + time)
{
yield return null;
}
if (callback != null) callback();
}
public static IEnumerator NextFrame(Action callback)
{
yield return new WaitForEndOfFrame();
if (callback != null)
callback();
}
}
}
下面是使用的示例:
using liusuwanxia.Timer;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class test : MonoBehaviour {
public Text textTimer;
private IEnumerator coroutine;
private int count = 60;
// Use this for initialization
void Start () {
coroutine = Timer.Start(1f, true, () => {
if (count <= 0) StopCoroutine(coroutine);
textTimer.text = string.Format("Timer: {0}", count--);
});
}
public void OnBtnStartClick()
{
StartCoroutine(coroutine);
}
public void OnBtnStopClick()
{
StopCoroutine(coroutine);
}
}
使用协程制作的计时器具有自由开启停止,精准高效等优点,不过相对于一般方法确实更难理解。个人建议在基本掌握Coroutine的用法之后,再试着理解和使用上述工具类。