博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【Unity技巧】统一管理回调函数——观察者模式
阅读量:7081 次
发布时间:2019-06-28

本文共 7812 字,大约阅读时间需要 26 分钟。

这次的内容有点类似设计模式里的观察者模式。但是和常规意义上的观察者模式也不是完全一致,所以各位就不要咬文嚼字啦!咦?设计模式?!不懂!没关系,说不定你以前就用过。

开场白

我们来想象一个场景。在加载一个模型时,你需要从网上下载,但是你并不知道下载需要花费多少时间。你所知道的是,当下载完成后,就可以把模型放在特定位置上,开始游戏。那么,我们怎样才能判断下载完成呢?
一个简单的方法是,在每一帧的时候都判断下载是否完成,完成后就可以继续后面的工作。因此,我们可以这样做,我们告诉一个管理器,嗨,你帮我盯着点,看下载完了没有,完了就叫我一声,好让我执行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
m_Timers = new List
(); private List
m_NeedRemoveTimer = new List
(); private List
m_CurRunTimer = new List
(); ///
/// Sets the timer. /// ///
/// The TimerObserverOrSubject you need to listen /// ///
/// The callback when condition is true. /// ///
/// Argument of the callback. /// ///
/// The TimerObserverOrSubject you need to observe /// ///
/// The condition function, must return a boolean. /// ///
/// Argument for condition function. /// 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); } ///
/// Sets the timer. /// ///
/// The TimerObserverOrSubject you need to listen /// ///
/// The callback when time is up. /// ///
/// Argument of the callback. /// ///
/// Timepass before calling the callback. /// 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); } } ///
/// Clears all Timers of the observer. /// ///
/// The TimerObserverOrSubject you need to clear /// public void ClearTimer(TimerObserverOrSubject observer) { List
needRemovedTimers = new List
(); 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函数:
/// 	/// Sets the timer.	/// 	/// 	/// The observer to observe the subject	/// 	/// 	/// The callback when condition is true.	/// 	/// 	/// Argument of the callback.	/// 	/// 	/// The subject you need to observe	/// 	/// 	/// The condition function, must return a boolean.	/// 	/// 	/// Argument for condition function.	/// 	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函数更简单:
/// 	/// Sets the timer.	/// 	/// 	/// The observer to observe the subject	/// 	/// 	/// The callback when time is up.	/// 	/// 	/// Argument of the callback.	/// 	/// 	/// Timepass before calling the callback.	/// 	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行代码注释掉,则可以达到相同的效果。

结束语

C#的委托机制还是非常常用的,使用Unity的童鞋最好还是了解一下。关于TimerController的执行效率,由于它是每一帧都要去判断所有的condition函数,所以应当让condition函数中的逻辑尽可能简单。
好了,这次就到这里,如果有更好的想法,或者这里的代码有什么问题,都非常欢迎指正。谢谢阅读!

转载于:https://www.cnblogs.com/xiaowangba/p/6314721.html

你可能感兴趣的文章
【LeetCode】Single Number
查看>>
iframe 无刷新上传图片以及返回值
查看>>
PHP_crack_check
查看>>
servlet提供下载图片服务
查看>>
shiro 在方法上添加注解配置
查看>>
NAT网络穿透
查看>>
SEO的艺术(原书第2版)
查看>>
ResouceBundle读取properties文件
查看>>
Spotlight-like Hint View
查看>>
NDRotator
查看>>
ubuntu安装pycharm,以及创建桌面快捷方式
查看>>
像写SQL一样编写Java数据应用-TinySqlDsl
查看>>
FreeCMS视频教程 用户管理
查看>>
关于header的那些事——header location重定向语句对页面进行跳转失败的原因
查看>>
汉语智能分词
查看>>
Se(2)---对浏览器操作
查看>>
mycncart 之网银在线chinapay支付方式
查看>>
Flink 读取Kafka写入Hive
查看>>
Junit 源码解析(一)
查看>>
pycurl mac 安装报错Curl is configured to use SSL,
查看>>