Unity中对于委托、事件的应用

来源:互联网 发布:转子发动机 知乎 编辑:程序博客网 时间:2024/06/04 18:18

[引言]

这边文章主要是针对刚踏入这行的程序员,是我工作半年多来积累下来的一些经验,仅作为参考,也希望各位经验丰富的前辈多批评指正。 我是专科生毕业,我们学校所教的C#的内容其实只有非常非常基础的那些部分,根本无法满足大型项目的需求,更别提代码的健壮性稳定性和易维护性了。

遥想当年用Unity做了个局域网坦克对战游戏,简直各种乱七八糟的调用到处都是,就像这样:

public ObjA objA;public ObjB objB;public MyUI myUI;void DoSomething(){  objA.do();  objB.do();  myUI.show("233");}


然后在求职之前又另外做了个项目,期间在论坛博客到处转悠,学到了点委托事件的内容,之后找到工作后用起来也是相当顺手的,并且我们公司BOSS偶尔也会做点软件设计的培训会议,给大家讲讲软件设计的相关内容,然后我才猛然发现我所用的设计模式原来叫做“观察者模式”。

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

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

1、计时器:

using UnityEngine;using System.Collections;/// <summary>///计时器 /// </summary>public class Timer : MonoBehaviour {public delegate void MyEventHandler(float currentTime);#region 计时器的三种基础事件public static event MyEventHandler onTimerStart;public static event MyEventHandler onTimerPause;public static event MyEventHandler onTimerStoped;#endregionprivate bool isStarted=false;public bool IsStarted{get{ return isStarted;}}private bool isStoped = true;public bool IsStoped{get{ return isStoped;}}private float totalTime = 0;// Update is called once per framevoid Update () {//空格键当作“开始/暂停”键if (Input.GetKeyDown (KeyCode.Space)) {OnChangeState ();}//回车键当作“停止”键if (Input.GetKeyDown (KeyCode.Return)) {OnSetStop ();}if (isStarted) {isStoped = false;totalTime += Time.deltaTime;}}void OnChangeState(){var _startState = !isStarted;isStarted = _startState;if (isStarted) {//检查onTimerStart是否为空,防止报空 (废话了。。。下面不做赘述)if (onTimerStart != null) {onTimerStart (totalTime);} else {Debug.Log ("onTimerStart is Empty");}} else {if (onTimerPause != null) {onTimerPause (totalTime);} else {Debug.Log ("onTimerPause is Empty");}}}void OnSetStop(){if (onTimerStoped != null) {onTimerStoped (totalTime);} else {Debug.Log ("onTimerStoped is Empty");}isStarted = false;isStoped = true;totalTime = 0;}}

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

public static event MyEventHandler onTimerStart;public static event MyEventHandler onTimerPause;public static event MyEventHandler onTimerStoped;
但是感觉还是不习惯哈,不要急,继续往下看~


注意这里:

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

2、监听者:

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

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

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

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

using UnityEngine;using System.Collections;/// <summary>///存放接口、委托、结构体、枚举 等/// </summary>namespace MyResources{/// <summary>/// 基础的委托/// </summary>public delegate void BaseEventHandler(float T);/// <summary>/// 基础的事件接口/// </summary>public interface IBaseEvent{void AddListener (BaseEventHandler call);void RemoveListener (BaseEventHandler call);void TriggerEvent (float T);}/// <summary>///计时器的事件结构体 /// </summary>public struct TimerEvent:IBaseEvent{private BaseEventHandler timerEventHandler;/// <summary>/// 注册此事件的监听/// </summary>/// <param name="call">回调函数.</param>public void AddListener(BaseEventHandler call){timerEventHandler += call;}/// <summary>/// 移除此事件的监听/// </summary>/// <param name="call">回调函数.</param>public void RemoveListener(BaseEventHandler call){timerEventHandler -= call;}/// <summary>///触发此事件/// </summary>public void TriggerEvent(float T){if (timerEventHandler != null) {timerEventHandler (T);} else {Debug.Log ("timerEventHandler is empty");}}}/// <summary>/// 高级的事件结构体/// 增加了注册“一次性”回调的入口/// </summary>public struct AdvanceEvent:IBaseEvent{private BaseEventHandler timerEventHandler;private BaseEventHandler removebleHandler;public void AddListener(BaseEventHandler call){timerEventHandler += call;}public void AddListener(BaseEventHandler call,CallbackOption option){switch (option) {case CallbackOption.EvryTime:timerEventHandler += call;break;case CallbackOption.Once:timerEventHandler += call;removebleHandler += call;break;}}public void RemoveListener(BaseEventHandler call){timerEventHandler -= call;}public void TriggerEvent(float T){if (timerEventHandler != null) {timerEventHandler (T);timerEventHandler -= removebleHandler;removebleHandler = null;} else {Debug.Log ("timerEventHandler is empty");}}}public enum CallbackOption{EvryTime,Once}}

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

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

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

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

using UnityEngine;using System.Collections;using MyResources;public class NewTimer : MonoBehaviour {public delegate void MyEventHandler(float currentTime);#region 计时器的三种基础事件public static TimerEvent OnTimerStart;public static TimerEvent OnTimerPause;public static TimerEvent OnTimerStop;#endregionprivate bool isStarted=false;public bool IsStarted{get{ return isStarted;}}private bool isStoped = true;public bool IsStoped{get{ return isStoped;}}private float totalTime = 0;// Update is called once per framevoid Update () {//空格键当作“开始/暂停”键if (Input.GetKeyDown (KeyCode.Space)) {OnChangeState ();}//回车键当作“停止”键if (Input.GetKeyDown (KeyCode.Return)) {OnSetStop ();}if (isStarted) {isStoped = false;totalTime += Time.deltaTime;}}void OnChangeState(){var _startState = !isStarted;isStarted = _startState;if (isStarted) {OnTimerStart.TriggerEvent (totalTime);} else {OnTimerPause.TriggerEvent (totalTime);}}void OnSetStop(){OnTimerStop.TriggerEvent (totalTime);isStarted = false;isStoped = true;totalTime = 0;}}

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

public static TimerEvent OnTimerStart;public static TimerEvent OnTimerPause;public static TimerEvent OnTimerStop;

是不是超级简单~

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


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

using UnityEngine;using System.Collections;public class NewListener : MonoBehaviour {// Use this for initializationvoid OnEnable () {NewTimer.OnTimerPause.AddListener (OnNewTimerPause);}void OnDisable(){NewTimer.OnTimerPause.RemoveListener (OnNewTimerPause);}void OnNewTimerPause (float currentTime) {Debug.Log ("[计时器暂停]当前计时:" + currentTime);}}

是不是非常非常清晰明了啊~



通过以上简单的讲解想必大家对于事件的使用什么的有一定理解了吧,如果有哪些不清晰或者有错误的地方还欢迎各位批评指正哦,第一次发博客,忘各位海涵啦~




1 0