這次的內容有點類似設計模式裏的觀察者模式。但是和常規意義上的觀察者模式也不是完全一致,所以各位就不要咬文嚼字啦!咦?設計模式?!不懂!沒關係,說不定你以前就用過。
開場白
我們來想象一個場景。在加載一個模型時,你需要從網上下載,但是你並不知道下載需要花費多少時間。你所知道的是,當下載完成後,就可以把模型放在特定位置上,開始遊戲。那麼,我們怎樣才能判斷下載完成呢?
一個簡單的方法是,在每一幀的時候都判斷下載是否完成,完成後就可以繼續後面的工作。因此,我們可以這樣做,我們告訴一個管理器,嗨,你幫我盯着點,看下載完了沒有,完了就叫我一聲,好讓我執行XXX函數。我們今天要做的,就是構造這樣一個管理器。
實現
我們不防把上面這樣一件工作成爲一個計數器——Timer(這個名字可能不太恰當),把需要被通知者成爲觀察者——Oberver,而像下載管理器這樣的對象成爲一個主題——Subject。
首先,我們來定義觀察者和主題對象。TimerObserverOrSubject.cs如下:
-
using UnityEngine;
-
using System.Collections;
-
-
public class TimerObserverOrSubject : MonoBehaviour {
-
-
virtual protected void OnDestroy ()
-
{
-
if(Singleton.IsCreatedInstance("TimerController"))
-
{
-
(Singleton.getInstance("TimerController") as TimerController).ClearTimer(this);
-
}
-
}
-
}
TimerObserverOrSubject.cs的內容非常簡單,它的工作就是在該腳本被析構時,及時地從計數器管理器裏面刪除涉及這個對象的所有Timer。
計數器管理器的腳本——TimerController.cs如下:
-
using UnityEngine;
-
using System.Collections;
-
using System.Collections.Generic;
-
-
public class TimerController : MonoBehaviour {
-
-
public delegate void OnCallBack(object arg);
-
public delegate bool OnIsCanDo(object arg);
-
-
public class Timer {
-
public TimerObserverOrSubject m_Observer;
-
public OnCallBack m_Callback = null;
-
public object m_Arg = null;
-
-
public TimerObserverOrSubject m_Subject;
-
public OnIsCanDo m_IsCanDoFunc = null;
-
public object m_ArgForIsCanDoFunc = null;
-
-
public float m_PassTime = 0;
-
-
public Timer(TimerObserverOrSubject observer, OnCallBack callback, object arg,
-
TimerObserverOrSubject subject, OnIsCanDo isCanDoFunc, object argForIsCanDo) {
-
m_Observer = observer;
-
m_Callback = callback;
-
m_Arg = arg;
-
-
m_Subject = subject;
-
m_IsCanDoFunc = isCanDoFunc;
-
m_ArgForIsCanDoFunc = argForIsCanDo;
-
-
m_PassTime = 0;
-
}
-
-
public Timer(TimerObserverOrSubject observer, OnCallBack callback, object arg, float time) {
-
m_Observer = observer;
-
m_Callback = callback;
-
m_Arg = arg;
-
-
m_Subject = null;
-
m_IsCanDoFunc = null;
-
m_ArgForIsCanDoFunc = null;
-
-
m_PassTime = time;
-
}
-
}
-
private List<Timer> m_Timers = new List<Timer>();
-
private List<Timer> m_NeedRemoveTimer = new List<Timer>();
-
private List<Timer> m_CurRunTimer = new List<Timer>();
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
public void SetTimer(TimerObserverOrSubject observer, OnCallBack callback ,object arg,
-
TimerObserverOrSubject subject, OnIsCanDo isCanDoFunc,object argForIsCanDo) {
-
if (observer == null || subject == null || callback == null || isCanDoFunc == null) return;
-
-
if (isCanDoFunc(argForIsCanDo)) {
-
callback(arg);
-
return;
-
}
-
-
Timer timer = new Timer(observer, callback, arg, subject, isCanDoFunc, argForIsCanDo);
-
m_Timers.Add(timer);
-
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
public void SetTimer(TimerObserverOrSubject observer, OnCallBack callback , object arg, float timepass) {
-
if (observer != null && callback != null) {
-
Timer timer = new Timer(observer, callback, arg, timepass);
-
m_Timers.Add(timer);
-
}
-
}
-
-
-
-
-
-
-
-
public void ClearTimer(TimerObserverOrSubject observer) {
-
List<Timer> needRemovedTimers = new List<Timer>();
-
-
foreach (Timer timer in m_Timers) {
-
if (timer.m_Observer == observer || timer.m_Subject) {
-
needRemovedTimers.Add(timer);
-
}
-
}
-
-
foreach (Timer timer in needRemovedTimers) {
-
m_Timers.Remove(timer);
-
}
-
}
-
-
-
void Update ()
-
{
-
InitialCurTimerDict();
-
RunTimer();
-
RemoveTimer();
-
}
-
-
private void InitialCurTimerDict() {
-
m_CurRunTimer.Clear();
-
-
foreach (Timer timer in m_Timers) {
-
m_CurRunTimer.Add(timer);
-
}
-
}
-
-
private void RunTimer() {
-
m_NeedRemoveTimer.Clear();
-
-
foreach (Timer timer in m_CurRunTimer) {
-
if (timer.m_IsCanDoFunc == null) {
-
timer.m_PassTime = timer.m_PassTime - Time.deltaTime;
-
if (timer.m_PassTime < 0) {
-
timer.m_Callback(timer.m_Arg);
-
m_NeedRemoveTimer.Add(timer);
-
}
-
} else {
-
if (timer.m_IsCanDoFunc(timer.m_ArgForIsCanDoFunc)) {
-
timer.m_Callback(timer.m_Arg);
-
m_NeedRemoveTimer.Add(timer);
-
}
-
}
-
}
-
}
-
-
private void RemoveTimer() {
-
foreach (Timer timer in m_NeedRemoveTimer) {
-
if (m_Timers.Contains(timer)) {
-
m_Timers.Remove(timer);
-
}
-
}
-
}
-
-
}
首先,它定義了回調函數的類型:
-
public delegate void OnCallBack(object arg);
-
public delegate bool OnIsCanDo(object arg);
關於C#的委託機制,如果有童鞋不瞭解,請詳見
官方文檔。簡單來說,委託類似一個函數指針,常被用於回調函數。
然後,定義了一個數據類型Timer用於保存一個計數器的各個信息。
接下來,就是TimerController的兩個重要的SetTimer函數。我們先看第一個SetTimer函數:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
public void SetTimer(TimerObserverOrSubject observer, OnCallBack callback ,object arg,
-
TimerObserverOrSubject subject, OnIsCanDo isCanDoFunc,object argForIsCanDo) {
-
if (observer == null || subject == null || callback == null || isCanDoFunc == null) return;
-
-
if (isCanDoFunc(argForIsCanDo)) {
-
callback(arg);
-
return;
-
}
-
-
Timer timer = new Timer(observer, callback, arg, subject, isCanDoFunc, argForIsCanDo);
-
m_Timers.Add(timer);
-
}
根據函數說明可以看出,它負責建立一個計數器,當subject的isCanDoFunc(argForIsCanDo)函數返回true時,通知observer,執行observer的callback(arg)函數。
第二個SetTimer函數更簡單:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
public void SetTimer(TimerObserverOrSubject observer, OnCallBack callback , object arg, float timepass) {
-
if (observer != null && callback != null) {
-
Timer timer = new Timer(observer, callback, arg, timepass);
-
m_Timers.Add(timer);
-
}
-
}
它負責建立一個計數器,在
timepass的時間後,通知observer,執行observer的callback(arg)函數。
Update()函數裏面負責檢查所有Timer是否可以觸發以及是否需要刪除。
例子
在這個例子裏,我們需要在程序開始運行5秒後,打印一些信息。當然這個的實現有很多方法,這裏我們使用今天實現的TimerController來實現。
TimerSample.cs的內容如下:
-
using UnityEngine;
-
using System.Collections;
-
-
public class TimerSample : TimerObserverOrSubject {
-
-
private TimerController m_TimerCtr = null;
-
-
private bool m_IsCanDisplay = false;
-
-
private string m_DisplayContent = "Hello, candycat!";
-
-
-
void Start () {
-
m_TimerCtr = Singleton.getInstance("TimerController") as TimerController;
-
-
-
-
m_TimerCtr.SetTimer(this, Display, null, this, IsCanDisplay, null);
-
-
StartCoroutine(DelayDisplay());
-
}
-
-
void Display(object arg) {
-
if (arg == null) {
-
Debug.Log(m_DisplayContent);
-
} else {
-
string content = arg as string;
-
-
Debug.Log(content);
-
}
-
}
-
-
bool IsCanDisplay(object arg) {
-
return m_IsCanDisplay;
-
}
-
-
IEnumerator DelayDisplay() {
-
yield return new WaitForSeconds(5.0f);
-
-
m_IsCanDisplay = true;
-
}
-
-
-
void Update () {
-
-
}
-
}
首先,它向
TimerController請求註冊了一個計時器。這裏,它的條件是IsCanDisplay函數,它返回bool值m_IsCanDisplay。而這個值將會在5秒後,通過協同函數DelayDisplay來由false置爲true。當其爲true時,TimerController就將通知TimerSample調用Display函數。
我們將第16行代碼註釋解開,並將18-20行代碼註釋掉,則可以達到相同的效果。
結束語
C#的委託機制還是非常常用的,使用Unity的童鞋最好還是瞭解一下。關於TimerController的執行效率,由於它是每一幀都要去判斷所有的condition函數,所以應當讓condition函數中的邏輯儘可能簡單。
好了,這次就到這裏,如果有更好的想法,或者這裏的代碼有什麼問題,都非常歡迎指正。謝謝閱讀!