丝路设计培训学校-专注CG行业人才培养

  • 24小时免费客服热线
  • 025-6965-8961

当前位置:丝路教育>设计教程>游戏美术>unity>正文

Unity3d教程:订阅者模式实现事件机制

2018-12-12 16:01 来源: 丝路设计培训 分类: unity
145 作者:丝路设计培训

[导读] 我们知道一个模块的实例可以通过Unity3D中的GetComponent获得,然后引用这个实例来完成相关任务的调用。显然这种方法,然而,我们携带现金来应对不同的人,每笔交易,我们需要考虑所有现

  我们知道一个模块的实例可以通过Unity3D中的GetComponent获得,然后引用这个实例来完成相关任务的调用。显然这种方法,然而,我们携带现金来应对不同的人,每笔交易,我们需要考虑所有现金投入和支出的问题,从两个方面考虑安全性和耦合,这种方法在面对复杂的系统设计,很容易导致模块间的相互依赖,这可以增加不同模块之间的耦合。为了解决这个问题,人们开始考虑单例模式,因为单例模式可以保证全局中唯一的实例,这样可以有效的减少模块之间的直接引用。单例模型就像我们在银行有一个唯一的账户,所以我们只需要在交易时控制通过这个账户的资金流动。单例模式确保了每个模块的独立性,但是单例模式更多的是一种主动的行为,也就是说,我们在需要的时候主动调用这个模块。单例模式的问题是它不能解决调用方的反馈问题,除非调用方主动调用调用方的模块实例。在这一点上,我们似乎看到了一种新的模式,这就是我们接下来要提到的事件机制。

  订阅者模式和事件机制

  首先这里要提到一种称为“订阅者模式”的设计模式,这种设计模式在《大话设计模式》这本书中称为“观察者模式”或者“发布-订阅(Publish/Subscribe)模式”,我们这里暂且叫做“订阅者模式”吧!该模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个对象在状态发生变化时会通知所有观察者对象,使它们能够自动更新自己。针对这个模式,我们可以考虑事件机制的实现,事件机制可以理解为在一个事件中心(Subject)保存有对所有事件(Observer)的引用,事件中心负责对这些事件进行分发,这样每个事件就可以通过回调函数的方式进行更新,这样就实现了一个事件机制。下面给出基本的代码实现:

  using System;

  using System.Collections;

  using System.Collections.Generic;

  using UnityEngine;

  namespace UniEventDispatcher

  {

  ///

  /// 定义事件分发委托

  ///

  public delegate void OnNotification(Notification notific);

  ///

  ///通知中心

  ///

  public class NotificationCenter

  {

  ///

  /// 通知中心单例

  ///

  private static NotificationCenter instance=null;

  public static NotificationCenter Get()

  {

  if(instance == null){

  instance = new NotificationCenter();

  return instance;

  }

  return instance;

  }

  ///

  /// 存储事件的字典

  ///

  private Dictionary eventListeners

  = new Dictionary();

  ///

  /// 注册事件

  ///

  /// 事件Key

  /// 事件监听器

  public void AddEventListener(string eventKey,OnNotification eventListener)

  {

  if(!eventListeners.ContainsKey(eventKey)){

  eventListeners.Add(eventKey,eventListener);

  }

  }

  ///

  /// 移除事件

  ///

  /// 事件Key

  public void RemoveEventListener(string eventKey)

  {

  if(!eventListeners.ContainsKey(eventKey))

  return;

  eventListeners[eventKey] =null;

  eventListeners.Remove(eventKey);

  }

  ///

  /// 分发事件

  ///

  /// 事件Key

  /// 通知

  public void DispatchEvent(string eventKey,Notification notific)

  {

  if (!eventListeners.ContainsKey(eventKey))

  return;

  eventListeners[eventKey](notific);

  }

  ///

  /// 分发事件

  ///

  /// 事件Key

  /// 发送者

  /// 通知内容

  public void DispatchEvent(string eventKey, GameObject sender, object param)

  {

  if(!eventListeners.ContainsKey(eventKey))

  return;

  eventListeners[eventKey](new Notification(sender,param));

  }

  ///

  /// 分发事件

  ///

  /// 事件Key

  /// 通知内容

  public void DispatchEvent(string eventKey,object param)

  {

  if(!eventListeners.ContainsKey(eventKey))

  return;

  eventListeners[eventKey](new Notification(param));

  }

  ///

  /// 是否存在指定事件的监听器

  ///

  public Boolean HasEventListener(string eventKey)

  {

  return eventListeners.ContainsKey(eventKey);

  }

  }

  }

  注意到在这个“通知中心”中,我们首先实现了单例模式,这样我们可以通过Get方法来获取该“通知中心”的唯一实例,其次这里利用一个字典来存储对所有事件的引用,这样保证外部可以通过AddEventListener和RemoveEventListener这两个方法来进行事件的添加和移除,对于添加的事件引用我们可以通过DispatchEvent方法来分发一个事件,事件的回调函数采用委托来实现,注意到这个委托需要一个Notification类型,对该类型简单定义如下:

  using System;

  using UnityEngine;

  namespace UniEventDispatcher

  {

  public class Notification

  {

  ///

  /// 通知发送者

  ///

  public GameObject sender;

  ///

  /// 通知内容

  /// 备注:在发送消息时需要装箱、解析消息时需要拆箱

  /// 所以这是一个糟糕的设计,需要注意。

  ///

  public object param;

  ///

  /// 构造函数

  ///

  /// 通知发送者

  /// 通知内容

  public Notification(GameObject sender, object param)

  {

  this.sender = sender;

  this.param = param;

  }

  ///

  /// 构造函数

  ///

  ///

  public Notification(object param)

  {

  this.sender = null;

  this.param = param;

  }

  ///

  /// 实现ToString方法

  ///

  ///

  public override string ToString()

  {

  return string.Format("sender={0},param={1}", this.sender, this.param);

  }

  }

  }

  对Notification的定义需要提供发送者和发送内容,这样可以保证所有的通知都按照这样的格式进行定义,如果有Socket开发经验的朋友可能会联想到通讯协议的定义,这里是比较相似啦,哈哈!

  使用事件机制的一个示例

  这里以一个简单的示例来验证事件机制的可行性,我们在场景中有一个球体,默认这个球体的颜色为白色,通过调整界面中的RGB数值,可以改变球体的颜色,在这个示例中UI是事件发送者,负责UI中Slider控件的数值发生变化时向球体发送消息,传递的数据类型是Color类型;球体为事件接收者,负责注册事件及接收到消息后的处理。因为代码较为简单,所以这里写在一个脚本中:

  using UnityEngine;

  using UnityEngine.UI;

  using System.Collections;

  using UniEventDispatcher;

  public class Example : MonoBehaviour

  {

  ///

  /// R数值的Slider

  ///

  private Slider sliderR;

  ///

  /// G数值的Slider

  ///

  private Slider sliderG;

  ///

  /// B数值的Slider

  ///

  private Slider sliderB;

  void Start ()

  {

  //在接收者中注册事件及其回调方法

  NotificationCenter.Get().AddEventListener("ChangeColor", ChangeColor);

  //在发送者中分发事件,这里以UI逻辑为例

  sliderR = GameObject.Find("Canvas/SliderR").GetComponent();

  sliderG = GameObject.Find("Canvas/SliderG").GetComponent();

  sliderB = GameObject.Find("Canvas/SliderB").GetComponent();

  //注册UI事件

  sliderR.onValueChanged.AddListener(OnValueChanged);

  sliderG.onValueChanged.AddListener(OnValueChanged);

  sliderB.onValueChanged.AddListener(OnValueChanged);

  }

  public void OnValueChanged(float value)

  {

  //获得RGB数值

  float r = sliderR.value;

  float g = sliderG.value;

  float b = sliderB.value;

  //分发事件,注意和接收者协议一致

  NotificationCenter.Get().DispatchEvent("ChangeColor", new Color(r, g, b));

  }

  ///

  /// 改变物体材质颜色

  ///

  ///

  public void ChangeColor(Notification notific)

  {

  Debug.Log(notific.ToString());

  //设置颜色

  renderer.material.color = (Color)notific.param;

  }

  }

  该示例运行效果如下:

Unity3d教程,订阅者模式实现事件机制

  订阅者模式实现事件机制小结

  虽然目前这个事件机制在实现和使用上没有什么问题,可是从扩展性和可优化性上来考虑,这个设计目前存在以下问题:

  字符型的键名使用起来方便,可是对通知者和接收者由1个以上的人力来维护的时候双方需要通过沟通来确定键名,可以考虑使用GameObject或者Transform来替代现在的键名设计,可是这种设计带来的新问题是会增加不同模块间的GameObject或者Transform的相互引用。

  在传递参数和接收参数时,通知方和接收方需要分别对参数进行装箱和解压,这不是一个好的设计,双方需要确保传递的参数类型是相同的。解决方案是为不同类型派生通知中心,或者考虑对通知中心提供通用约束,以使通知中的通知内容具有针对性,从而解决当前装箱和拆箱需求造成的性能问题。以下是今天的Unity3d教程的结尾,该教程介绍如何基于订阅模式实现事件机制。


版权声明:

本文由丝路设计培训学校的编辑人员所撰写,未经同意严禁转载,否则将严肃追究相关法律责任。文章原始链接:http://silucg.net/yxdhjc/unity/1205.html

unity相关文章 您可能感兴趣