Unity框架設(shè)計(jì):打造Timer定時(shí)器框架
1: 為什么我們要自己造輪子來做定時(shí)器系統(tǒng)
傳統(tǒng)的Unity做定時(shí)器的方式有三種,總結(jié)如下:
(1) 在組件類里面定義一個變量,每次Update的時(shí)候,累積時(shí)間,當(dāng)時(shí)間到達(dá)特定的閾值時(shí), 觸發(fā)函數(shù)調(diào)用。
void Update() {
? ? ? ?float dt = Time.deltaTime;
? ? ? ?this.passedTime += dt;
? ? ? ? if(this.passedTime >= 閾值) {
? ? ? ? ? ? ? ? ?doSomeThing();
? ? ? ? ? ? ? ? ?this.passedTime -= 閾值;
? ? ? ? ?}
}
(2) 使用協(xié)程,來做定時(shí)器等待。
yeild return new WaitForSecond(5.0f);
// 5秒后做相關(guān)的事情
doSomeThing();
?
(3) 組件類MonoBehaviour 接口 Invoke,來做定時(shí)器 this.Invoke("doSomeThing", 5);
?
上面三種方法都有自己的缺點(diǎn),第(1)(2)種使用起來不方便, 第(3)種節(jié)點(diǎn)隱藏后不方便觸發(fā),不方便掉其他代碼里面的回調(diào)函數(shù),不方便傳遞參數(shù)。
所以我們在項(xiàng)目里面決定自己造輪子,來打造一個Timer模塊,處理游戲種的各種定時(shí)器需求。
?
2: 定時(shí)器系統(tǒng)的核心原理是什么?
每個定時(shí)器對象是一個Timer, 添加的時(shí)候交給定時(shí)器管理系統(tǒng), 定時(shí)器系統(tǒng)負(fù)責(zé)管理所有的定時(shí)器對象,每次游戲Update的時(shí)候,遍歷里面的每個定時(shí)器對象,
把它們過去的時(shí)間增加Time.deltaTime, 當(dāng)過去的時(shí)間達(dá)到定時(shí)器觸發(fā)時(shí)間的時(shí)候,觸發(fā)定時(shí)器調(diào)用, 如果達(dá)到Timer觸發(fā)次數(shù),就把這個Timer移除,
否則就重置時(shí)間,繼續(xù)直到下一個時(shí)間的觸發(fā)。
?
3: 如何設(shè)計(jì)的接口
(1) 編寫TimerMgr Timer管理的一個單例,在游戲初始化的時(shí)候創(chuàng)建出來。
(2) 添加Init()接口, 在初始化的時(shí)候調(diào)用Init接口,對TimerMgr所要的數(shù)據(jù)與狀態(tài)進(jìn)行初始化。
(3) 編寫Update函數(shù),在每次Update的時(shí)候迭代Timer,來負(fù)責(zé)Timer的觸發(fā)。
(4) 提供Schedule系列接口,來給用戶加一個定時(shí)器,Schedule系列接口如下:
【參數(shù)】: func Timer時(shí)間到了以后調(diào)用的回調(diào)函數(shù);
param: 用戶給Timer觸發(fā)回調(diào)函數(shù)傳遞的參數(shù), 類型object,可以傳任意類型的數(shù)據(jù);
repeat: timer 要重復(fù)調(diào)用的次數(shù);
duration: 每次timer調(diào)用的時(shí)間間隔;
delay: 第一次timer觸發(fā)在多少秒以后;
【返回值】: Timer ID號,根據(jù)這個ID號可以在TimerMgr中查到這個Timer對象,用來作為刪除Timer的憑證;
public int Schedule(TimerHandler func, object param, int repeat, float duration, float delay = 0.0f);
(5)提供Unschedule系列接口:
【參數(shù)】: timerId, Timer的唯一標(biāo)識Id號,根據(jù)這個Id號取消Timer
public void Unschedule(int timeId);
?
4: 具體實(shí)現(xiàn)
(1) Timer對象,每一個Timer就是一個Timer對象,這個Timer對象保存到TimerMgr里面,數(shù)據(jù)結(jié)構(gòu)定義如下:
class TimerNode
{
public TimerMgr.TimerHandler callback;
public float duration; // 定時(shí)器觸發(fā)的時(shí)間間隔;
public float delay; // 第一次觸發(fā)要隔多少時(shí)間;
public int repeat; // 你要觸發(fā)的次數(shù);
public float passedTime; // 這個Timer過去的時(shí)間;
public object param; // 用戶要傳的參數(shù)
public bool isRemoved; // 是否已經(jīng)刪除了
public int timerId; // 標(biāo)識這個timer的唯一Id號;
}
(2) TimerMgr 定義一個委托類型,用來作為回調(diào)函數(shù)的類型:
public delegate void TimerHandler(object param);
(3) 定義一個字典,用來存放timeId到Timer對象的一個映射,存放游戲里面所有的Timer對象。
private Dictionary<int, TimerNode> timers = null;
(4) 兩個緩存隊(duì)列, 一個是新增Timer的緩存列表,一個是已刪除Timer的緩存列表,這個機(jī)制非常的重要,當(dāng)我們新增或刪除Timer的時(shí)候,
有可能在遍歷Timer 觸發(fā)回調(diào)的里面來添加或刪除一個Timer, 如果直接操作map里面的Timer,這樣就會改變遍歷時(shí)候的結(jié)構(gòu),導(dǎo)致出錯,所以我們添加或
刪除Timer的時(shí)候, 先放到緩存隊(duì)列,等Update里面Timer遍歷完了以后,再來添加或移除Timer到TimerMgr的map里面。
private List<TimerNode> removeTimers = null;
private List<TimerNode> newAddTimers = null;
(5) 每個Timer有一個唯一的Id號,所以我們設(shè)置一個自增長的Id號機(jī)制,在TimerMgr里面加數(shù)據(jù)成員 autoIncId, 每次創(chuàng)建一個Timer,就把a(bǔ)utoIncId當(dāng)前的值作為Id號,并往后加1。
private int autoIncId = 1;
?
(6) 初始化TimerMgr:
public void Init()
{
this.timers = new Dictionary<int, TimerNode>();
this.autoIncId = 1;
this.removeTimers = new List<TimerNode>();
this.newAddTimers = new List<TimerNode>();
}
?
(7) Schedule系列接口, 其余的都調(diào)用這個接口,傳特殊的參數(shù):
// [repeat < 0 or repeat == 0 表示的是無限觸發(fā)]
? ?public int Schedule(TimerHandler func, object param, int repeat, float duration, float delay = 0.0f)
? ?{
? ? ? ?TimerNode timer = new TimerNode();
? ? ? ?timer.callback = func;
? ? ? ?timer.param = param;
? ? ? ?timer.repeat = repeat;
? ? ? ?timer.duration = duration;
? ? ? ?timer.delay = delay;
? ? ? ?timer.passedTime = timer.duration; ?// 第一次只受delay影響,所以過去的時(shí)間為duration
? ? ? ?timer.isRemoved = false;
? ? ? ?timer.timerId = this.autoIncId;
? ? ? ?this.autoIncId++;
? ? ? ?// this.timers.Add(timer.timerId, timer);
? ? ? ?this.newAddTimers.Add(timer); // 加到緩存隊(duì)列里面,不直接加入到定時(shí)器Map
? ? ? ?return timer.timerId;
? ?}
(8) Unschedule系列接口:
public void Unschedule(int timerId)
{
if (!this.timers.ContainsKey(timerId)) {
return;
}
TimerNode timer = this.timers[timerId];
timer.isRemoved = true; ?// 先標(biāo)記,不直接刪除
}
(9) TimerMgr每次Update迭代Timer,并觸發(fā)
private void Update()
? ?{
? ? ? ?float dt = Time.deltaTime;
? ? ? ?// 把新加進(jìn)來的放入到我們的表里面來
? ? ? ?for (int i = 0; i < this.newAddTimers.Count; i++) {
? ? ? ? ? ?this.timers.Add(this.newAddTimers[i].timerId, this.newAddTimers[i]);
? ? ? ?}
? ? ? ?this.newAddTimers.Clear();
? ? ? ?// end
? ? ? ?
? ? ? ?// 遍歷迭代過程中不涉及添加or刪除,避免改變map結(jié)構(gòu)引發(fā)迭代錯誤
? ? ? ?foreach (TimerNode timer in this.timers.Values) {
? ? ? ? ? ?if (timer.isRemoved) {
? ? ? ? ? ? ? ?this.removeTimers.Add(timer);
? ? ? ? ? ? ? ?continue;
? ? ? ? ? ?}
? ? ? ? ? ?timer.passedTime += dt;
? ? ? ? ? ?if (timer.passedTime >= (timer.delay + timer.duration)) {
? ? ? ? ? ? ? ?// 做一次觸發(fā)
? ? ? ? ? ? ? ?timer.callback(timer.param);
? ? ? ? ? ? ? ?timer.repeat--;
? ? ? ? ? ? ? ?timer.passedTime -= (timer.delay + timer.duration);
? ? ? ? ? ? ? ?timer.delay = 0; // 很重要;
? ? ? ? ? ? ? ?if (timer.repeat == 0) { // 觸發(fā)次數(shù)結(jié)束,刪除這個Timer, 但不馬上刪除;
? ? ? ? ? ? ? ? ? ?timer.isRemoved = true;
? ? ? ? ? ? ? ? ? ?this.removeTimers.Add(timer);
? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ?// end
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?// 結(jié)束以后,清理掉要刪除的Timer;
? ? ? ?for (int i = 0; i < this.removeTimers.Count; i++) {
? ? ? ? ? ?this.timers.Remove(this.removeTimers[i].timerId);
? ? ? ?}
? ? ? ?this.removeTimers.Clear();
? ? ? ?// end
? ?}