Unity中實現Timer定時器
阿新 • • 發佈:2022-12-03
前言:好久沒寫部落格了,倒不是沒寫的了, 現在手裡堆著的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; namespaceJunFramework { 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。接受各種改進建議