这次做了个适合新手看的时间计时器系统,实现起来相对简单,总共两个脚本,其中只有一个是核心脚本。
using System;
using System.Collections.Generic;
using UnityEngine;
public class TimeActionNode
{
/// <summary>
/// 时间标识
/// </summary>
public int guid;
/// <summary>
/// 下次要触发的时间
/// </summary>
public float needTriggerTime;
/// <summary>
/// 注册的时间
/// </summary>
public float recordTime;
/// <summary>
/// 剩余的触发次数
/// </summary>
public int repeatCount;
/// <summary>
/// 是否无限次触发
/// </summary>
public bool isLoop;
/// <summary>
/// 延迟的时间
/// </summary>
public float duration;
/// <summary>
/// 被清理掉时如果时间未走完 是否会在被清理时触发一次
/// </summary>
public bool isInvokeWhenClear;
/// <summary>
/// 要执行的事件
/// </summary>
public Action action;
}
public class TimeManager
{
private List<TimeActionNode> totalNodes = new();
private int originGuid=1000;
private float nowTime;
/// <summary>
///
/// </summary>
/// <param name="action"></param>
/// <param name="duration"></param>
/// <param name="repeatCount">为-1时无限次触发</param>
/// <param name="isInvokeWhenClear"></param>
/// <returns></returns>
public int AddTimeCallBack(Action action, float duration, int repeatCount, bool isInvokeWhenClear)
{
TimeActionNode node = ObjectPool.Instance.GetObj<TimeActionNode>();
node.guid = originGuid++;
node.recordTime = nowTime;
node.action = action;
node.repeatCount = repeatCount;
node.isLoop = repeatCount == -1;
node.duration = duration;
node.needTriggerTime = nowTime + duration;
node.isInvokeWhenClear = isInvokeWhenClear;
totalNodes.Add(node);
return node.guid;
}
/// <summary>
/// 清除某时间事件节点
/// </summary>
/// <param name="guid"></param>
public void ClearGuid(int guid)
{
if (totalNodes == null) return;
for (int i = totalNodes.Count - 1; i >= 0; i--)
{
if (guid == totalNodes[i].guid)
{
if (totalNodes[i].isInvokeWhenClear)
totalNodes[i].action?.Invoke();
ObjectPool.Instance.StoreObj(totalNodes[i]);
totalNodes.RemoveAt(i);
return;
}
}
}
public void Update()
{
nowTime += Time.deltaTime;
for (int i = totalNodes.Count - 1; i >= 0; i--)
{
var node = totalNodes[i];
//没到时间 下一个
if (node.needTriggerTime > nowTime) continue;
//到时间了 执行一次 更新数据
if (node.repeatCount > 0 || node.isLoop)
{
node.action?.Invoke();
node.repeatCount--;
if (node.repeatCount <= 0&&!node.isLoop)
{
//触发回收 清除该时间节点
ObjectPool.Instance.StoreObj(node);
totalNodes.RemoveAt(i);
}
//还有触发次数 再次更新数据
else
{
node.needTriggerTime += node.duration;
}
}
}
}
}
using System;
public static class TimeSystem
{
private static TimeManager timeManager = new TimeManager();
/// <summary>
/// 添加时间事件
/// </summary>
/// <param name="action"></param>
/// <param name="duration">延迟时间</param>
/// <param name="repeatCount">为-1时无限次触发</param>
/// <param name="isInvokeWhenClear">被清除时如果时间没走完的话 是否立刻触发一次</param>
/// <returns></returns>
public static int AddTimeCallBack(Action action, float duration, int repeatCount = 1, bool isInvokeWhenClear = false)
{
return timeManager.AddTimeCallBack(action, duration, repeatCount, isInvokeWhenClear);
}
/// <summary>
/// 清除某个时间事件节点
/// </summary>
/// <param name="guid"></param>
public static void ClearGuid(int guid)
{
timeManager.ClearGuid(guid);
}
/// <summary>
/// 主逻辑中调用 或配合MonoSystem调用
/// </summary>
public static void Update()
{
timeManager.Update();
}
}
其中有个ObjectPool对象池,它是同来回收class类对象的,可以不用它,自己new一个新的TimeActionNode也行,用了对象池可以节约一些性能减少GC。
示例如下:
using Sirenix.OdinInspector;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameLogic:MonoBehaviour
{
private int timeId;
private void Awake()
{
MonoSystem.Instance.AddListener(MonoUpdateType.Update,TimeSystem.Update);
}
[Button]
void TestTime()
{
timeId=TimeSystem.AddTimeCallBack(() => { Debug.Log("1second later"); },2f,1,true);
Debug.Log(timeId+"该任务开启计时");
}
[Button]
void testClear()
{
TimeSystem.ClearGuid(timeId);
Debug.Log("清除时间节点事件:"+timeId);
}
}
在以上代码中的MonoSystem是公共Mono系统,我们可以不用这个系统,只需要在Update函数里调用TimeSystem.Update()函数即可;
结果如下所示: