1. 程式人生 > 其它 >Unity中實現Timer定時器

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。接受各種改進建議