Unity游戏开发之事件中心 引言在Unity游戏开发中模块间的通信是构建复杂、可维护游戏系统的核心挑战之一。传统的直接调用方式如GetComponentT()或静态引用虽然直观但随着项目规模扩大会迅速导致代码高度耦合、难以测试和扩展。事件中心Event Center模式应运而生它通过发布-订阅Publish-Subscribe机制将事件的发送者与接收者解耦让游戏对象能够以松耦合的方式进行通信。事件中心实现事件中心的核心设计围绕几个关键组件展开每个组件都有明确的职责1. 事件信息基接口定义一个空接口IEventInfo作为所有事件容器的基类型。这个接口本身不包含任何方法或属性其主要作用是提供统一的类型约束使得不同类型的事件委托带参数或不带参数能够以相同的方式存储在字典中实现类型安全的统一管理。public interface IEventInfo { }2. 泛型事件容器EventInfoT是一个泛型类用于封装带参数的事件委托。它继承自IEventInfo内部维护一个UnityActionT委托链。当多个监听者订阅同一事件时它们的委托会被合并到同一个actions委托中实现多播multicast功能。构造函数支持初始化时添加第一个监听者。public class EventInfoT : IEventInfo { public UnityActionT actions; public EventInfo(UnityActionT action) { actions action; } }3. 非泛型事件容器EventInfo是非泛型版本用于封装不带参数的事件委托。它同样继承自IEventInfo内部维护UnityAction委托链。这种设计允许事件中心同时支持有参和无参两种事件类型而无需为每种参数类型创建单独的容器类。public class EventInfo : IEventInfo { public UnityAction actions; public EventInfo(UnityAction action) { actions action; } }4. 事件中心核心类EventCenter采用单例模式确保整个游戏运行时只有一个全局访问点。它内部使用一个字典eventDic来管理所有事件键为事件名称字符串值为对应的IEventInfo容器。这种设计使得事件注册、触发和移除都能通过统一的接口进行同时保持了良好的性能字典查找时间复杂度为 O(1)。public class EventCenter : SingletonEventCenter { private Dictionarystring, IEventInfo eventDic new Dictionarystring, IEventInfo(); }5. 事件监听注册方法提供两组重载的AddEventListener方法分别用于注册带参数和不带参数的事件监听。方法内部会自动处理两种情况如果事件名称已存在则将新的委托合并到现有委托链中如果不存在则创建新的事件容器并添加到字典。这种设计避免了重复代码同时确保了类型安全——泛型版本确保参数类型匹配非泛型版本则用于无参事件。public void AddEventListenerT(string name, UnityActionT action) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfoT).actions action; } else { eventDic.Add(name, new EventInfoT(action)); } } public void AddEventListener(string name, UnityAction action) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfo).actions action; } else { eventDic.Add(name, new EventInfo(action)); } }6. 事件派发方法事件派发同样提供泛型和非泛型两个版本。方法首先检查字典中是否存在对应的事件名称如果存在则安全调用委托链使用?.Invoke()操作符避免空引用异常。泛型版本支持传递任意类型的参数参数值会传递给所有已注册的监听者。这种设计确保了事件派发的安全性和灵活性即使没有监听者注册也不会导致程序崩溃。public void DispatchEventT(string name, T info) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfoT).actions?.Invoke(info); } } public void DispatchEvent(string name) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfo).actions?.Invoke(); } }7. 事件清理与内存管理为了避免内存泄漏和无效的委托引用事件中心提供了两种清理方式RemoveEventListener用于移除特定事件的特定监听者支持泛型参数匹配Clear方法则清空整个事件字典释放所有事件容器的引用。在场景切换或对象销毁时调用这些方法可以确保不会留下悬空委托这对于长期运行的游戏项目尤为重要。public void RemoveEventListenerT(string name, UnityActionT action) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfoT).actions - action; } } public void Clear() { eventDic.Clear(); }使用示例// 注册事件 EventCenter.Instance.AddEventListener(PlayerDie, OnPlayerDie); // 派发事件 EventCenter.Instance.DispatchEvent(PlayerDie); // 带参数的版本 EventCenter.Instance.AddEventListenerint(ScoreUpdate, OnScoreUpdate); EventCenter.Instance.DispatchEvent(ScoreUpdate, 100);完整代码实现以下是事件中心的完整实现using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; /// summary /// 事件信息基接口用于统一管理不同类型的事件容器 /// /summary public interface IEventInfo { } /// summary /// 泛型事件容器用于封装带参数的事件委托 /// /summary public class EventInfoT : IEventInfo { public UnityActionT actions; public EventInfo(UnityActionT action) { actions action; } } /// summary /// 非泛型事件容器用于封装不带参数的事件委托 /// /summary public class EventInfo : IEventInfo { public UnityAction actions; public EventInfo(UnityAction action) { actions action; } } /// summary /// 事件中心核心类采用单例模式如果不知道可以去看之前做过的设计模式的文章 /// /summary public class EventCenter : SingletonEventCenter { private Dictionarystring, IEventInfo eventDic new Dictionarystring, IEventInfo(); /// summary /// 注册带参数的事件监听 /// /summary public void AddEventListenerT(string name, UnityActionT action) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfoT).actions action; } else { eventDic.Add(name, new EventInfoT(action)); } } /// summary /// 注册不带参数的事件监听 /// /summary public void AddEventListener(string name, UnityAction action) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfo).actions action; } else { eventDic.Add(name, new EventInfo(action)); } } /// summary /// 派发带参数的事件 /// /summary public void DispatchEventT(string name, T info) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfoT).actions?.Invoke(info); } } /// summary /// 派发不带参数的事件 /// /summary public void DispatchEvent(string name) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfo).actions?.Invoke(); } } /// summary /// 移除带参数的事件监听 /// /summary public void RemoveEventListenerT(string name, UnityActionT action) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfoT).actions - action; } } /// summary /// 移除不带参数的事件监听 /// /summary public void RemoveEventListener(string name, UnityAction action) { if (eventDic.ContainsKey(name)) { (eventDic[name] as EventInfo).actions - action; } } /// summary /// 清空所有事件监听 /// /summary public void Clear() { eventDic.Clear(); } }结语通过本文我们实现了一个功能完整、类型安全的 Unity 事件中心。它通过发布-订阅模式有效解耦了游戏模块间的通信提供了统一的注册、派发和清理接口。在实际项目中合理使用事件中心可以显著提升代码的可维护性和可测试性建议在需要跨模块通信、UI 响应、游戏状态同步等场景中应用此模式。记得在场景切换或对象销毁时及时清理事件监听避免内存泄漏。如有错误请指正。