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

前言

协程作为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;

    /// 
    /// 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的用法之后,再试着理解和使用上述工具类。

你可能感兴趣的:(unity开发)