unity 委托事件简单应用

[csharp]  view plain  copy
  1. public ObjA objA;  
  2. public ObjB objB;  
  3. public MyUI myUI;  
  4.   
  5. void DoSomething(){  
  6.   objA.do();  
  7.   objB.do();  
  8.   myUI.show("233");  
  9. }  

上面代码所展示的问题呢就是:变量过多,调用复杂,可能会出现调用死循环,维护麻烦,逻辑混乱等等,那么,我们就得想办法解决了。本次主要讲解事件的使用,需要有对于委托和事件的基本认识,如果有不太理解的朋友呢请参考这里

好了,假设我们现在需要设计一款计时器(Timer)的功能,它有一些基础的事件:开始计时、暂停计时、停止计时(以下简称“计时器事件”)。并且要将这个事件告诉给A,那肯定会有人通过声明一个变量A ,然后在计时器事件中进行调用。那现在突然需求改变了,一下子增加了B、C、D、E.....Z。这下怎么办呢? 肯定不能一个个放进去了吧。所以我们就需要有个机制让计时器用大喇叭喊,这样所有的人就能听到了,这样就引入了“事件”这个概念。那么就有以下程序:

1、计时器:

[csharp]  view plain  copy
  1. using UnityEngine;  
  2. using System.Collections;  
  3. /// <summary>  
  4. ///计时器   
  5. /// </summary>  
  6. public class Timer : MonoBehaviour {  
  7.     public delegate void MyEventHandler(float currentTime);  
  8.     #region 计时器的三种基础事件  
  9.   
  10.     public static event MyEventHandler onTimerStart;  
  11.     public static event MyEventHandler onTimerPause;  
  12.     public static event MyEventHandler onTimerStoped;  
  13.  
  14.     #endregion  
  15.     private bool isStarted=false;  
  16.     public bool IsStarted{  
  17.         get{   
  18.             return isStarted;  
  19.         }  
  20.     }  
  21.     private bool isStoped = true;  
  22.     public bool IsStoped{  
  23.         get{   
  24.             return isStoped;  
  25.         }  
  26.     }  
  27.     private float totalTime = 0;  
  28.     // Update is called once per frame  
  29.     void Update () {  
  30.   
  31.         //空格键当作“开始/暂停”键  
  32.         if (Input.GetKeyDown (KeyCode.Space)) {  
  33.             OnChangeState ();  
  34.         }  
  35.         //回车键当作“停止”键  
  36.         if (Input.GetKeyDown (KeyCode.Return)) {  
  37.             OnSetStop ();  
  38.         }  
  39.   
  40.         if (isStarted) {  
  41.             isStoped = false;  
  42.             totalTime += Time.deltaTime;  
  43.         }  
  44.     }  
  45.     void OnChangeState(){  
  46.         var _startState = !isStarted;  
  47.         isStarted = _startState;  
  48.         if (isStarted) {  
  49.             //检查onTimerStart是否为空,防止报空 (废话了。。。下面不做赘述)  
  50.             if (onTimerStart != null) {  
  51.                 onTimerStart (totalTime);  
  52.             } else {  
  53.                 Debug.Log ("onTimerStart is Empty");  
  54.             }  
  55.         } else {  
  56.             if (onTimerPause != null) {  
  57.                 onTimerPause (totalTime);  
  58.             } else {  
  59.                 Debug.Log ("onTimerPause is Empty");  
  60.             }  
  61.         }  
  62.     }  
  63.     void OnSetStop(){  
  64.         if (onTimerStoped != null) {  
  65.             onTimerStoped (totalTime);  
  66.         } else {  
  67.             Debug.Log ("onTimerStoped is Empty");  
  68.         }  
  69.         isStarted = false;  
  70.         isStoped = true;  
  71.         totalTime = 0;  
  72.     }  
  73. }  

然后,我们通过这三行代码就声明了计时器的三种事件啦:

[csharp]  view plain  copy
  1. public static event MyEventHandler onTimerStart;  
  2. public static event MyEventHandler onTimerPause;  
  3. public static event MyEventHandler onTimerStoped;  
但是感觉还是不习惯哈,不要急,继续往下看~


注意这里:

[csharp]  view plain  copy
  1. var _startState = !isStarted;  
  2. isStarted = _startState;  
我为什么要这样进行赋值呢? 有人可能会说我多此一举,但是我想解释下的是,编程中切记不要编聪明的程序,可能你在编写的时候很容易很简单,但是等你开始维护你的项目的时候,你会发现非常棘手,你自己可能都看不懂你代码是什么意思。

2、监听者:

