我们来想象一个场景。在加载一个模型时,你需要从网上下载,但是你并不知道下载需要花费多少时间。你所知道的是,当下载完成后,就可以把模型放在特定位置上,开始游戏。那么,我们怎样才能判断下载完成呢?

一个简单的方法是,在每一帧的时候都判断下载是否完成,完成后就可以继续后面的工作。因此,我们可以这样做,我们告诉一个管理器,嗨,你帮我盯着点,看下载完了没有,完了就叫我一声,好让我执行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>();


/// <summary>

/// Sets the timer.

/// </summary>

/// <param name=’observer’>

/// The TimerObserverOrSubject you need to listen

/// </param>

/// <param name=’callback’>

/// The callback when condition is true.

/// </param>

/// <param name=’arg’>

/// Argument of the callback.

/// </param>

/// <param name=’observer’>

/// The TimerObserverOrSubject you need to observe

/// </param>

/// <param name=’isCanDoFunc’>

/// The condition function, must return a boolean.

/// </param>

/// <param name=’argForIsCanDo’>

/// Argument for condition function.

/// </param>

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);

}


/// <summary>

/// Sets the timer.

/// </summary>

/// <param name=’observer’>

/// The TimerObserverOrSubject you need to listen

/// </param>

/// <param name=’callback’>

/// The callback when time is up.

/// </param>

/// <param name=’arg’>

/// Argument of the callback.

/// </param>

/// <param name=’timepass’>

/// Timepass before calling the callback.

/// </param>

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);

}

/// <summary>

/// Clears all Timers of the observer.

/// </summary>

/// <param name=’observer’>

/// The TimerObserverOrSubject you need to clear

/// </param>

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);

}

}

// Update is called once per frame

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函数:

/// <summary>

/// Sets the timer.

/// </summary>

/// <param name=’observer’>

/// The observer to observe the subject

/// </param>

/// <param name=’callback’>

/// The callback when condition is true.

/// </param>

/// <param name=’arg’>

/// Argument of the callback.

/// </param>

/// <param name=’subject’>

/// The subject you need to observe

/// </param>

/// <param name=’isCanDoFunc’>

/// The condition function, must return a boolean.

/// </param>

/// <param name=’argForIsCanDo’>

/// Argument for condition function.

/// </param>

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函数更简单:

/// <summary>

/// Sets the timer.

/// </summary>

/// <param name=’observer’>

/// The observer to observe the subject

/// </param>

/// <param name=’callback’>

/// The callback when time is up.

/// </param>

/// <param name=’arg’>

/// Argument of the callback.

/// </param>

/// <param name=’timepass’>

/// Timepass before calling the callback.

/// </param>

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!”;


// Use this for initialization

void Start () {

m_TimerCtr = Singleton.getInstance(“TimerController”) as TimerController;


//m_TimerCtr.SetTimer(this, Display, m_DisplayContent, 5);


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;

}

// Update is called once per frame

void Update () {


首先,它向TimerController请求 注册了一个计时器。这里,它的条件是IsCanDisplay函数,它返回bool值m_IsCanDisplay。而这个值将会在5秒后,通过协同函数 DelayDisplay来由false置为true。当其为true时,TimerController就将通知TimerSample调用 Display函数。



我们将第16行代码注释解开,并将18-20行代码注释掉,则可以达到相同的效果。