Unity中实现Timer定时器

前言:好久没写博客了,倒不是没写的了, 现在手里堆着的demo和小功能很多,而是懒,我是真滴懒啊。
需求:1.延迟执行方法;2.循环执行,间隔可控制;3.可以改变更新模式(update、fixedupdate,lateupdate),可以决定是否会受到Unity时间缩放影响;4.调用简单,可复用

思路:
1.改变更新模式和受到unity时间缩放的功能,可以使用全局静态变量控制

2.目前网上做定时器的方法三种:Coroutine、update、invoke,我选的第二种,使用MonoBehavior的update来做更新

3.不搞什么单例Manager之类的,调用麻烦,写的一长串,什么Instance的,看着很low

4.Timer对象只对数据信息做保存之用,更新时间状态等,集中由一个脚本的Update处理,分割数据层和逻辑层

5.虽然大部分使用Timer都是随用随丢,不会暂存,但是我new对象时,仍要保留这个功能,而且还要重置、完成等功能

 实现:

第一步就是先实现UpdateRegister功能,继承自MonoBehavior,使用Unity自己的Update方法。提供给外部注册和取消注册的功能

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

namespace JunFramework
{
    public enum UpdateMode
    {
        Update,
        FixedUpdate,
        LateUpdate
    }

    public enum TimeMode
    {
        Scaled,
        UnScaled
    }
    public class UpdateRegister : MonoBehaviour
    {
        /// <summary>
        /// update注册列表
        /// </summary>
        private static Dictionary<int, Action> update = new Dictionary<int, Action>();

        /// <summary>
        /// fixedupfate注册列表
        /// </summary>
        private static Dictionary<int, Action> fixedupdate = new Dictionary<int, Action>();

        /// <summary>
        /// lateupdate注册列表
        /// </summary>
        private static Dictionary<int, Action> lateupdate = new Dictionary<int, Action>();

        /// <summary>
        /// 全局唯一update下标
        /// </summary>
        private static int updateIndex = 0;
      
        /// <summary>
        /// 是否初始化
        /// </summary>
        private static bool isInit = false;

        /// <summary>
        /// 是否已经初始化
        /// </summary>
        public static bool IsInit
        {
            get => isInit;
        }

        /// <summary>
        /// 注册
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="handler"></param>
        /// <returns></returns>
        public static int Register(UpdateMode mode, Action handler)
        {
            ++updateIndex;
            switch (mode)
            {
                case UpdateMode.Update:
                    update.Add(updateIndex, handler);
                    break;
                case UpdateMode.FixedUpdate:
                    fixedupdate.Add(updateIndex, handler);
                    break;
                case UpdateMode.LateUpdate:
                    lateupdate.Add(updateIndex, handler);
                    break;
            }
            return updateIndex;
        }

        /// <summary>
        /// 根据updateindex取消注册
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="index"></param>
        public static void Cancle(UpdateMode mode, int index)
        {
            switch (mode)
            {
                case UpdateMode.Update:
                    update.Remove(index);
                    break;
                case UpdateMode.FixedUpdate:
                    fixedupdate.Remove(index);
                    break;
                case UpdateMode.LateUpdate:
                    lateupdate.Remove(index);
                    break;
            }
        }

        private void Awake()
        {
            isInit = true;
        }

        private void Update()
        {
            using (var e = update.GetEnumerator())
            {
                if (e.MoveNext())
                {
                    e.Current.Value?.Invoke();
                }
            }
        }

        private void FixedUpdate()
        {
            using (var e = fixedupdate.GetEnumerator())
            {
                if (e.MoveNext())
                {
                    e.Current.Value?.Invoke();
                }
            }
        }

        private void LateUpdate()
        {
            using (var e = lateupdate.GetEnumerator())
            {
                if (e.MoveNext())
                {
                    e.Current.Value?.Invoke();
                }
            }
        }
    }
}

第二步实现Timer对象,存储调用者传入的信息,可获取Timer状态(是否完成?是否正在工作?当前进度?)。可调用开始、结束、暂停、继续、重置、完成功能

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

namespace JunFramework.Timer
{
    public class Timer
    {
        #region timer设置

        /// <summary>
        /// Timer是否自动释放
        /// </summary>
        public static bool IsAutoRelease = true;

        /// <summary>
        /// Timer更新模式
        /// </summary>
        public static UpdateMode TimerUpdateMode = UpdateMode.FixedUpdate;

        /// <summary>
        /// Timer时间是否收到Unity缩放影响
        /// </summary>
        public static TimeMode TimerScaledModel = TimeMode.UnScaled;

        #endregion

        private float currentTime = 0f;//当前时间(0 -- steptime)
        private int currentRepeat = 0;//当前重复次数
        private float stepTime = 0f;//间隔
        private int repeatTimes = 0;//重复次数
        private bool isLoop = false;//是否循环
        private bool isDone = false;//是否完成
        private bool isStart = false;//是否开始
        private int updateIndex = 0;//update注册获取的下标,用于移除update注册
        private Action callback;//回调
        private float timeAcceleration;//每帧增加的时间

        /// <summary>
        /// 是否完成
        /// </summary>
        public bool IsDone
        {
            get => isDone;
        }

        /// <summary>
        /// 当前进度
        /// </summary>
        public float Progress
        {
            get => currentTime / stepTime;
        }

