开发平台:Unity 2020
编程平台:Visual Studio 2020
类 与 类 之间的通讯是程序开发中经常遭遇的事。其目的是传递属性、字段等内容,以提供给另一类中的方法以执行。于是为了强化这过程,降低耦合度。出现了 事件分发器(EventDispatcher)的设计。
Unity 内置拖拽 或 脚本内赋值
通常情况下,初学者会选择 public Component m_Component; 方式进行跨类的调用。假设,名为 Example_A.cs 的脚本内中有一个 名为 PrintDebug(string message) 的方法。则在名为 Example_B.cs 的脚本中应当按照以下进行引用与调用:
public class Example_B : Monobehaviour
{
public Example_A ExampleA;
public void Awake() { // 要么拖拽、要么transform/GameObject 查找赋值 }
public void Start()
{
ExampleA.PrintDebug("This is a text");
}
}
优势:上手简单,可快速构建关系。
劣势:若涉及到一个类与多个类间的关系,这种方式是不可取,且麻烦至极。在后续的维护与更改上将增加难度与时长。
使用 事件分发器 传递信息
事件分发器,其某方面上与 MVC 设计模式有着异曲同工之处。在 MVC 框架设计中,关联 视图与控制器 的管理思想。即 注册、注销。将同类型对象,从场景内激活的那一刻起,添加至控制器中。交由控制器 发送信号,由对象自己接收信号判断是否拥有此类型信号,有则响应,无则静默。若该对象被禁用,则移除至队列外,不再受控制器管理。
public class Example_C : Monobehaivour
{
public void OnEnable() => EventDispatcher.AddObserver("PlayerDoit", OnPlayerDo);
public void OnDisable() => EventDispactcher.RemoveObserver("PlayerDoit", OnPlayerDo);
public void OnPlayerDo() { Deug.Log("I have to do something which i really want to do"); }
}
备注:与 UGUI EventSystem 有相同点。例如程序上 Button / Toggle / InputField 添加与移除 响应事件的监听方式,具体代码:addListener(callback) 与 removeListener(callback)
public class Example_D : MonoBehaviour
{
public void Start() => EventDispatcher.SendMessage("PlayerDoit");
}
由 Example_D.cs 发送讯息,通知拥有该类型事件的监听器响应结果。即 Example_C.cs 中在 OnEnable 阶段注册的监听器响应方法 OnPlayerDo 。
优势:仅需 SendMessage 即可实现消息跨类的进行。若期望于新增或禁用则需 Add/Remove + Observer。方便管理。
劣势:需要合理的设计与应用,错误的使用 事件监听器 将导致代码冗余度增加。应避免一个发信内响应的是另一个发信方式等情况发生。

由 事件分发器 管理组件对象,根据消息类型,传递参数与响应。在第一设计中,构想使用 Dictionary (消息类型,脚本对象)数据类型用于注册被添加至事件中的对象。即被添加至对应 string 中的 Component 对象,被通知执行其对应的方法。执行的方法依赖于各类对象中的信号记录。例如
public class EventDispatcher
{
public static Dictionary<string, List<Component>> EventResgisters = new Dictionary<string, List<Component>>();
// 消息类型参考(无实际意义)
private List<string> MessageRegister = new List<string>()
{
"Login",
"OpenMainPage",
"OpenDescription"
};
public static void SendMessage(string message, object[] data)
{
var targets = EventRegisters(name);
foreach(var item in targets)
{
item.OnReceiveMsg(message, data);
}
}
public static void Register(string messageName, Component comp) {}
public static void Logout(string messageName, Component comp) {}
}
而作为响应事件的对象,提供每类型信号下与之匹配的方法。在完成注册后,通过 确认是否在注册的消息机制中 - 使用 switch- case 筛选信号类别与响应事件类别,从而完成响应过程。如下图所示:
public class Example_E : MonoBehaviour
{
private void OnEnable() => EventDispatcher.Register("Login", this);
private void OnDisable() => EventDispatcher.Logout("Login", this);
private List<string> MessageRegister = new List<string>()
{
"Login",
"OpenDescription"
};
public void OnReceiveMsg(string messageTarget, object[] data)
{
if(!MessageRegister.Contains(messageTarget)) return;
switch(messageTarget)
{
case "Login":
DoLogin(data);
breake;
case "OpenDiscription":
DoOpenDiscription(data);
breake;
default:
break;
}
}
private void DoLogin(object[] data) {}
private void DoOpenDiscription(object[] data) {}
}
虽然一定程度上,确实有助于实现消息的管理与响应机制,但仍存在许多方面明显表现不足的地方。代码的冗余与操作上的复杂尤为突出:
Switch-case 语句的多次叠加下,显得不容易快速比对与快速定位。 程序的设计目的是便利化实现过程,而非复杂化。于是在这样的要求和时间的洗涤中,接触到了更加完美的 事件分发器 设计机制。学习与巩固了 CSharp 知识。
Unity 监听机制 AddListener 是最为体现委托与事件的地方。例如 button.onClick.AddListener(delegate { DoLogin(); }); 这段代码行,其目的性是为 Button 对象添加事件监听选项。当 Button 的点击行为发生时,触发该 DoLogin() 方法。注意!监听器中添加的属于 UnityEvent 的委托事件类型。如下图所示:

于是,委托 + 事件 无疑是最佳的设计方案。通过给对象添加监听器,监听分发的事件是否符合己监听,从响应事件。在整体结构上,只需 OnEnable/OnDiable 周期中注册、注销监听器即可。解决了 第一次设计方案中,代码行多,后期冗杂大的问题。于是有以下程序设计:
public class Dispatcher
{
public delegate void EventHandler(param object[] _objects);
public static Dictionary<string, EventHandler> RegistrationEvents = new Dictionary<string, List<EventHandler>>();
public static void SendMessage() {}
public static void AddObserver() {}
public static void RemoveObserver() {}
}
EventHandler?.Invoke 。例如:实现 委托 的注册行为。即如下所示:
public void AddObserver(string name, EventHandler eventHandler) => RegistrationEvents[name] += EventHandler;
同理情况下,注销委托 即 RemoveObserver(string name, EventHandler eventHandler) 通过 -= 方式完成。于是,在观察者(监听器)准备就绪后,剩下的关注焦点落到了事件的分发。
如何分发?
与 第一次设计 中采取的监听方式不同,委托的分发无需识别信息内容是否与现存文本匹配。因为委托的特性,凡监听名称匹配的 委托对象,其下的所有委托均接收消息内容。即仅需要考虑 委托消息,委托传递的参数 共两个内容。但就目前情况而言,需要思考委托的调用。正如 onClick.AddListener(delegate { OnClickDown() })。委托需要自己的执行方式。大致为以下顺序:
var thisDelegate = new EventHandler(委托)thisDelegate.Invoke()Null 的情况。在使用委托时,应注意空引用的判断。使用if语句或?.语法糖可判断。 于是,事件分发 即SendMessage(string name, param object[] _objects) 得到实现。可以测试一下 类与类 之间的通讯行为。如下图所示,由 Example01.cs 发送名为 SayHello,内容为 来自Example01的问候。Example02.cs 则注册、注销监听器,实现监听响应的事件方法 OnSayHelloEventHandler。经拆箱后解析 Example01 传来的消息,并 Debug 至控制台。