[csharp]  view plain  copy
  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class Listener : MonoBehaviour {  
  5.       
  6.     /// <summary>  
  7.     /// 注册事件监听  
  8.     /// </summary>  
  9.     void OnEnable () {  
  10.         Timer.onTimerPause += new Timer.MyEventHandler (OnTimerPause);  
  11.     }  
  12.     /// <summary>  
  13.     ///考虑到在有的需求里,某个脚本或者GameObject会重复启用禁用多次,  
  14.     /// 故在其禁用的时候取消事件的注册,以免此方法被重复调用   
  15.     /// </summary>  
  16.     void OnDisable(){  
  17.         Timer.onTimerPause -= new Timer.MyEventHandler (OnTimerPause);  
  18.     }  
  19.     public void OnTimerPause(float currentTime){  
  20.         Debug.Log ("[计时器暂停]当前计时:" + currentTime);  
  21.     }  
  22. }  
到这里思路就已经很清晰明了,Timer作为事件的发送者,Listener为其监听者,依赖系统的功能广播它的事件并调用“监听”了这些事件的方法,这样做就避免了上面提到问题,让程序的耦合性降低了不少。

但是这样还仅仅只是事件在项目的其中一种使用方式,大家也看出来了,感觉使用起来好像特别麻烦啊,每次使用一个事件都要先判断事件为不为空,使用者还要使用“+=、-=”这样的操作符,感觉理解起来好像也很复杂的样子啊。

下面我们就要针对这个问题进行进一步优化啦,还没缓过神来的朋友们先喝杯茶缓缓哈。

好,下面我们就要先解决下使用和理解复杂这个问题啦,所以我们先做点准备。

[csharp]  view plain  copy
  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. /// <summary>  
  5. ///存放接口、委托、结构体、枚举 等  
  6. /// </summary>  
  7. namespace MyResources{  
  8.     /// <summary>  
  9.     /// 基础的委托  
  10.     /// </summary>  
  11.     public delegate void BaseEventHandler(float T);  
  12.     /// <summary>  
  13.     /// 基础的事件接口  
  14.     /// </summary>  
  15.     public interface IBaseEvent{  
  16.         void AddListener (BaseEventHandler call);  
  17.         void RemoveListener (BaseEventHandler call);  
  18.         void TriggerEvent (float T);  
  19.     }  
  20.     /// <summary>  
  21.     ///计时器的事件结构体   
  22.     /// </summary>  
  23.     public struct TimerEvent:IBaseEvent{  
  24.         private BaseEventHandler timerEventHandler;  
  25.         /// <summary>  
  26.         /// 注册此事件的监听  
  27.         /// </summary>  
  28.         /// <param name="call">回调函数.</param>  
  29.         public void AddListener(BaseEventHandler call){  
  30.             timerEventHandler += call;  
  31.         }  
  32.         /// <summary>  
  33.         /// 移除此事件的监听  
  34.         /// </summary>  
  35.         /// <param name="call">回调函数.</param>  
  36.         public void RemoveListener(BaseEventHandler call){  
  37.             timerEventHandler -= call;  
  38.         }  
  39.         /// <summary>  
  40.         ///触发此事件  
  41.         /// </summary>  
  42.         public void TriggerEvent(float T){  
  43.             if (timerEventHandler != null) {  
  44.                 timerEventHandler (T);  
  45.             } else {  
  46.                 Debug.Log ("timerEventHandler is empty");  
  47.             }  
  48.         }  
  49.     }  
  50.     /// <summary>  
  51.     /// 高级的事件结构体  
  52.     /// 增加了注册“一次性”回调的入口  
  53.     /// </summary>  
  54.     public struct AdvanceEvent:IBaseEvent{  
  55.         private BaseEventHandler timerEventHandler;  
  56.         private BaseEventHandler removebleHandler;  
  57.         public void AddListener(BaseEventHandler call){  
  58.             timerEventHandler += call;  
  59.         }  
  60.         public void AddListener(BaseEventHandler call,CallbackOption option){  
  61.               
  62.             switch (option) {  
  63.             case CallbackOption.EvryTime:  
  64.                 timerEventHandler += call;  
  65.                 break;  
  66.             case CallbackOption.Once:  
  67.                 timerEventHandler += call;  
  68.                 removebleHandler += call;  
  69.                 break;  
  70.             }  
  71.         }  
  72.         public void RemoveListener(BaseEventHandler call){  
  73.             timerEventHandler -= call;  
  74.         }  
  75.   
  76.         public void TriggerEvent(float T){  
  77.             if (timerEventHandler != null) {  
  78.                 timerEventHandler (T);  
  79.                 timerEventHandler -= removebleHandler;  
  80.                 removebleHandler = null;  
  81.             } else {  
  82.                 Debug.Log ("timerEventHandler is empty");  
  83.             }  
  84.         }  
  85.     }  
  86.   
  87.     public enum CallbackOption{  
  88.         EvryTime,Once  
  89.     }  
  90. }  

