功能分析:

首先,我们必须先明确一个消息系统的核心功能:

  • 一个通用的事件监听器
  • 管理各个业务监听的事件类型(注册和解绑事件监听器)
  • 全局广播事件
  • 广播事件所传参数数量和数据类型都是可变的(数量可以是 0~3,数据类型是泛型)

设计思路:

清楚了上述的几个要求之后,我们不难自行定制一个业务层的消息系统,即在消息系统初始化时将每个模块绑定的消息列表,根据消息类型分类(用一个 string 类型的数据类标识),即建立一个字典 Dictionary<string,List<Model>> 每条消息触发时需要通知的模块列表,即某条消息触发,遍历字典中绑定的模块列表,然后有两种选择方案:

  • 假如模块是与 gameObject 绑定的继承自 MonoBehaviour 的脚本,通过 Unity 原生的向指定模块发送消息的接口 gameObject.SendMessage(message) 发送消息,模块用则用 public void getMessage(string message) 函数接收消息;

  • 假如模块是独立的 C# 实例,则可以给模块设计一个基类,基类中有一个虚构函数,在具体模块中重写这个函数,这样消息中心要想模块发送触发消息时,直接将模块字典中绑定的模块引用强转为基类类型,然后调用该虚构函数。

然而,这样的 DIY 的消息管理系统最常见的问题就是:模块已经销毁了,但在字典中的引用还在,那么消息要传递给模块的时候,就会触发 MissingReferenceException 或者 NullReferenceException 这类空引用错误。


插件简介:

Advanced CSharp Messenger 是一个 C# 高级版本的消息传递系统 。它将会在加载一个新的 level 后自动清理其事件表。这将防止程序员意外地调用被毁坏的方法,从而将有助于防止很多 MissingReferenceExceptions。此消息传递系统基于杆海德 CSharpMessenger 和马格努斯沃尔费尔特的CSharpMessenger 扩展。

核心功能:

1.注册一个事件监听器:

Messenger.AddListener<T>( "消息类型标识", OnCallback);
//事件回调函数
void OnCallback(T data){

}

监听事件可以带参也可不带参,参数类型是泛型,既可以传递基础数据类型,也可以传递 gameObject 对象,当然两种情况属于完全不同的时间,用一个字符串来表示事件的类型,OnCallback 是事件出发时的回调函数,回调函数的参数表与监听格式一致。

2.取消注册事件监听器:

这里需要注意的就是与注册时的参数格式完全一致,只是把 AddListener 改成 RemoveListener

Messenger.RemoveListener<T>( "消息类型标识", OnCallback);

3.广播事件:

//不带参
Messenger.Broadcast( "消息类型标识");
//带参
Messenger.Broadcast<T>( "消息类型标识", data1);

第一个参数是事件类型标识,后面的参数表是与 <T> 中指定的数据类型对应的回传数据。


插件引入:

只需在当前项目组添加一下两个脚本,即可开始使用 Advanced CSharp Messenger 这个消息管理器来管理我们项目的消息了。

Callback.cs

public delegate void Callback();
public delegate void Callback<T>(T arg1);
public delegate void Callback<T, U>(T arg1, U arg2);
public delegate void Callback<T, U, V>(T arg1, U arg2, V arg3);

Messenger.cs

/*
 * Advanced C# messenger by Ilya Suzdalnitski. V1.0
 *
 * Based on Rod Hyde's "CSharpMessenger" and Magnus Wolffelt's "CSharpMessenger Extended".
 *
 * Features:
    * Prevents a MissingReferenceException because of a reference to a destroyed message handler.
    * Option to log all messages
    * Extensive error detection, preventing silent bugs
 *
 * Usage examples:
    1. Messenger.AddListener<GameObject>("prop collected", PropCollected);
       Messenger.Broadcast<GameObject>("prop collected", prop);
    2. Messenger.AddListener<float>("speed changed", SpeedChanged);
       Messenger.Broadcast<float>("speed changed", 0.5f);
 *
 * Messenger cleans up its evenTable automatically upon loading of a new level.
 *
 * Don't forget that the messages that should survive the cleanup, should be marked with Messenger.MarkAsPermanent(string)
 *
 */

