Unity中计时器管理类

该代码实现了一个基于Unity的计时器管理系统,利用Update和Time.deltaTime进行计时,通过堆排序优化性能,避免每次更新都遍历列表。系统支持注册定时器、按标签获取定时器、清除单个或全部定时器功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

该文中的计时器是基于Unity的生命周期中Update的实现,通过Time.deltaTime的累加作为计数器,将需要定时的对象类根据延长的时间排序,不用每一次Update时都对列表进行操作。

代码记录,留做之后扩展使用:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestTimeManger : MonoBehaviour
{

    protected static List<TimerComponent> componentsList = new List<TimerComponent>();
    protected static int _tagCount = 1000;  //标签的初始值
    private bool isBackground = false;      //是否可以在后台运行
    private float initTime = 0f;            //初始时间
    float dt = 0;                           //刷新时间

    private static TestTimeManger _instance;
    public static TestTimeManger Instance
    {
        get
        {
            return _instance;
        }
    }
    private void Awake()
    {
        if (_instance == null)
        {
            _instance = this;
        }
    }

    void Start()
    {
        dt = Time.deltaTime;
    }

    private void Update()
    {
        initTime += dt;
        if (componentsList.Count > 0)
        {
            if (componentsList[0].life <= initTime)
            {
                for (int i = 0; i < componentsList.Count; i++)
                {
                    if (componentsList[i].life <= initTime)
                    {
                        componentsList[i].func();
                        if (componentsList[i].count == -1)
                        {
                            componentsList[i].life += componentsList[i].interval;
                        }
                        else if (componentsList[i].count == 1)
                        {
                            componentsList.Remove(componentsList[i]);
                        }
                        else
                        {
                            componentsList[i].count -= 1;
                            componentsList[i].life += componentsList[i].interval;
                        }
                    }
                }
                HeapSortFunction();
            }
        }
    }

    /// <summary>
    /// 注册一个定时器
    /// </summary>
    /// <param name="interval">间隔 单位秒</param>
    /// <param name="func">调度方法</param>
    /// <param name="times">循环次数 times{ 0 | < 1 }:一直循环</param>
    /// <returns></returns>
    public int SetInterval(float interval, Action func, int times = 1)
    {
        var scheduler = new TimerComponent();
        scheduler.life = initTime + interval;
        scheduler.interval = interval;
        scheduler.func = func;
        scheduler.count = times;
        scheduler.tag = ++_tagCount;
        componentsList.Add(scheduler);
        HeapSortFunction();
        return _tagCount;
    }

    /// <summary>
    /// 通过tag获取定时器的对象
    /// </summary>
    /// <param name="tag"></param>
    /// <returns></returns>
    public TimerComponent GetTimer(int tag)
    {
        for (int i = 0; i < componentsList.Count; i++)
        {
            if (tag == componentsList[i].tag)
            {
                return componentsList[i];
            }
        }
        return null;
    }

    /// <summary>
    /// 清空计时器
    /// </summary>
    /// <param name="tag"></param>
    public void ClearTime(int tag)
    {
        for (int i = 0; i < componentsList.Count; i++)
        {
            if (tag == componentsList[i].tag)
            {
                componentsList.Remove(componentsList[i]);
                Debug.LogError("ClearTimer " + tag + "  " + componentsList.Count);
                return;
            }
        }
    }

    /// <summary>
    /// 清空所有计时器
    /// </summary>
    public void ClearTimers()
    {
        componentsList.Clear();
    }

    /// <summary>
    /// 堆排序
    /// </summary>
    public void HeapSortFunction()
    {
        BuildMaxHeap();
        for (int i = componentsList.Count - 1; i > 0; i--)
        {
            Tuple<TimerComponent, TimerComponent> tuple =  Swap(componentsList[0], componentsList[i]); //将堆顶元素依次与无序区的最后一位交换(使堆顶元素进入有序区)
            componentsList[0] = tuple.Item1;
            componentsList[i] = tuple.Item2;
            MaxHeapify(0, i); //重新将无序区调整为大顶堆
        }
    }

    ///<summary>
    /// 创建大顶推(根节点大于左右子节点)
    ///</summary>
    ///<param name="array">待排数组</param>
    private void BuildMaxHeap()
    {
        //根据大顶堆的性质可知:数组的前半段的元素为根节点,其余元素都为叶节点
        for (int i = componentsList.Count / 2 - 1; i >= 0; i--) //从最底层的最后一个根节点开始进行大顶推的调整
        {
            MaxHeapify(i, componentsList.Count); //调整大顶堆
        }
    }

    /// <summary>
    /// 进行堆排序
    /// </summary>
    private void MaxHeapify(int currentIndex, int heapSize)
    {
        int left = 2 * currentIndex + 1;    //左子节点在数组中的位置
        int right = 2 * currentIndex + 2;   //右子节点在数组中的位置
        int large = currentIndex;   //记录此根节点、左子节点、右子节点 三者中最大值的位置

        if (left < heapSize && componentsList[left].life > componentsList[large].life)  //与左子节点进行比较
        {
            large = left;
        }
        if (right < heapSize && componentsList[right].life > componentsList[large].life)    //与右子节点进行比较
        {
            large = right;
        }
        if (currentIndex != large)  //如果 currentIndex != large 则表明 large 发生变化(即:左右子节点中有大于根节点的情况)
        {
            Tuple<TimerComponent, TimerComponent> tuple = Swap(componentsList[currentIndex], componentsList[large]); //将堆顶元素依次与无序区的最后一位交换(使堆顶元素进入有序区)
            componentsList[currentIndex] = tuple.Item1;
            componentsList[large] = tuple.Item2;
            MaxHeapify(large, heapSize); //以上次调整动作的large位置(为此次调整的根节点位置),进行递归调整
        }
    }

    /// <summary>
    /// 交换位置
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    private Tuple<TimerComponent, TimerComponent> Swap(TimerComponent a, TimerComponent b)
    {
        TimerComponent temp = null;
        temp = a;
        a = b;
        b = temp;
        return Tuple.Create(a, b);
    }
}

