协程应用——制作简单的计时器

来源:互联网 发布:石油大学华东 网络登录 编辑:程序博客网 时间:2024/06/14 16:15

前言

协程作为Unity中一个比较重要的特性,当前有很多种奇妙的特性。这次就简单介绍一下如何使用Coroutine写一个可以复用的计时器(Timer)。如果你对协程不是很熟悉,建议可以先阅读一下我之前写的一篇 协程(Coroutine)原理分析。

Timer的常规实现

大部分情况下, 我们可以通过直接在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,帧数高的话这种误差倒还说得过去,帧数低就会有比较可观的累积误差。

Timer的协程实现方法

接下来就介绍一下如何用协程实现计时器的功能。这里就需要用到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的协程复用

如你所见,使用协程可以在任何我们愿意的地方启动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;    /// <summary>    /// Ready to use timers for coroutines    /// </summary>    /// <summary>    /// Ready to use timers for coroutines    /// </summary>    public class Timer    {        /// <summary>        /// 简单的正计时器, 可以指定每次停顿时执行的动作        /// </summary>        /// <param name="duration">停顿的间隔</param>        /// <param name="callback">停顿时调用的函数</param>        /// <returns></returns>        public static IEnumerator Start(float duration, Action callback)        {            return Start(duration, false, callback);        }        /// <summary>        /// 简单的正计时器, 可以指定每次停顿时执行的动作        /// </summary>        /// <param name="duration">每次停顿的间隔</param>        /// <param name="repeat">是否重复执行</param>        /// <param name="callback">停顿时调用的函数</param>        /// <returns></returns>        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的用法之后,再试着理解和使用上述工具类。

原创粉丝点击