//#define LOG_ALL_MESSAGES
//#define LOG_ADD_LISTENER
//#define LOG_BROADCAST_MESSAGE
#define REQUIRE_LISTENER

using System;
using System.Collections.Generic;
using UnityEngine;

static internal class Messenger {
    #region Internal variables

    //Disable the unused variable warning
#pragma warning disable 0414
    //Ensures that the MessengerHelper will be created automatically upon start of the game.
    static private MessengerHelper messengerHelper = ( new GameObject("MessengerHelper") ).AddComponent< MessengerHelper >();
#pragma warning restore 0414

    static public Dictionary<string, Delegate> eventTable = new Dictionary<string, Delegate>();

    //Message handlers that should never be removed, regardless of calling Cleanup
    static public List< string > permanentMessages = new List< string > ();
    #endregion
    #region Helper methods
    //Marks a certain message as permanent.
    static public void MarkAsPermanent(string eventType) {
#if LOG_ALL_MESSAGES
        Debug.Log("Messenger MarkAsPermanent \t\"" + eventType + "\"");
#endif

        permanentMessages.Add( eventType );
    }

    static public void Cleanup()
    {
#if LOG_ALL_MESSAGES
        Debug.Log("MESSENGER Cleanup. Make sure that none of necessary listeners are removed.");
#endif

        List< string > messagesToRemove = new List<string>();

        foreach (KeyValuePair<string, Delegate> pair in eventTable) {
            bool wasFound = false;

            foreach (string message in permanentMessages) {
                if (pair.Key == message) {
                    wasFound = true;
                    break;
                }
            }

            if (!wasFound)
                messagesToRemove.Add( pair.Key );
        }

        foreach (string message in messagesToRemove) {
            eventTable.Remove( message );
        }
    }

    static public void PrintEventTable()
    {
        Debug.Log("\t\t\t=== MESSENGER PrintEventTable ===");

        foreach (KeyValuePair<string, Delegate> pair in eventTable) {
            Debug.Log("\t\t\t" + pair.Key + "\t\t" + pair.Value);
        }

        Debug.Log("\n");
    }
    #endregion

    #region Message logging and exception throwing
    static public void OnListenerAdding(string eventType, Delegate listenerBeingAdded) {
#if LOG_ALL_MESSAGES || LOG_ADD_LISTENER
        Debug.Log("MESSENGER OnListenerAdding \t\"" + eventType + "\"\t{" + listenerBeingAdded.Target + " -> " + listenerBeingAdded.Method + "}");
#endif

        if (!eventTable.ContainsKey(eventType)) {
            eventTable.Add(eventType, null );
        }

        Delegate d = eventTable[eventType];
        if (d != null && d.GetType() != listenerBeingAdded.GetType()) {
            throw new ListenerException(string.Format("Attempting to add listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being added has type {2}", eventType, d.GetType().Name, listenerBeingAdded.GetType().Name));
        }
    }

    static public void OnListenerRemoving(string eventType, Delegate listenerBeingRemoved) {
#if LOG_ALL_MESSAGES
        Debug.Log("MESSENGER OnListenerRemoving \t\"" + eventType + "\"\t{" + listenerBeingRemoved.Target + " -> " + listenerBeingRemoved.Method + "}");
#endif

        if (eventTable.ContainsKey(eventType)) {
            Delegate d = eventTable[eventType];

            if (d == null) {
                throw new ListenerException(string.Format("Attempting to remove listener with for event type \"{0}\" but current listener is null.", eventType));
            } else if (d.GetType() != listenerBeingRemoved.GetType()) {
                throw new ListenerException(string.Format("Attempting to remove listener with inconsistent signature for event type {0}. Current listeners have type {1} and listener being removed has type {2}", eventType, d.GetType().Name, listenerBeingRemoved.GetType().Name));
            }
        } else {
            throw new ListenerException(string.Format("Attempting to remove listener for type \"{0}\" but Messenger doesn't know about this event type.", eventType));
        }
    }

    static public void OnListenerRemoved(string eventType) {
        if (eventTable[eventType] == null) {
            eventTable.Remove(eventType);
        }
    }

