打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
在Unity3d中实现观察者模式
    有2种方法,第一种,封装U3D中的发消息函数,自己写一个事件机制出来。第2种,用C#内置的事件机制。

第一种方法,下面这个类是Digital-tutors出的Unity Mobile GameDevelopment这一套教程里06.Communication with NotificationCenter里的事件机制类,他是用JS写的,我把这个脚本翻译成了C#的。并加了详细的中文注释。本质是一个观察者模式的实现,该类是一个单例的类,用哈希表来保存场景中的所有消息,哈希表中每个键值对,表示的是【某一消息(函数名)——该消息的所有观察者线性表】。最终以u3dapi的SendMessage函数将消息发了过去,所以只能传递一个数据实参,受SendMessage函数本身限制。但是你传的参数可以是脚本对象,把参数写到对象中,这样就可以传多个参数了。

=======================NotificationCenter.cs======================================================
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

// 2013年8月23日11:19:44,郭志程

///
/// 信息中心类,用来处理GameObjects相互发消息。本质是观察者模式。
/// 通过AddObserver函数注册观察者,RemoveObserver注销观察者。
/// 内部通过哈希表对场景中所有的消息进行管理
///
public class NotificationCenter : MonoBehaviour {
   
    privatestatic NotificationCenter defaultCenter = null;
    ///
    /// 单例模式,在场景中自动造一个挂有NotificationCenter脚本的Default NotificationCenter游戏物体,如果手动创建了一个则不再创建
    ///   
    publicstatic NotificationCenter DefaultCenter (){       
       if (null == defaultCenter){           
           GameObject notificationObject = new GameObject("DefaultNotificationCenter");           
           defaultCenter = notificationObject.AddComponent();
        
       return defaultCenter;
    }

    //哈希表包含了所有的发送的信息。其中每个键值对,表示的是【某一消息——该消息的所有观察者线性表】
    privateHashtable notifications = null;
    void Awake(){
       this.notifications = new Hashtable();
    }
    
    ///
    ///注册观察者
    ///   
    public voidAddObserver(Component observer, string name) {AddObserver(observer, name, null); }
    public voidAddObserver(Component observer, string name, Component sender){
       // 对观察者的名字进行检查
       if (name == null || name == "") {Debug.Log("在AddObserver函数中指定的是空名称!."); return; }
       // 哈希表中的值是List,new list
       if (null == this.notifications[name]) {
           this.notifications[name] = new List();
       }
       // 该条消息的所有观察者,通过List将他拉出来对其操作
       List notifyList = this.notifications[name] asList;  
       // 将观察者加入到哈希表中值LIST中去,也就是注册上了
       if (!notifyList.Contains(observer)) { notifyList.Add(observer);}
    }

    ///
    ///注销观察者
    /// 
    public voidRemoveObserver(Component observer, string name) {
       // 该条消息的所有观察者,通过List将他拉出来对其操作
       List notifyList = this.notifications[name] asList;  
       
       if (null != notifyList) {
           // 删除这个已注册的观察者
           if (notifyList.Contains(observer)) { notifyList.Remove(observer);}
           // 如果这个消息没有观察者,则在哈希表中删除这个消息关键字
           if (notifyList.Count == 0) { this.notifications.Remove(name);}
       }
    }
   
    ///
   ///  事件源把发消息出去
    ///   
    public voidPostNotification(Component aSender, string aName) {PostNotification(aSender, aName, null); }
    public voidPostNotification(Component aSender, string aName, object aData) {PostNotification(new Notification(aSender, aName, aData)); }
    public voidPostNotification(Notification aNotification) {
      
       if (aNotification.name == null || aNotification.name == "") {Debug.Log("Null name sent to PostNotification."); return; }
       // 该条消息的所有观察者,通过List将他拉出来对其操作
       List notifyList = this.notifications[aNotification.name] asList;
       if (null == notifyList) { Debug.Log("在PostNotification的通知列表中未找到: "+ aNotification.name); return; }

       List observersToRemove = new List();
       foreach (Component observer in notifyList){           
           if (!observer) {
               observersToRemove.Add(observer);
           } else {
               // 最终以u3dapi的SendMessage函数将消息发了过去,所以只能传递一个数据实参,受SendMessage函数本身限制
               observer.SendMessage(aNotification.name, aNotification,SendMessageOptions.DontRequireReceiver);
           }
        
       // 清除所有无效的观察者
       foreach (Component observer in observersToRemove) {
           notifyList.Remove(observer);
       }
    }

}

///
/// 通信类是物体发送给接受物体的一个通信类型。这个类包含发送的游戏物体(U3D的Component类对象,
/// 而不是GameObject),通信的名字(函数名),可选的数据实参
///
public class Notification {
    publicComponent sender;
    publicstring name;
    publicobject data;
    //构造函数
    publicNotification(Component aSender, string aName) { sender = aSender;name = aName; data = null; }
    publicNotification(Component aSender, string aName, object aData) {sender = aSender; name = aName; data = aData;}   
}
==================================================================================================
怎么用,很简单,就跟写事件是一样的。观察者注册事件,事件源发出事件,观察者就对其响应,对其处理。下面我写个简单的小例子演示如何使用。

==================TestTongXing_source.cs==========================================================
using UnityEngine;
using System.Collections;

//此教程通信类的测试,事件源

public class TestTongXing_source : MonoBehaviour {

    private A aa= null;

    void Start() {
       aa = new A(10,48);
     
   
    void Update() {
      if(Input.GetKeyUp(KeyCode.P)){
           // 事件源,将消息发出去,注册了的观察者会接受消息,进行对应的处理
           NotificationCenter.DefaultCenter().PostNotification(this,"printShow", this); 
       }
    }

    public AgetA(){
       return this.aa;
    }
}

public class A {

    public inti, j;
   
    public A(inti,int j) {
       this.i = i;
       this.j = j;
    }
}
==================================================================================================
我在update里写,按下P键事件源就发出事件,对这个事件注册监听的观察者就会对其处理。

接下来是观察者

===============================TestTongXing.cs====================================================
using UnityEngine;
using System.Collections;

// 此教程通信类的测试,观察者

public class TestTongXing : MonoBehaviour {

    private A aa= null;

    void Start() {
       // 注册,观察者,注册后才会接受消息,对消息进行对应的处理
       NotificationCenter.DefaultCenter().AddObserver(this,"printShow"); 
    }

    voidprintShow(Notification note) {
       Debug.Log(transform + "从," + note.sender + "," + "接收一个信息内容:" +note.data + ",通知名称为:" + note.name);
       aa =((TestTongXing_source)(note.data)).getA();
   
       Debug.Log("i = " + aa.i + ", j = " + aa.j);
    }
}
==================================================================================================
在start里注册监听事件,并写出了监听事件的实现函数,对其处理。注意我得到参数那里的类型转换,因为所有的参数都是以object类型传过去的,所有任何类型都可以传。

在场景里建了个CUBE和SPHERE,一个挂测试的事件源脚本,一个挂观察者脚本。


总结,这个脚本确实写的还是很不错的,1.写完这个脚本,对C#的事件实现能有更好的理解。肯定也有用到LIST和哈希表来搞定吧。2.手写实现了观察者模式,这样脚本和脚本之间没有了getComponent这样的紧耦合,而是观察者使用ADD函数注册监听,事件源可以不知道观察者的存在,只管POST发出事件。3.因为是用U3DAPI的发消息函数,所以支持协程函数,这个我自己测试过了,而用C#内置的事件就不支持。不过缺点是U3DAPI的发消息函数是用反射来实现的,所以速度比较慢,C#内置的事件是用委托实现的,速度快。

第2种方法,大家可以看看这篇文章,http://zijan.iteye.com/blog/871207,讲的就挺好。在easytouch等这种触屏插件就是用的C#的事件机制来响应各种触屏的效果的。


==========================================2015年1月19日23:24:02更新==============================
Notification通知中心结构:参考http://wiki.unity3d.com/index.php/Category:Messaging 这里有很多个写好的例子可以使用

Unity3D - 关于Delegate -SignalSlot信息槽的使用和SendMessage取替:

http://blog.csdn.net/chiuan/article/details/7883449

Unity3D事件派发机制之Delegate:

http://www.unitymanual.com/thread-36726-1-1.html?_dsign=521c245d


==========================================2015年1月25日22:58:31更新==============================

基于Unity的多线程之间的事件派发:http://unity3d.9tech.cn/news/2013/1018/33247.html

简单介绍一下这篇文章里说的多线程间的事件派发的使用,如文章里所说的,使用需要在任意脚本的Update中加入EventDispatcher.Instance().OnTick( );函数,下面我这介绍如何使用

=================================================TestListenrNewThread.cs==========================

using UnityEngine;
using System.Threading;
using com.gzc.CsharpThreadLockEvent;

public class TestListenrNewThread : MonoBehaviour {

    Threadthread;
    boolisQuitNewThread;

    void Start() {
       if ( string.IsNullOrEmpty(Thread.CurrentThread.Name) ) {

           // 为Unity3d的主线程起名字
           Thread.CurrentThread.Name ="U3dThead";
            
       newThread( );

       //监听事件
       com.gzc.CsharpThreadLockEvent.EventDispatcher.Instance().RegistEventListener("NewThread_WaitSomeTime",EventCallback);
    }

    voidOnDestroy ( ) {
       killNewThread( );
    }

    voidnewThread ( ) {
       thread = new Thread(new ThreadStart(run));
       thread.IsBackground = true;
       thread.Start( );
       if ( string.IsNullOrEmpty(thread.Name) ) {
           thread.Name = "NewThread";
          
       isQuitNewThread = false;
    }

    voidkillNewThread ( ) {
       thread.Abort( );
       thread.Join(10);

       isQuitNewThread = true;
       Debug.LogWarning("killNewThread !!");
    }

    void run ( ){
       while ( !isQuitNewThread ) {
           Thread.Sleep(System.TimeSpan.FromSeconds(3));  //单位是秒      //Thread.Sleep(1 * 1000); //ok 单位是毫秒

           ChatEvent chatEvent = new com.gzc.CsharpThreadLockEvent.ChatEvent();
           chatEvent.sEventName = "NewThread_WaitSomeTime";

           // 我们自己起的线程里不能用U3D的API,这里用U3D的随机数就报错了
           System.Random random = new System.Random( );
           chatEvent.iChannel = random.Next(48);
           chatEvent.sName = "NMB" + chatEvent.iChannel;
           chatEvent.sContent = "nmb" + chatEvent.iChannel;
           // 派发事件
           com.gzc.CsharpThreadLockEvent.EventDispatcher.Instance().DispatchEvent(chatEvent);
           Debug.LogWarning(Thread.CurrentThread.Name + ", 新线程发出事件!!");
       }
    }

    voidEventCallback ( EventBase eb ) {
       ChatEvent ce = eb as ChatEvent;
       Debug.LogWarning(this.gameObject.name + ",Thread.CurrentThread.Name=" + Thread.CurrentThread.Name + ",EventCallback:" + ", sContent=" + ce.sContent + ", iChannel=" +ce.iChannel + ", sEventName=" + ce.sEventName + ", sName=" +ce.sName);
    }

}
=================================================TestListenrNewThread.cs==========================

这里我是在一个继承MONO的脚本里新起一个线程,在新线程里每隔3秒发出一次事件,然后在U3D主线程中执行回调函数,简单打印一下从子线程中发来的数据。







本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
匹夫细说C#:庖丁解牛聊委托,那些编译器藏的和U3D给的
Unity MonoBehaviour
Unity手游崩溃异常如何捕获
Windows Vista aware NT Service interacting with the desktop
EntityFramework用法探索(七)线程安全实践
Jive功能与设计模式
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服