        /// <summary>
        /// 是否正在工作
        /// </summary>
        public bool IsWokring
        {
            get => !isDone && isStart;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stepTime">间隔时长</param>
        /// <param name="repeatTimes">重复次数</param>
        /// <param name="callback">回调</param>
        public Timer(float stepTime, int repeatTimes, Action callback)
        {
            this.currentTime = 0f;
            this.currentRepeat = 0;
            this.stepTime = stepTime;
            this.repeatTimes = repeatTimes;
            this.isLoop = false;
            this.isDone = false;
            this.timeAcceleration = GetPerTime();
            this.callback = callback;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stepTime">间隔</param>
        /// <param name="callback">回调</param>
        public Timer(float stepTime, Action callback)
        {
            this.currentTime = 0f;
            this.currentRepeat = 0;
            this.stepTime = stepTime;
            this.repeatTimes = 1;
            this.isLoop = false;
            this.isDone = false;
            this.timeAcceleration = GetPerTime();
            this.callback = callback;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="stepTime">间隔</param>
        /// <param name="isLoop">是否循环</param>
        /// <param name="callback">回调</param>
        public Timer(float stepTime, bool isLoop, Action callback)
        {
            this.currentTime = 0f;
            this.currentRepeat = 0;
            this.stepTime = stepTime;
            this.isLoop = isLoop;
            if (!isLoop)
            {
                this.repeatTimes = 1;
            }
            this.timeAcceleration = GetPerTime();
            this.isDone = false;
            this.callback = callback;
        }

        /// <summary>
        /// 每帧更新
        /// </summary>
        /// <param name="addTime">每帧增加的时间</param>
        private void Update(float addTime)
        {
            if (isDone)
                return;
            if (!isStart)
                return;

            currentTime += addTime;
            if (currentTime >= stepTime)
            {
                callback?.Invoke();
                currentTime = 0f;
                if (!isLoop)
                {
                    ++currentRepeat;
                    if (currentRepeat >= repeatTimes)
                    {
                        isDone = true;
                        if (IsAutoRelease)
                        {
                            Stop();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 开始计时器
        /// </summary>
        public void Start()
        {
            if (!UpdateRegister.IsInit)
            {
                Debug.LogWarning("UpdateRegister is not init, so this action will not works, please init UpdateRegister first!!!");
                return;
            }
            isStart = true;
            this.updateIndex = UpdateRegister.Register(TimerUpdateMode, () => this.Update(timeAcceleration));
        }

        /// <summary>
        /// 停止计时器(如果只是想暂停可用Pause,stop操作不可恢复)
        /// </summary>
        public void Stop()
        {
            isStart = false;
            isDone = true;
            UpdateRegister.Cancle(TimerUpdateMode, this.updateIndex);
        }

        /// <summary>
        /// 暂停
        /// </summary>
        public void Pause()
        {
            isStart = false;
        }

        /// <summary>
        /// 继续
        /// </summary>
        public void Resume()
        {
            isStart = true;
        }

        /// <summary>
        /// 重置
        /// </summary>
        public void Reset()
        {
            isStart = false;
            isDone = false;
            currentTime = 0f;
            currentRepeat = 0;
        }

        /// <summary>
        /// 不用等待,直接完成
        /// </summary>
        public void Complete()
        {
            if (isLoop)
                return;
            isDone = true;

            int tempRepeat = repeatTimes;
            while (tempRepeat > 0)
            {
                callback?.Invoke();
                --tempRepeat;
            }
            Stop();

        }

        /// <summary>
        /// 通过更新模式和time模式获取每帧更新时间
        /// </summary>
        /// <returns></returns>
        private float GetPerTime()
        {
            float resultTime = 0f;
            if (TimerScaledModel == TimeMode.Scaled && TimerUpdateMode == UpdateMode.FixedUpdate)
            {
                resultTime = Time.fixedDeltaTime;
            }
            else if (TimerScaledModel == TimeMode.UnScaled && TimerUpdateMode == UpdateMode.FixedUpdate)
            {
                resultTime = Time.fixedUnscaledDeltaTime;
            }
            else if (TimerScaledModel == TimeMode.Scaled && TimerUpdateMode == UpdateMode.Update)
            {
                resultTime = Time.deltaTime;
            }
            else if (TimerScaledModel == TimeMode.UnScaled && TimerUpdateMode == UpdateMode.FixedUpdate)
            {
                resultTime = Time.unscaledDeltaTime;
            }
            return resultTime;
        }
    }
}

第三步,简单编写测试代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using JunFramework.Timer;

public class TimerExample : MonoBehaviour
{
    private Timer timer = null;
    // Start is called before the first frame update
    void Start()
    {
        Timer.IsAutoRelease = true;
        Timer.TimerScaledModel = JunFramework.TimeMode.UnScaled;
        Timer.TimerUpdateMode = JunFramework.UpdateMode.FixedUpdate;

        //Example1
        //int i = 0;
        //Timer timer = new Timer(2, 2, () => Debug.Log(i++));
        //timer.Start();

        //Example2
        //timer = new Timer(2, true, () => Debug.Log("执行"));
        //timer.Start();

        //Example3
        timer = new Timer(2, 3, () => Debug.Log("看我看我"));
        timer.Start();
        timer.Complete();

        timer.Reset();
        timer.Start();
    }

    // Update is called once per frame
    void Update()
    {
        //Example2
        //if (Input.GetKeyDown(KeyCode.Space))
        //{
        //    timer.Pause();
        //}
        //else if (Input.GetKeyDown(KeyCode.A))
        //{
        //    timer.Resume();
        //}
    }
}

 

 总结:

本次Timer参考的是公司的lua Timer,其实这Timer也是大神蒙占志写的。我这个是C#写的,用于自己的游戏Demo。接受各种改进建议

posted @ 2022-12-03 15:53  军酱不是酱  阅读(2287)  评论(1编辑  收藏  举报