    static public void OnBroadcasting(string eventType) {
#if REQUIRE_LISTENER
        if (!eventTable.ContainsKey(eventType)) {
            throw new BroadcastException(string.Format("Broadcasting message \"{0}\" but no listener found. Try marking the message with Messenger.MarkAsPermanent.", eventType));
        }
#endif
    }

    static public BroadcastException CreateBroadcastSignatureException(string eventType) {
        return new BroadcastException(string.Format("Broadcasting message \"{0}\" but listeners have a different signature than the broadcaster.", eventType));
    }

    public class BroadcastException : Exception {
        public BroadcastException(string msg)
            : base(msg) {
        }
    }

    public class ListenerException : Exception {
        public ListenerException(string msg)
            : base(msg) {
        }
    }
    #endregion

    #region AddListener
    //No parameters
    static public void AddListener(string eventType, Callback handler) {
        OnListenerAdding(eventType, handler);
        eventTable[eventType] = (Callback)eventTable[eventType] + handler;
    }

    //Single parameter
    static public void AddListener<T>(string eventType, Callback<T> handler) {
        OnListenerAdding(eventType, handler);
        eventTable[eventType] = (Callback<T>)eventTable[eventType] + handler;
    }

    //Two parameters
    static public void AddListener<T, U>(string eventType, Callback<T, U> handler) {
        OnListenerAdding(eventType, handler);
        eventTable[eventType] = (Callback<T, U>)eventTable[eventType] + handler;
    }

    //Three parameters
    static public void AddListener<T, U, V>(string eventType, Callback<T, U, V> handler) {
        OnListenerAdding(eventType, handler);
        eventTable[eventType] = (Callback<T, U, V>)eventTable[eventType] + handler;
    }
    #endregion

    #region RemoveListener
    //No parameters
    static public void RemoveListener(string eventType, Callback handler) {
        OnListenerRemoving(eventType, handler);
        eventTable[eventType] = (Callback)eventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }

    //Single parameter
    static public void RemoveListener<T>(string eventType, Callback<T> handler) {
        OnListenerRemoving(eventType, handler);
        eventTable[eventType] = (Callback<T>)eventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }

    //Two parameters
    static public void RemoveListener<T, U>(string eventType, Callback<T, U> handler) {
        OnListenerRemoving(eventType, handler);
        eventTable[eventType] = (Callback<T, U>)eventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }

    //Three parameters
    static public void RemoveListener<T, U, V>(string eventType, Callback<T, U, V> handler) {
        OnListenerRemoving(eventType, handler);
        eventTable[eventType] = (Callback<T, U, V>)eventTable[eventType] - handler;
        OnListenerRemoved(eventType);
    }
    #endregion

