zoukankan      html  css  js  c++  java
  • 【Unity技巧】统一管理回调函数——观察者模式

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


    开场白


    我们来想象一个场景。在加载一个模型时,你需要从网上下载,但是你并不知道下载需要花费多少时间。你所知道的是,当下载完成后,就可以把模型放在特定位置上,开始游戏。那么,我们怎样才能判断下载完成呢?
    一个简单的方法是,在每一帧的时候都判断下载是否完成,完成后就可以继续后面的工作。因此,我们可以这样做,我们告诉一个管理器,嗨,你帮我盯着点,看下载完了没有,完了就叫我一声,好让我执行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行代码注释掉,则可以达到相同的效果。


    结束语


    C#的委托机制还是非常常用的,使用Unity的童鞋最好还是了解一下。关于TimerController的执行效率,由于它是每一帧都要去判断所有的condition函数,所以应当让condition函数中的逻辑尽可能简单。

    好了,这次就到这里,如果有更好的想法,或者这里的代码有什么问题,都非常欢迎指正。谢谢阅读!
  • 相关阅读:
    Java课程设计---实现登录(2)
    Java课程设计---项目数据库设计(含实体类)
    Java课程设计---学生信息管理系统需求分析及总体设计
    Java课程设计---索引
    【软件测试】基础-概念篇
    【计算机网络】定义、作用、特点计算机网络
    【计算机网络】趣谈网络协议-测试习题
    【Java】Java注释
    【Java】一个简单的Java应用程序
    【Java】Java关键字、含义
  • 原文地址:https://www.cnblogs.com/xiaowangba/p/6314721.html
Copyright © 2011-2022 走看看