准备完啦,上面呢就是声明了一个事件所需要的委托,一个事件接口,和计时器基本的事件的结构体啦。之所以要声明接口再让计时器事件结构体继承它呢就是为了规范所有包含事件的结构体的基本框架啦,这下不会有人吐槽说:啊呀,为什么我感觉我的项目里从来没用过接口呢。。。  这下不就用上了嘛~

我猜你们肯定也注意到了怎么感觉这个接口有点熟悉呢? 是不是在哪里见过呢?  对! 没错!  它和 UnityEvent还是比较像的,大家可能经常会在使用Button组件的时候用到这个方法,长得都差不多啦,是不是感觉很好用? 

[csharp]  view plain  copy
  1. Button btn;  
  2. btn.OnClick.AddListener(Do);  
我们在TimerEvent中也声明了AddListener(),用以更简洁地注册事件。其原理和上面那个是一样的,只是进行了下封装。相同的,在触发事件上也进行了封装,并检测了是否有报空指针的问题,所以在之后使用中直接调用TriggerEvent()方法也更加放心啦,并且还有一个好处就是隐藏了其中的委托,也更加安全了。

好,准备工作做好了,我们现在把Timer的程序改改:

[csharp]  view plain  copy
  1. using UnityEngine;  
  2. using System.Collections;  
  3. using MyResources;  
  4.   
  5. public class NewTimer : MonoBehaviour {  
  6.     public delegate void MyEventHandler(float currentTime);  
  7.     #region 计时器的三种基础事件  
  8.   
  9.     public static TimerEvent OnTimerStart;  
  10.     public static TimerEvent OnTimerPause;  
  11.     public static TimerEvent OnTimerStop;  
  12.     #endregion  
  13.     private bool isStarted=false;  
  14.     public bool IsStarted{  
  15.         get{   
  16.             return isStarted;  
  17.         }  
  18.     }  
  19.     private bool isStoped = true;  
  20.     public bool IsStoped{  
  21.         get{   
  22.             return isStoped;  
  23.         }  
  24.     }  
  25.     private float totalTime = 0;  
  26.     // Update is called once per frame  
  27.     void Update () {  
  28.   
  29.         //空格键当作“开始/暂停”键  
  30.         if (Input.GetKeyDown (KeyCode.Space)) {  
  31.             OnChangeState ();  
  32.         }  
  33.         //回车键当作“停止”键  
  34.         if (Input.GetKeyDown (KeyCode.Return)) {  
  35.             OnSetStop ();  
  36.         }  
  37.   
  38.         if (isStarted) {  
  39.             isStoped = false;  
  40.             totalTime += Time.deltaTime;  
  41.         }  
  42.     }  
  43.     void OnChangeState(){  
  44.         var _startState = !isStarted;  
  45.         isStarted = _startState;  
  46.         if (isStarted) {  
  47.             OnTimerStart.TriggerEvent (totalTime);  
  48.         } else {  
  49.             OnTimerPause.TriggerEvent (totalTime);  
  50.         }  
  51.     }  
  52.     void OnSetStop(){  
  53.         OnTimerStop.TriggerEvent (totalTime);  
  54.         isStarted = false;  
  55.         isStoped = true;  
  56.         totalTime = 0;  
  57.     }  
  58. }  

这样我们就可以直接声明计时器的事件啦,是不是一看TimerEvent就知道是啥啦:

[csharp]  view plain  copy
  1. public static TimerEvent OnTimerStart;  
  2. public static TimerEvent OnTimerPause;  
  3. public static TimerEvent OnTimerStop;  

是不是超级简单~

然后在事件的监听上呢直接AddListener()就好了,触发事件也只需要TriggerEvent()就好了,妈妈再也不用担心我报空指针啦~


接下来就是更改监听器的程序了,简直不能再简单了。

[csharp]  view plain  copy
  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class NewListener : MonoBehaviour {  
  5.   
  6.     // Use this for initialization  
  7.     void OnEnable () {  
  8.         NewTimer.OnTimerPause.AddListener (OnNewTimerPause);  
  9.     }  
  10.     void OnDisable(){  
  11.         NewTimer.OnTimerPause.RemoveListener (OnNewTimerPause);  
  12.     }  
  13.     void OnNewTimerPause (float currentTime) {  
  14.         Debug.Log ("[计时器暂停]当前计时:" + currentTime);  
  15.     }  
  16. }  
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值