public class TimerComponent
{
    public int tag;     //自生的唯一标识
    public float life;  //生命周期时长
    public float interval; //间隔时长
    public int count;   //调用次数
    public Action func; //回调方法
}

测试代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        int ab1 = TestTimeManger.Instance.SetInterval(5, () =>
        {
            Debug.LogError("1111111111111");
        }, 1);

        int ab2 = TestTimeManger.Instance.SetInterval(4f, () =>
        {
            TestTimeManger.Instance.ClearTime(ab1);
            Debug.LogError("222222222");
        }, 2);

        int ab3 = TestTimeManger.Instance.SetInterval(3f, () =>
        {
            Debug.LogError("333333");
        }, -1);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

### Unity 中实现计时器的最佳实践 在 Unity 的开发过程中,`Update()` 函数是一个常用的更新循环方法,它会在每一帧被调用一次。因此,许多初学者会倾向于将计时器逻辑放置在此处[^1]。然而,从设计的角度来看,直接在 `Update()` 方法中维护计时器可能会导致代码不够整洁,并且难以扩展和维护。 为了优化这一过程,可以考虑以下几种最佳实践: #### 使用独立的协程 (Coroutine) Unity 提供了强大的协程机制来处理异步操作。通过创建一个单独的协程来进行计时器管理,可以使代码结构更清晰、可读性更高。这种方式不仅减少了对 `Update()` 函数的依赖,还能够更好地控制计时器的行为。 以下是基于协程的一个简单计时器实现示例: ```csharp using UnityEngine; public class TimerExample : MonoBehaviour { private float timerDuration = 5f; // 定义计时器持续时间 private bool isTimerRunning = false; public void StartTimer() { if (!isTimerRunning) { StartCoroutine(TimerRoutine()); } } private IEnumerator TimerRoutine() { isTimerRunning = true; float elapsedTime = 0f; while (elapsedTime < timerDuration) { elapsedTime += Time.deltaTime; Debug.Log($"Elapsed time: {elapsedTime:F2}s"); yield return null; // 等待下一帧继续执行 } OnTimerComplete(); isTimerRunning = false; } private void OnTimerComplete() { Debug.Log("Timer has completed!"); } } ``` 这种方法的优点在于它可以轻松暂停、恢复或重置计时器,而无需频繁访问全局状态变量[^2]。 --- #### 利用自定义脚本组件封装计时功能 如果项目中有多个地方需要用到似的计时器逻辑,则可以通过编写一个通用的时间管理工具来集中处理这些需求。这种做法不仅可以提高代码复用率,还能减少重复劳动并增强项目的模块化程度。 下面展示了一个简单的计时器工具的设计思路: ```csharp using System.Collections.Generic; using UnityEngine; public static class TimerManager { private static readonly Dictionary<string, Coroutine> timers = new(); public static void AddTimer(string key, float duration, System.Action onCompleteCallback) { if (timers.ContainsKey(key)) { RemoveTimer(key); } var coroutine = ExecuteTimer(duration, onCompleteCallback); timers[key] = coroutine; } public static void RemoveTimer(string key) { if (timers.TryGetValue(key, out Coroutine coroutine)) { Object.FindObjectOfType<Behaviour>().StopCoroutine(coroutine); timers.Remove(key); } } private static IEnumerator ExecuteTimer(float duration, System.Action onCompleteCallback) { yield return new WaitForSeconds(duration); onCompleteCallback?.Invoke(); // 调用完成回调函数 } } // 示例用法 public class ExampleUsage : MonoBehaviour { void Start() { TimerManager.AddTimer("exampleKey", 3f, () => Debug.Log("This message will appear after 3 seconds")); } } ``` 此方案允许开发者动态注册不同名称的计时器实例,并支持随时取消特定的任务运行[^4]。 --- #### UI 显示与参数调整 当涉及到用户界面显示时(比如 Text 组件),可以直接绑定到上述任意一种方式中的进度数据上进行实时渲染。例如,假设有一个用于呈现倒计时数值的文字对象,那么只需将其拖拽至指定字段即可自动同步变化情况[^3]。 同时还可以引入额外的速度因子属性 (`Speed`) 来灵活调节流逝速率,从而满足更多个性化场景下的应用需求。 --- ### 总结 虽然利用 `Update()` 可以快速搭建起基础版的计时器模型,但从长远角度来看并不推荐这样做。相比之下采用专门针对该问题定制化的解决方案——无论是借助于内置协程还是外部辅助型插件库等形式都能够显著改善整体架构质量以及提升后续迭代效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值