如何在Unity3d中实现观察者模式
发表于2017-02-05
观察者模式一般是用来实现事件处理系统,考虑到有些有些开发者还不会,下面就给大家介绍下Unity3d中观察者模式实现的方法,一起来看看吧。
封装U3D中的发消息函数,自己写一个事件机制出来。本质是一个观察者模式的实现,该类是一个单例的类,用哈希表来保存场景中的所有消息,哈希表中每个键值对,表示的是【某一消息(函数名)——该消息的所有观察者线性表】。最终以u3d api的SendMessage函数将消息发了过去,所以只能传递一个数据实参,受SendMessage函数本身限制。但是你传的参数可以是脚本对象,把参数写到对象中,这样就可以传多个参数了。
=======================NotificationCenter.cs==============================
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | using UnityEngine; using System.Collections; using System.Collections.Generic; /// /// 信息中心类,用来处理GameObjects相互发消息。本质是观察者模式。 /// 通过AddObserver函数注册观察者,RemoveObserver注销观察者。 /// 内部通过哈希表对场景中所有的消息进行管理 /// public class NotificationCenter : MonoBehaviour { private static NotificationCenter defaultCenter = null ; /// /// 单例模式, 在场景中自动造一个挂有NotificationCenter脚本的Default Notification Center游戏物体,如果手动创建了一个则不再创建 /// public static NotificationCenter DefaultCenter () { if ( null == defaultCenter) { GameObject notificationObject = new GameObject( "Default Notification Center" ); defaultCenter = notificationObject.AddComponent(); } return defaultCenter; } // 哈希表包含了所有的发送的信息。其中每个键值对,表示的是【某一消息——该消息的所有观察者线性表】 private Hashtable notifications = null ; void Awake() { this .notifications = new Hashtable(); } /// /// 注册观察者 /// public void AddObserver(Component observer, string name) { AddObserver(observer, name, null ); } public void AddObserver(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] as List; // 将观察者加入到哈希表中值LIST中去,也就是注册上了 if (!notifyList.Contains(observer)) { notifyList.Add(observer); } } /// /// 注销观察者 /// public void RemoveObserver(Component observer, string name) { // 该条消息的所有观察者,通过List将他拉出来对其操作 List notifyList = this .notifications[name] as List; if ( null != notifyList) { // 删除这个已注册的观察者 if (notifyList.Contains(observer)) { notifyList.Remove(observer); } // 如果这个消息没有观察者,则在哈希表中删除这个消息关键字 if (notifyList.Count == 0) { this .notifications.Remove(name); } } } /// /// 事件源把发消息出去 /// public void PostNotification(Component aSender, string aName) { PostNotification(aSender, aName, null ); } public void PostNotification(Component aSender, string aName, object aData) { PostNotification( new Notification(aSender, aName, aData)); } public void PostNotification(Notification aNotification) { if (aNotification.name == null || aNotification.name == "" ) { Debug.Log( "Null name sent to PostNotification." ); return ; } // 该条消息的所有观察者,通过List将他拉出来对其操作 List notifyList = this .notifications[aNotification.name] as List; if ( null == notifyList) { Debug.Log( "在PostNotification的通知列表中未找到: " + aNotification.name); return ; } List observersToRemove = new List(); foreach (Component observer in notifyList) { if (!observer) { observersToRemove.Add(observer); } else { // 最终以u3d api的SendMessage函数将消息发了过去,所以只能传递一个数据实参,受SendMessage函数本身限制 observer.SendMessage(aNotification.name, aNotification, SendMessageOptions.DontRequireReceiver); } } // 清除所有无效的观察者 foreach (Component observer in observersToRemove) { notifyList.Remove(observer); } } } /// /// 通信类是物体发送给接受物体的一个通信类型。这个类包含发送的游戏物体(U3D的Component类对象, /// 而不是GameObject),通信的名字(函数名),可选的数据实参 /// public class Notification { public Component sender; public string name; public object data; // 构造函数 public Notification(Component aSender, string aName) { sender = aSender; name = aName; data = null ; } public Notification(Component aSender, string aName, object aData) { sender = aSender; name = aName; data = aData; } }
|
怎么用,很简单,就跟写事件是一样的。观察者注册事件,事件源发出事件,观察者就对其响应,对其处理。下面我写个简单的小例子演示如何使用。
==================TestTongXing_source.cs=================================
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 A getA(){ return this .aa; } } public class A { public int i, j; public A( int i, int j) { this .i = i; this .j = j; } } |
在update里写,按下P键事件源就发出事件,对这个事件注册监听的观察者就会对其处理。
接下来是观察者
===============================TestTongXing.cs=========================
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using UnityEngine; using System.Collections; // 此教程通信类的测试,观察者 public class TestTongXing : MonoBehaviour { private A aa = null ; void Start () { // 注册,观察者,注册后才会接受消息,对消息进行对应的处理 NotificationCenter.DefaultCenter().AddObserver( this , "printShow" ); } void printShow(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,一个挂测试的事件源脚本,一个挂观察者脚本。
实现方法分析
写完这个脚本,对C#的事件实现能有更好的理解。肯定也有用到LIST和哈希表来搞定吧。
手写实现了观察者模式,这样脚本和脚本之间没有了getComponent这样的紧耦合,而是观察者使用ADD函数注册监听,事件源可以不知道观察者的存在,只管POST发出事件。
3.因为是用U3D API的发消息函数,所以支持协程函数,这个我自己测试过了,而用C#内置的事件就不支持。不过缺点是U3D API的发消息函数是用反射来实现的,所以速度比较慢,C#内置的事件是用委托实现的,速度快。