Unity 一个轻量级、高扩展性的计时器管理器TimerManager

在Unity项目中,计时器(Timer)是极其常见的需求——比如延迟执行一段逻辑、循环触发某个事件、生命周期绑定到某个对象等等。
今天要分享的,是我自己在项目中使用的一个轻量、好扩展、工程化水准较高的计时器管理系统 —— TimerManager

这套系统主要解决以下问题:

  • 替代 Invoke / Coroutine,提供更加灵活和可控的定时任务。

  • 支持重复调用无限循环Transform绑定销毁

  • 简单易用,接口统一,一行代码创建/销毁计时器

  • 不引入额外开销,纯C#逻辑,无任何GC频繁生成问题。

核心设计理念

  1. 集中管理、统一更新

    • 所有Timer对象由TimerManager集中维护,在Update中统一检查并触发。

    • 计时逻辑与主流程解耦。

  2. 灵活的回调支持

    • 支持无参数回调TimerHandler)和带参数回调TimerArgsHandler)。

    • 满足简单触发和复杂逻辑需求,适合不同场景。

  3. Transform关联

    • 创建计时器时可指定一个Transform作为挂钩。

    • 如果目标Transform销毁(比如GameObject被Destroy),可以手动调用Clear清理相关Timer,避免幽灵回调或空引用异常。

  4. 扩展方法友好

    • 通过TimerManagerExtensions,可以直接在任何Transform上调用 .CreateTimer().CreateTimerWithArgs().ClearTimer(),体验自然流畅。

代码结构解析

1. TimerManager - 单例核心

    
    public class SingletonMonoBase<T> : MonoBehaviour where T : MonoBehaviour
    {
        private static T instance;
    
        public static T Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = FindObjectOfType<T>();
    
                    // 如果在场景中找不到该类型的对象,则创建一个新对象
                    if (instance == null)
                    {
                        GameObject singletonObject = new GameObject(typeof(T).Name);
                        instance = singletonObject.AddComponent<T>();
                    }
                }
    
                return instance;
            }
        }
    
        public bool destroyOnLoadScene;
    
        protected virtual void Awake()
        {
            // 如果该对象不是单例对象,则销毁它
            if (instance != null && instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                // 将当前对象赋值给单例对象
                instance = this as T;
                if (!destroyOnLoadScene)
                    // 确保该对象在场景切换时不被销毁
                    DontDestroyOnLoad(gameObject);
            }
        }
}
  public class TimerManager : SingletonMonoBase<TimerManager>

2. 创建计时器

  • 无参数的计时器

public Timer CreateTimer(TimerHandler callBack, float time, int repeats = 1, Transform associatedTransform = null)
  • 带参数的计时器

public Timer CreateTimerWithArgs(TimerArgsHandler callBack, float time, int repeats, Transform associatedTransform = null, params object[] args)

底层统一走到Create()函数,新建一个Timer对象并添加到内部列表_timers中。

注意:这里传入的repeats如果小于0,意味着无限循环;如果大于0,就是指定次数调用。

3. 销毁计时器

public Timer DestroyTimer(Timer timer)

手动销毁某个Timer并清理资源。

public void Clear(Transform associatedTransform)

批量清理与某个Transform绑定的所有Timer,防止引用悬挂。

4. 驱动逻辑 - GameUpdate

每一帧(或固定频率),执行Update()

  • 遍历当前所有Timer。

  • 检查当前时间是否超过了Timer设定的触发时间。

  • 触发回调,并根据重复次数决定是否销毁。

特别注意:

if (timer.Repeats-- == 0)
{
    DestroyTimer(timer);
}

先执行回调,再减少Repeats,保证逻辑稳定性。

扩展用法 —— Transform的甜蜜糖

为了让使用体验更好,我封装了扩展方法:

例如,可以这样优雅地直接在一个Transform上使用:

transform.CreateTimer(() => { Debug.Log("触发了定时任务"); }, 2.0f, 3);

或者带参数:

transform.CreateTimerWithArgs((args) => { Debug.Log($"收到参数: {args[0]}"); }, 1.0f, 1, "Hello");

最后如果这个Transform销毁了,只需要:

transform.ClearTimer();

再也不怕回调到空对象引发Bug。

最后献上完整代码

using Reacool.Core.Update;
using System.Collections.Generic;
using UnityEngine;

namespace Reacool.Core.Timer
{
    public class TimerManager :  SingletonMonoBase<TimerManager>
    {
        private List<Timer> _timers = new List<Timer>(); //时间管理器

        /// <summary>
        /// 创建一个简单的计时器
        /// </summary>
        /// <param name="callBack">回调函数</param>
        /// <param name="time">计时器时间</param>
        /// <param name="repeats">回调次数  小于0代表循环 大于0代表repeats次</param>
        public Timer CreateTimer(TimerHandler callBack, float time, int repeats = 1, Transform associatedTransform = null)
        {
            return Create(callBack, null, time, repeats, associatedTransform);
        }

        public Timer CreateTimerWithArgs(TimerArgsHandler callBack, float time, int repeats, Transform associatedTransform = null, params System.Object[] args)
        {
            return Create(null, callBack, time, repeats, associatedTransform, args);
        }

