推荐阅读
一、前言
今天,来分享一下如何实现定时器。
实现定时器需要搞明白二个问题:
一、定时器如何实现
二、为什么制作定时器系统
首选,我们来了解一下如何在Unity里面实现定时器。
二、Unity 定时器的三种实现方法
在Unity开发程序的时候,会学习到很多实现定时器效果的方法,比如说:
2-1、使用Time.deltaTime累加方式
在Update里面,使用Time.deltaTime实现:
using UnityEngine;
public class Test : MonoBehaviour
{
public float timer = 0f;
void Update()
{
timer += Time.deltaTime;
if (timer >= 2)
{
doSomething();
timer = 0f; // 定时2秒
}
}
void doSomething()
{
Debug.Log("每2秒执行一次");
}
}
或者通过Time.time来实现:
using UnityEngine;
public class Test : MonoBehaviour
{
public float timer = 0f;
void Start()
{
timer = Time.time;
}
void Update()
{
timer += Time.deltaTime;
if (Time.time - timer >= 2)// 定时2秒
{
doSomething();
timer = Time.time;
}
}
void doSomething()
{
Debug.Log("每2秒执行一次");
}
}
2-2、使用延迟调用函数
using UnityEngine;
public class Test : MonoBehaviour
{
void Start()
{
//0秒后,每2秒执行一次doSomething
InvokeRepeating("doSomething", 0, 2);
}
void doSomething()
{
Debug.Log("每2秒执行一次");
}
}
2-3、使用协程
using System;
using System.Collections;
using UnityEngine;
public class Test : MonoBehaviour
{
void Start()
{
//每2秒执行一次doSomething
StartCoroutine(UpdateTimer(2f, doSomething));
}
void doSomething()
{
Debug.Log("每2秒执行一次");
}
IEnumerator UpdateTimer(float timer,Action callBack)
{
var wait = new WaitForSeconds(timer);
while (true)
{
yield return wait;
callBack();
}
}
}
提示:场景内协程多了将会出现明显的卡顿。
可以看出来,这三种实现定时器的方法都很简单,在项目体量不是太大,并且代码量也还算简洁的时候,使用这三种实现方法是没有问题的。
但是,当代码量多的时候,再使用这些方法就会显得冗余杂乱。
这时候就需要一个定时器系统来管理这些定时的任务,这就是为何要制作定时器系统的原因。
三、实现定时器系统
实现原理:
每个定时器对象是一个Timer, 添加的时候交给定时器管理系统, 定时器系统负责管理所有的定时器对象,每次游戏Update的时候,遍历里面的每个定时器对象,
把它们过去的时间增加Time.deltaTime, 当过去的时间达到定时器触发时间的时候,触发定时器调用, 如果达到Timer触发次数,就把这个Timer移除。
否则就重置时间,继续直到下一个时间的触发。
代码:
一个TimerMgr,Timer管理类,用来管理定时器系统:
using System.Collections.Generic;
using UnityEngine;
class TimerNode
{
public TimerMgr.TimerHandler callback;
public float repeatRate; // 定时器触发的时间间隔;
public float time; // 第一次触发要隔多少时间;
public int repeat; // 你要触发的次数;
public float passedTime; // 这个Timer过去的时间;
public bool isRemoved; // 是否已经删除了
public int timerId; // 标识这个timer的唯一Id号;
}
public class TimerMgr
{
public delegate void TimerHandler();
private Dictionary<int, TimerNode> timers = null;//存放Timer对象
private List<TimerNode> removeTimers = null;//新增Timer缓存队列
private List<TimerNode> newAddTimers = null;//删除Timer缓存队列
private int autoIncId = 1;//每个Timer的唯一标示
//初始化Timer管理器
public void Init()
{
timers = new Dictionary<int, TimerNode>();
autoIncId = 1;
removeTimers = new List<TimerNode>();
newAddTimers = new List<TimerNode>();
}
/// <summary>
/// 以秒为单位调用方法methodName,然后在每个repeatRate重复调用。
/// </summary>
/// <param name="methodName">回调函数</param>
/// <param name="time">延迟调用</param>
/// <param name="repeatRate">时间间隔</param>
/// <param name="repeat">重复调用的次数 小于等于0表示无限触发</param>
public int Schedule(TimerHandler methodName, float time, float repeatRate, int repeat=0)
{
TimerNode timer = new TimerNode();
timer.callback = methodName;
timer.repeat = repeat;
timer.repeatRate = repeatRate;
timer.time = time;
timer.passedTime = timer.repeatRate; // 延迟调用
timer.isRemoved = false;
timer.timerId = autoIncId;
autoIncId++;
newAddTimers.Add(timer); // 加到缓存队列里面
return timer.timerId;
}
//移除Timers
public void Unschedule(int timerId)
{
if (!timers.ContainsKey(timerId))
{
return;
}
TimerNode timer = timers[timerId];
timer.isRemoved = true; // 先标记,不直接删除
}
//在Update里面调用
public void Update()
{
float dt = Time.deltaTime;
// 添加新的Timers
for (int i = 0; i < newAddTimers.Count; i++)
{
timers.Add(newAddTimers[i].timerId, newAddTimers[i]);
}
newAddTimers.Clear();
foreach (TimerNode timer in timers.Values)
{
if (timer.isRemoved)
{
removeTimers.Add(timer);
continue;
}
timer.passedTime += dt;
if (timer.passedTime >= (timer.time + timer.repeatRate))
{
// 做一次触发
timer.callback();
timer.repeat--;
timer.passedTime -= (timer.time + timer.repeatRate);
timer.time = 0;
if (timer.repeat == 0)
{
// 触发次数结束,将该删除的加入队列
timer.isRemoved = true;
removeTimers.Add(timer);
}
}
}
// 清理掉要删除的Timer;
for (int i = 0; i < removeTimers.Count; i++)
{
timers.Remove(removeTimers[i].timerId);
}
removeTimers.Clear();
}
}
调用:
using System;
using System.Collections;
using UnityEngine;
public class Test : MonoBehaviour
{
TimerMgr timer;
int TimerID;
void Start()
{
//初始化
timer = new TimerMgr();
timer.Init();
//启动定时器
TimerID = timer.Schedule(doSomething, 0, 2);
}
void Update()
{
timer.Update();
if (Input.GetKeyDown(KeyCode.W))
{
//关闭定时器
timer.Unschedule(TimerID);
}
}
private void doSomething()
{
Debug.Log("每2秒执行一次");
}
}
四、后言
这个定时器系统,可以用来处理在开发中可能用到的定时器需求。
不用再去Time.deltatime或者用协程去制作定时器了。
制作完定时器后,在其他项目也可以使用。