    #region Broadcast
    //No parameters
    static public void Broadcast(string eventType) {
#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
        Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
#endif
        OnBroadcasting(eventType);

        Delegate d;
        if (eventTable.TryGetValue(eventType, out d)) {
            Callback callback = d as Callback;

            if (callback != null) {
                callback();
            } else {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }

    //Single parameter
    static public void Broadcast<T>(string eventType, T arg1) {
#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
        Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
#endif
        OnBroadcasting(eventType);

        Delegate d;
        if (eventTable.TryGetValue(eventType, out d)) {
            Callback<T> callback = d as Callback<T>;

            if (callback != null) {
                callback(arg1);
            } else {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }

    //Two parameters
    static public void Broadcast<T, U>(string eventType, T arg1, U arg2) {
#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
        Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
#endif
        OnBroadcasting(eventType);

        Delegate d;
        if (eventTable.TryGetValue(eventType, out d)) {
            Callback<T, U> callback = d as Callback<T, U>;

            if (callback != null) {
                callback(arg1, arg2);
            } else {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }

    //Three parameters
    static public void Broadcast<T, U, V>(string eventType, T arg1, U arg2, V arg3) {
#if LOG_ALL_MESSAGES || LOG_BROADCAST_MESSAGE
        Debug.Log("MESSENGER\t" + System.DateTime.Now.ToString("hh:mm:ss.fff") + "\t\t\tInvoking \t\"" + eventType + "\"");
#endif
        OnBroadcasting(eventType);

        Delegate d;
        if (eventTable.TryGetValue(eventType, out d)) {
            Callback<T, U, V> callback = d as Callback<T, U, V>;

            if (callback != null) {
                callback(arg1, arg2, arg3);
            } else {
                throw CreateBroadcastSignatureException(eventType);
            }
        }
    }
    #endregion
}

//This manager will ensure that the messenger's eventTable will be cleaned up upon loading of a new level.
public sealed class MessengerHelper : MonoBehaviour {
    void Awake ()
    {
        DontDestroyOnLoad(gameObject);
    }

    //Clean up eventTable every time a new level loads.
    public void OnLevelWasLoaded(int unused) {
        Messenger.Cleanup();
    }
}

当然,假如为了脚本管理方便,也可将两部分代码都合并在同一个脚本中,而且事件绑定的 key 都是以一个 string 来标志的,为了统一管理消息,这里我又创建了一个脚本 Msg_Define.cs

public class Msg_Define{
    public const string MSG_START = "MSG_START";
    public const string MSG_AWAKE = "MSG_AWAKE";
}

测试实例:

这里我们可以直接在一个测试场景中新建一个C#测试脚本,并都绑定到场景中的相机上(保证点击Unity运行时,脚本会处于工作状态),然后通过在脚本中广播一个事件(以多种传参形式),然后在脚本自身进行监听,如此完成自发自收的测试:

脚本 TestMsg.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestMsg : MonoBehaviour {

    void Awake()
    {
        //监听消息
        Messenger.AddListener(Msg_Define.MSG_AWAKE, OnAwakeCall);
        Messenger.AddListener<int>(Msg_Define.MSG_START, OnStartCall);
        //发送不带参数广播
        Messenger.Broadcast(Msg_Define.MSG_AWAKE);
    }

    void Start()
    {
        //发送带参数广播
        Messenger.Broadcast<int>(Msg_Define.MSG_START,666);
    }

    void OnDestroy()
    {
        //移除监听
        Messenger.RemoveListener(Msg_Define.MSG_AWAKE, OnAwakeCall);
        Messenger.RemoveListener<int>(Msg_Define.MSG_START, OnStartCall);
    }
    //消息回调
    void OnAwakeCall()
    {
        Debug.logger.Log("awake");
    }

    void OnStartCall(int num)
    {
        Debug.logger.Log("start"+num);
    }
}

运行 Unity,可以在 Unity 的 Console 面板中看到输出结果:

awake
UnityEngine.Logger:Log(Object)
start666
UnityEngine.Logger:Log(Object)

参考资料:

《Unity 3D游戏客户端基础框架》消息系统的更多相关文章

  1. 《Unity 3D游戏客户端基础框架》概述

    框架概述: 做了那么久的业务开发,也做了一年多的核心战斗开发,最近想着自己倒腾一套游戏框架,当然暂不涉及核心玩法类型和战斗框架,核心战斗的设计要根据具体的游戏类型而定制,这里只是一些通用的基础系统的框 ...

  2. 《Unity 3D游戏客户端基础框架》多线程异步 Socket 框架构建

    引言: 之前写过一个 demo 案例大致讲解了 Socket 通信的过程,并和自建的服务器完成连接和简单的数据通信,详细的内容可以查看 Unity3D -- Socket通信(C#).但是在实际项目应 ...

  3. 《Unity 3D游戏客户端基础框架》系统设计

    引言 最近到看一个 <贪吃蛇大战开发实例>,其中 贪吃蛇大作战游戏开发实战(3):系统构架设计 提供的系统架构的设计思路我觉得还是值得学习一下的,接下来的内容是我看完视频后的一点笔记. 架 ...

  4. Unity 3D游戏开发学习路线(方法篇)

    Unity 3D本来是由德国的一些苹果粉丝开发的一款游戏引擎,一直只能用于Mac平台,所以一直不被业外人士所知晓.但是后来也推出了2.5版,同时发布了PC版本,并将其发布方向拓展到手持移动设备.Uni ...

  5. Unity 3D游戏开发引擎:最火的插件推荐

    摘要:为了帮助使用Unity引擎的开发人员制作更完美的游戏.我们精心挑选了十款相关开发插件和工具.它们是:2D Toolkit.NGUI.Playmaker.EasyTouch & EasyJ ...

  6. Unity 3D 游戏上线之后的流水总结

    原地址:http://tieba.baidu.com/p/2817057297?pn=1 首先.unity 灯光烘焙 :Unity 3D FBX模型导入.选项Model 不导入资源球.Rig 不导入骨 ...

  7. Unity 3d游戏逆向及.NET Reflector工具使用介绍

    移动平台游戏框架主要有unity 3d和cocos 2d.我们首先得识别游戏使用的框架.识别Unity游戏Android平台的apk包可以直接解压,看是否有./assets/bin/Data/Mana ...

  8. 【Unity】1.0 第1章 Unity—3D游戏开发和虚拟现实应用开发的首选

    分类:Unity.C#.VS2015 创建日期:2016-03-23 一.简介 Unity是跨平台2D.3D游戏和虚拟现实高级应用程序的专业开发引擎,是由Unity Technologies公司研制的 ...

  9. 排名前10的H5、Js 3D游戏引擎和框架

    由于很多人都在用JavaScript.HTML5和WebGL技术创建基于浏览器的3D游戏,所有JavaScript 3D游戏引擎是一个人们主题.基于浏览器的游戏最棒的地方是平台独立,它们能在iOS.A ...

随机推荐

  1. tomcat+svn+maven+jenkins实现自动构建

    首先说明一个各软件的版本: tomcat:apache-tomcat-8.5.16.tar.gz maven:apache-maven-3.5.0-bin.tar.gz svn:subversion- ...

  2. shell进阶教程

    背景:就自己常用的shell脚本写作风格,总结了一些知识点.也是作为交接工作的一部分文档.部分内容单独写 #!/bin/sh # shell脚本进阶教程 # 1.常用知识点:变量设置/日期设置/格式化 ...

  3. 20145335郝昊《java程序设计》第7周学习总结

    20145335郝昊 <Java程序设计>第7周学习总结 教材学习内容总结 认识时间与日期 格林威治标准时间:简称GMT时间,参考格林威治皇家天文台的标准太阳时间. 世界时:简称UT,借由 ...

  4. chrome调试工具

    Chrome调试面板 Chrome 开发者工具是一套内置在Google Chrome中Web开发和调试工具.使用开发者工具来重演,调试和剖析您的网站.其中常用的有Elements(元素面板).Cons ...

  5. Java基础东西(按位操作运算)

    http://aokunsang.iteye.com/blog/615658 前奏:   昨天一哥们问我Java位移你会吗,我说不会,想想位移这么麻烦,一般有位移的Java代码一律不看,有几个人会啊, ...

  6. ASP.NET MVC 必备开发环境

    许多初学者为了搭建开发环境,很多软件找不齐,或者找不到的比较新而且稳定版本.所以我将下载和安装的资料整理了下,供大家下载.资料均收集于网络,但基本核实资料的可靠性,但不能完全保证.如果你在使用过程中发 ...

  7. 详解WIFI破解-Kali篇

    转自: http://www.secbox.cn/hacker/wireless/4877.html 工具: 1:笔记本 2:USB无线网卡(必备) 3:kali系统 4:靠谱字典 第一种方法: 暴力 ...

  8. [转载]Javassist 使用指南(一)

    ======================= 本文转载自简书,感谢原作者!. 原链接如下:https://www.jianshu.com/p/43424242846b =============== ...

  9. iOS日常学习 - 每个Xcode开发者应该知道的七个使用技巧

    本文为转载学习使用原文链接 工欲善其事,必先利其器.对一个iOS开发者来说,这就意味着对Xcode的熟练掌握程度.Xcode是一个学习起来有点难度的软件,下面的这些技巧或许可以显著的提高你的编程效率. ...

  10. vue集成ueditor

    相关代码见github 1.引入ueditor相关的文件,具体目录见下图如下 我将下载的文件放在static下面,这里专门用来放置相关的静态文件 (在ueditor.config.js需要配置一下路径 ...