        private Timer Create(TimerHandler callBack, TimerArgsHandler callBackArgs, float time, int repeats, Transform associatedTransform, params System.Object[] args)
        {
            Timer timer = new Timer(callBack, callBackArgs, time, repeats, args, associatedTransform);
            _timers.Add(timer);
            return timer;
        }

        public Timer DestroyTimer(Timer timer)
        {
            if (timer != null)
            {
                _timers.Remove(timer);
                timer.CleanUp();
                timer = null;
            }

            return timer;
        }

        public void ClearAll()
        {
            if (_timers != null)
            {
                for (int i = 0; i < _timers.Count; i++)
                {
                    _timers[i].CleanUp();
                }

                _timers.Clear();
            }
        }

        /// <summary>
        /// 清理与指定Transform关联的所有计时器
        /// </summary>
        public void Clear(Transform associatedTransform)
        {
            for (int i = _timers.Count - 1; i >= 0; i--)
            {
                if (_timers[i].AssociatedTransform == associatedTransform)
                {
                    DestroyTimer(_timers[i]);
                }
            }
        }

        /// <summary>
        /// 固定更新检查更新的频率
        /// </summary>
        public void Update()
        {
            if (_timers != null && _timers.Count != 0)
            {
                for (int i = _timers.Count - 1; i >= 0; i--)
                {
                    if (i > _timers.Count - 1)
                    {
                        return;
                    }

                    Timer timer = _timers[i];
                    float curTime = Time.time;
                    if (timer.Frequency + timer.LastTickTime > curTime)
                    {
                        continue;
                    }

                    timer.LastTickTime = curTime;
                    if (timer.Repeats-- == 0)
                    {
                        //计时完成,可以删除了
                        DestroyTimer(timer);
                    }
                    else
                    {
                        //触发计时
                        timer.Notify();
                    }
                }
            }
        }
    }
    public static class TimerManagerExtensions
    {
        /// <summary>
        /// 直接在 Transform 上创建一个计时器,自动将当前 Transform 作为关联对象传入。无参回调!
        /// </summary>
        public static Timer CreateTimer(this Transform transform, TimerHandler callback, float time, int repeats = 1)
        {
            return TimerManager.Instance.CreateTimer(callback, time, repeats, transform);
        }

        /// <summary>
        /// 直接在 Transform 上创建一个带参数的计时器,自动将当前 Transform 作为关联对象传入
        /// </summary>
        public static Timer CreateTimerWithArgs(this Transform transform, TimerArgsHandler callback, float time, int repeats, params object[] args)
        {
            return TimerManager.Instance.CreateTimerWithArgs(callback, time, repeats, transform, args);
        }

        /// <summary>
        /// 直接在 Transform 上调用,清理与当前 Transform 关联的所有计时器。有参回调!
        /// </summary>
        public static void ClearTimer(this Transform transform)
        {
            if (transform == null)
            {
                Debug.LogWarning("ClearTimer: 尝试对一个 null 的 Transform 清理计时器。");
                return;
            }
            TimerManager.Instance.Clear(transform);
        }
    }
}
using System;
using UnityEngine;

namespace Reacool.Core.Timer
{
    
    public delegate void TimerHandler();
    public delegate void TimerArgsHandler(System.Object[] args);
    public class Timer
    {
        public TimerHandler Handler;           //无参的委托
        public TimerArgsHandler ArgsHandler;   //带参数的委托
        public float Frequency;               //时间间隔
        public int Repeats;                   //重复次数
        public System.Object[] Args;

        public float LastTickTime;
        public Transform AssociatedTransform;   // 关联的Transform

        public event Action OnComplete;        //计时器完成一次工作
        public event Action OnDestroy;        //计时器被销毁

        public Timer() { }

        /// <summary>
        /// 创建一个时间事件对象
        /// </summary>
        /// <param name="Handler">回调函数</param>
        /// <param name="ArgsHandler">带参数的回调函数</param>
        /// <param name="frequency">时间内执行</param>
        /// <param name="repeats">重复次数</param>
        /// <param name="Args">参数  可以任意的传不定数量,类型的参数</param>
        public Timer(TimerHandler Handler, TimerArgsHandler ArgsHandler, float frequency, int repeats, System.Object[] Args, Transform associatedTransform)
        {
            this.Handler = Handler;
            this.ArgsHandler = ArgsHandler;
            this.Frequency = frequency;
            this.Repeats = repeats == 0 ? 1 : repeats;
            this.Args = Args;
            this.LastTickTime = Time.time;
            this.AssociatedTransform = associatedTransform; // 赋值

        }

        public void Notify()
        {
            if (Handler != null)
                Handler();
            if (ArgsHandler != null)
                ArgsHandler(Args);
            if (OnComplete != null)
            {
                OnComplete();
            }
        }

        /// <summary>
        /// 清理计时器,初始化参数  同时清理事件
        /// </summary>
        public void CleanUp()
        {
            Handler = null;
            ArgsHandler = null;
            Repeats = 1;
            Frequency = 0;
            if (OnDestroy != null)
            {
                OnDestroy();
            }
            OnDestroy = null;
            OnComplete = null;
        }
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贪小心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值