如何在Unity3d中实现观察者模式

发表于2017-02-05
评论1 4.1k浏览
  观察者模式一般是用来实现事件处理系统,考虑到有些有些开发者还不会,下面就给大家介绍下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#内置的事件是用委托实现的,速度快。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引