Unity 工具类 之 简单定时任务系统 TimerTaskSystem 的实现
目录
Unity 工具类 之 简单定时任务系统 TimerTaskSystem 的实现
一、简单介绍
Unity 工具类,自己整理的一些游戏开发可能用到的模块,单独独立使用,方便游戏开发。
定时任务系统,顾名思义就是在指定时间执行指定任务,这里包括定时(ms,s,min,hour,day)任务和定时帧任务两大类定时任务。
二、关键技术
1、实现原理很简单,在 Update 中计时计数看任务是否到达定时执行条件,到了执行,没到继续监测;
2、单例,使用单例很方便的进行定时任务添加;
3、定时任务,有循环次数,可以无限循环执行,也可以制定次数循环;
4、AddTimerTask/AddTimerFrameTask 添加定时/帧任务;
5、DeleteTimerTask/DeleteTimerFrameTask 删除定时/帧任务;
6、ReplaceTimerTask/ReplaceTimerFrameTask 替换定时/帧任务
三、注意事项
1、帧任务执行,计数帧可能会越界;
四、效果预览
五、实现步骤
1、打开Unity,新建一个工程,如下图
2、在工程中新建脚本,MonoSingleton 单列类,TimerTaskSystem 定时任务系统实现定时的核心代码逻辑,Test_TimerTaskSystem 测试 定时任务系统,如下图
3、把测试脚本和定时任务系统脚本挂载到场景中,如下图
4、测试效果如下图
五、关键代码
1、MonoSingleton
using UnityEngine;
public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance = null;
private static readonly object locker = new object();
private static bool bAppQuitting;
public static T Instance
{
get
{
if (bAppQuitting)
{
instance = null;
return instance;
}
lock (locker)
{
if (instance == null)
{
// 保证场景中只有一个 单例
T[] managers = Object.FindObjectsOfType(typeof(T)) as T[];
if (managers.Length != 0)
{
if (managers.Length == 1)
{
instance = managers[0];
instance.gameObject.name = typeof(T).Name;
return instance;
}
else
{
Debug.LogError("Class " + typeof(T).Name + " exists multiple times in violation of singleton pattern. Destroying all copies");
foreach (T manager in managers)
{
Destroy(manager.gameObject);
}
}
}
var singleton = new GameObject();
instance = singleton.AddComponent<T>();
singleton.name = "(singleton)" + typeof(T);
singleton.hideFlags = HideFlags.None;
DontDestroyOnLoad(singleton);
}
instance.hideFlags = HideFlags.None;
return instance;
}
}
}
protected virtual void Awake()
{
bAppQuitting = false;
}
protected virtual void OnDestroy()
{
bAppQuitting = true;
}
}
2、TimerTaskSystem
using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 定时任务系统功能有
/// 1、添加定时任务
/// 2、删除定时任务
/// 3、替换定时任务
/// </summary>
public class TimerTaskSystem : MonoSingleton<TimerTaskSystem>
{
// 任务 ID
private int taskId;
// taskId 列表
private List<int> taskIdList = new List<int>();
// 回收任务结束不用了的 taskId 的缓存列表
private List<int> recycleTaskIdList = new List<int>();
// 定时任务列表
private List<TimerTask> taskTimeList = new List<TimerTask>();
// 临时 定时任务列表 (作为缓存列表)
private List<TimerTask> tmpTaskTimeList = new List<TimerTask>();
// 获取任务id 的锁
private static readonly string objLock = "lock";
// 帧计数
int frameCounter = 0;
// 帧定时任务列表
private List<TimerFrameTask> taskFrameTimeList = new List<TimerFrameTask>();
// 帧临时 定时任务列表 (作为缓存列表)
private List<TimerFrameTask> tmpTaskFrameTimeList = new List<TimerFrameTask>();
// Update is called once per frame
void Update()
{
// 检测定时任务是否到达可执行
CheckTimerTask();
// 检测定时帧任务是否到达可执行
CheckTimerFrameTask();
// 回收任务ID
RecycleTaskId();
}
/// <summary>
/// 检测定时任务是否到达可执行
/// </summary>
private void CheckTimerTask() {
// 加入临时定时任务列表的定时任务 (这样做保证同帧不干扰下面遍历任务列表)
for (int tempIndex = 0; tempIndex < tmpTaskTimeList.Count; tempIndex++)
{
taskTimeList.Add(tmpTaskTimeList[tempIndex]);
}
tmpTaskTimeList.Clear();
// 遍历检测定时任务是否满足执行条件
for (int index = 0; index < taskTimeList.Count; index++)
{
TimerTask task = taskTimeList[index];
if (Time.realtimeSinceStartup * 1000 < task.destTime)
{
continue;
}
else
{
Action callback = task.callback;
// 异常捕获,避免回调函数出错
try
{
if (callback != null)
{
callback();
}
}
catch (Exception e)
{
Debug.Log(e.Message);
}
// 任务循环只剩一次
if (task.loopCount == 1)
{
// 把结束任务不用的 taskid 缓存到 回收任务 id 列表中
recycleTaskIdList.Add(task.taskId);
taskTimeList.RemoveAt(index);
index--;
}
else
{
// 不是无限循环,循环次数 -1
if (task.loopCount != 0)
{
task.loopCount -= 1;
}
// 目标时间更新一个 delay 时间
task.destTime += task.delay;
}
}
}
}
/// <summary>
/// 检测定时帧任务是否到达可执行
/// </summary>
private void CheckTimerFrameTask()
{
// 帧计数(会不会越界)
frameCounter += 1;
// 加入临时定时任务列表的定时任务 (这样做保证同帧不干扰下面遍历任务列表)
for (int tempIndex = 0; tempIndex < tmpTaskFrameTimeList.Count; tempIndex++)
{
taskFrameTimeList.Add(tmpTaskFrameTimeList[tempIndex]);
}
tmpTaskFrameTimeList.Clear();
// 遍历检测定时任务是否满足执行条件
for (int index = 0; index < taskFrameTimeList.Count; index++)
{
TimerFrameTask task = taskFrameTimeList[index];
if (frameCounter < task.destFrame)
{
continue;
}
else
{
Action callback = task.callback;
// 异常捕获,避免回调函数出错
try
{
if (callback != null)
{
callback();
}
}
catch (Exception e)
{
Debug.Log(e.Message);
}
// 任务循环只剩一次
if (task.loopCount == 1)
{
// 把结束任务不用的 taskid 缓存到 回收任务 id 列表中
recycleTaskIdList.Add(task.taskId);
taskFrameTimeList.RemoveAt(index);
index--;
}
else
{
// 不是无限循环,循环次数 -1
if (task.loopCount != 0)
{
task.loopCount -= 1;
}
// 目标时间更新一个 delay 时间
task.destFrame += task.delay;
}
}
}
}
#region TimeTask
/// <summary>
/// 添加定时的任务
/// </summary>
/// <param name="callback">任务回调</param>
/// <param name="delay">延迟时间</param>
/// <param name="loopCount">循环次数(0 为 无限循环)</param>
/// <param name="timerUnit">时间单位(ms,s,m,h,Day)</param>
/// <returns>返回任务id</returns>
public int AddTimerTask(Action callback, float delay,int loopCount = 1, TimerUnit timerUnit = TimerUnit.Millisecond) {
if (timerUnit != TimerUnit.Millisecond) {
switch (timerUnit)
{
case TimerUnit.Second:
delay = delay * 1000;
break;
case TimerUnit.Minute:
delay = delay * 1000 * 60;
break;
case TimerUnit.Hour:
delay = delay * 1000 * 60 * 60;
break;
case TimerUnit.Day:
delay = delay * 1000 * 60 * 60 * 24;
break;
default:
Debug.Log("Add Tssk Unit Type Error...");
break;
}
}
int tid = GetTaskId();
float destTime = Time.realtimeSinceStartup * 1000 + delay;
TimerTask timerTask = new TimerTask(tid, callback, destTime, delay, loopCount);
// 添加新定时任务的时候,先添加到临时列表(避免同帧遍历任务列表时干扰遍历列表)
tmpTaskTimeList.Add(timerTask);
// tid 添加到使用列表中
taskIdList.Add(tid);
return tid;
}
/// <summary>
/// 删除指定任务
/// </summary>
/// <param name="taskId">任务id</param>
/// <returns>返回是否删除</returns>
public bool DeleteTimerTask(int taskId) {
bool exist = false;
for (int i = 0; i < taskTimeList.Count; i++) {
TimerTask task = taskTimeList[i];
if (task.taskId == taskId) {
taskTimeList.RemoveAt(i);
for (int j = 0; j < taskIdList.Count; j++) {
if (taskIdList[j] == taskId) {
taskIdList.RemoveAt(j);
break;
}
}
exist = true;
break;
}
}
// 从临时缓存任务列表中查找
if (exist == false) {
for (int i =0; i<tmpTaskTimeList.Count; i++) {
TimerTask task = tmpTaskTimeList[i];
if (task.taskId == taskId) {
tmpTaskTimeList.RemoveAt(i);
for (int j = 0; j < taskIdList.Count; j++)
{
if (taskIdList[j] == taskId)
{
taskIdList.RemoveAt(j);
break;
}
}
exist = true;
break;
}
}
}
return exist;
}
/// <summary>
/// 替换存在的任务
/// </summary>
/// <param name="taskId">任务id</param>
/// <param name="callback">任务回调</param>
/// <param name="delay">延迟时间</param>
/// <param name="loopCount">循环次数(0 为 无限循环)</param>
/// <param name="timerUnit">时间单位(ms,s,m,h,Day)</param>
/// <returns>返回是否替换成功</returns>
public bool ReplaceTimerTask(int taskId, Action callback, float delay, int loopCount = 1, TimerUnit timerUnit = TimerUnit.Millisecond)
{
if (timerUnit != TimerUnit.Millisecond)
{
switch (timerUnit)
{
case TimerUnit.Second:
delay = delay * 1000;
break;
case TimerUnit.Minute:
delay = delay * 1000 * 60;
break;
case TimerUnit.Hour:
delay = delay * 1000 * 60 * 60;
break;
case TimerUnit.Day:
delay = delay * 1000 * 60 * 60 * 24;
break;
default:
Debug.Log("Add Tssk Unit Type Error...");
break;
}
}
float destTime = Time.realtimeSinceStartup * 1000 + delay;
TimerTask newTimerTask = new TimerTask(taskId, callback, destTime, delay, loopCount);
bool isReplace = false;
// 从任务列表中查找对应的 taskId 进行任务替换
for (int i = 0; i < taskTimeList.Count; i++)
{
TimerTask task = taskTimeList[i];
if (task.taskId == taskId)
{
taskTimeList[i] = newTimerTask;
isReplace = true;
break;
}
}
// 从临时缓冲任务列表中替换
if (isReplace == false) {
for (int i = 0; i < tmpTaskTimeList.Count; i++)
{
TimerTask task = tmpTaskTimeList[i];
if (task.taskId == taskId)
{
tmpTaskTimeList[i] = newTimerTask;
isReplace = true;
break;
}
}
}
return isReplace;
}
#endregion
#region TimerFrameTask
/// <summary>
/// 添加定时帧任务
/// </summary>
/// <param name="callback">任务回调</param>
/// <param name="delay">延迟帧数</param>
/// <param name="loopCount">循环次数(0 为 无限循环)</param>
/// <returns>返回任务id</returns>
public int AddTimerFrameTask(Action callback, int delay, int loopCount = 1)
{
int tid = GetTaskId();
int destTime = frameCounter + delay;
TimerFrameTask timerTask = new TimerFrameTask(tid, callback, destTime, delay, loopCount);
// 添加新定时帧任务的时候,先添加到临时列表(避免同帧遍历任务列表时干扰遍历列表)
tmpTaskFrameTimeList.Add(timerTask);
// tid 添加到使用列表中
taskIdList.Add(tid);
return tid;
}
/// <summary>
/// 删除指定任务
/// </summary>
/// <param name="taskId">任务id</param>
/// <returns>返回是否删除</returns>
public bool DeleteTimerFrameTask(int taskId)
{
bool exist = false;
for (int i = 0; i < taskFrameTimeList.Count; i++)
{
TimerFrameTask task = taskFrameTimeList[i];
if (task.taskId == taskId)
{
taskFrameTimeList.RemoveAt(i);
for (int j = 0; j < taskIdList.Count; j++)
{
if (taskIdList[j] == taskId)
{
taskIdList.RemoveAt(j);
break;
}
}
exist = true;
break;
}
}
// 从临时缓存任务列表中查找
if (exist == false)
{
for (int i = 0; i < tmpTaskFrameTimeList.Count; i++)
{
TimerFrameTask task = tmpTaskFrameTimeList[i];
if (task.taskId == taskId)
{
tmpTaskFrameTimeList.RemoveAt(i);
for (int j = 0; j < taskIdList.Count; j++)
{
if (taskIdList[j] == taskId)
{
taskIdList.RemoveAt(j);
break;
}
}
exist = true;
break;
}
}
}
return exist;
}
/// <summary>
/// 替换存在的任务
/// </summary>
/// <param name="taskId">任务id</param>
/// <param name="callback">任务回调</param>
/// <param name="delay">延迟帧数</param>
/// <param name="loopCount">循环次数(0 为 无限循环)</param>
/// <returns>返回是否替换成功</returns>
public bool ReplaceTimerFrameTask(int taskId, Action callback, int delay, int loopCount = 1)
{
int destTime = frameCounter+ delay;
TimerFrameTask newTimerFrameTask = new TimerFrameTask(taskId, callback, destTime, delay, loopCount);
bool isReplace = false;
// 从任务列表中查找对应的 taskId 进行任务替换
for (int i = 0; i < taskFrameTimeList.Count; i++)
{
TimerFrameTask task = taskFrameTimeList[i];
if (task.taskId == taskId)
{
taskFrameTimeList[i] = newTimerFrameTask;
isReplace = true;
break;
}
}
// 从临时缓冲任务列表中替换
if (isReplace == false)
{
for (int i = 0; i < tmpTaskFrameTimeList.Count; i++)
{
TimerFrameTask task = tmpTaskFrameTimeList[i];
if (task.taskId == taskId)
{
tmpTaskFrameTimeList[i] = newTimerFrameTask;
isReplace = true;
break;
}
}
}
return isReplace;
}
#endregion
#region Tools
/// <summary>
/// 回收任务结束,不用的taskId,以备下次使用
/// </summary>
private void RecycleTaskId() {
for (int i = 0; i < recycleTaskIdList.Count; i++)
{
int taskId = recycleTaskIdList[i];
for (int j = 0; j < taskIdList.Count; j++)
{
if (taskIdList[j] == taskId)
{
taskIdList.RemoveAt(j);
break;
}
}
}
recycleTaskIdList.Clear();
}
/// <summary>
/// 获得任务ID
/// </summary>
/// <returns>返回一个任务ID</returns>
private int GetTaskId()
{
lock (objLock) {
taskId += 1;
while (true) {
// 防止越界
if (taskId == int.MaxValue) {
taskId = 0;
}
// 遍历循环检查taskId是否被使用
bool isUsed = false;
for (int i =0;i < taskIdList.Count; i++) {
if (taskId == taskIdList[i]) {
isUsed = true;
break;
}
}
// 如果该 taskId 没有被使用,跳出循环
if (isUsed == false)
{
break;
}
else {
taskId += 1;
}
}
}
return taskId;
}
#endregion
#region 任务模型
/// <summary>
/// 任务模型
/// </summary>
class TimerTask {
public int taskId;
public Action callback;
public float destTime;
public float delay;
public int loopCount;
public TimerTask(int taskId, Action callback, float destTime, float delay, int loopCount)
{
this.taskId = taskId;
this.callback = callback;
this.destTime = destTime;
this.delay = delay;
this.loopCount = loopCount;
}
}
/// <summary>
/// 帧任务模型
/// </summary>
class TimerFrameTask
{
public int taskId;
public Action callback;
public int destFrame;
public int delay;
public int loopCount;
public TimerFrameTask(int taskId, Action callback, int destFrame, int delay, int loopCount)
{
this.taskId = taskId;
this.callback = callback;
this.destFrame = destFrame;
this.delay = delay;
this.loopCount = loopCount;
}
}
#endregion
}
/// <summary>
/// 时间单位枚举
/// </summary>
public enum TimerUnit
{
Millisecond,
Second,
Minute,
Hour,
Day
}
3、Test_TimerTaskSystem
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test_TimerTaskSystem : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// 任务 ID
private int taskId;
void Update() {
#region 测试定时任务
if (Input.GetKeyDown(KeyCode.A)) {
Debug.Log("AddTimerTask");
taskId = TimerTaskSystem.Instance.AddTimerTask(TestFunA, 1000, 0);
}
if (Input.GetKeyDown(KeyCode.D)){
Debug.Log("DeleteTimerTask " + taskId);
TimerTaskSystem.Instance.DeleteTimerTask(taskId);
}
if (Input.GetKeyDown(KeyCode.R))
{
Debug.Log("ReplaceTimerTask " + taskId);
TimerTaskSystem.Instance.ReplaceTimerTask(taskId, TestFunB, 500, 2);
}
#endregion
#region 测试定时帧任务
if (Input.GetKeyDown(KeyCode.Alpha1))
{
Debug.Log("AddTimerFrameTask");
taskId = TimerTaskSystem.Instance.AddTimerFrameTask(TestFrameFunA, 50, 0);
}
if (Input.GetKeyDown(KeyCode.Alpha2))
{
Debug.Log("DeleteTimerFrameTask " + taskId);
TimerTaskSystem.Instance.DeleteTimerFrameTask(taskId);
}
if (Input.GetKeyDown(KeyCode.Alpha3))
{
Debug.Log("ReplaceTimerFrameTask " + taskId);
TimerTaskSystem.Instance.ReplaceTimerFrameTask(taskId, TestFrameFunB, 500, 2);
}
#endregion
}
#region 测试用到的函数
void TestFunA() {
Debug.Log(" TestFunA taskId = " + taskId + " "+System.DateTime.Now);
}
void TestFunB()
{
Debug.Log(" TestFunB taskId = " + taskId + " " + System.DateTime.Now);
}
void TestFrameFunA()
{
Debug.Log(" TestFrameFunA taskId = " + taskId + " " + System.DateTime.Now);
}
void TestFrameFunB()
{
Debug.Log(" TestFrameFunB taskId = " + taskId + " " + System.DateTime.Now);
}
#endregion
}