当前位置: 首页 > article >正文

别再滥用单例了!在Unity中实现一个轻量级、可测试的事件总线(Event Bus)系统

重构Unity事件系统从单例依赖到可测试事件总线的进阶实践在游戏开发中我们经常遇到不同组件间需要通信的场景。传统做法是使用GameManager单例或静态类来全局传递数据但这种做法会导致代码高度耦合、难以测试和维护。想象一下当你需要单独测试一个血条UI组件时却不得不启动整个游戏场景仅仅因为它依赖了一个全局静态事件系统——这显然违背了良好的软件工程原则。1. 为什么我们需要放弃单例事件系统单例模式在Unity开发中被广泛使用尤其是在事件传递场景中。开发者习惯创建一个EventManager单例让所有组件都能方便地订阅和发布事件。但这种便利性背后隐藏着严重的架构问题测试困难依赖于全局状态的代码无法进行独立单元测试隐藏依赖组件间的通信关系不透明难以追踪事件流向生命周期问题静态实例在场景切换时可能引发意外行为并发风险全局访问在多线程环境下容易产生竞态条件// 典型的单例事件系统使用方式 - 不推荐 public class PlayerHealth : MonoBehaviour { void TakeDamage(int amount) { EventManager.Instance.Publish(PlayerDamaged, amount); } }相比之下基于依赖注入的事件总线系统提供了更优雅的解决方案特性单例事件系统可注入事件总线可测试性差优秀耦合度高低生命周期管理困难灵活线程安全风险高可控架构清晰度模糊明确2. 设计轻量级事件总线核心让我们从零开始构建一个不依赖单例模式的事件总线系统。核心设计原则是使用接口抽象事件总线功能通过构造函数注入依赖支持强类型事件定义提供清晰的订阅/发布机制首先定义事件总线接口public interface IEventBus { void SubscribeT(ActionT handler) where T : IEvent; void UnsubscribeT(ActionT handler) where T : IEvent; void PublishT(T eventData) where T : IEvent; } public interface IEvent { }实现一个具体的事件总线类public class EventBus : IEventBus { private readonly DictionaryType, ListDelegate _handlers new(); public void SubscribeT(ActionT handler) where T : IEvent { var eventType typeof(T); if (!_handlers.ContainsKey(eventType)) { _handlers[eventType] new ListDelegate(); } _handlers[eventType].Add(handler); } public void PublishT(T eventData) where T : IEvent { if (_handlers.TryGetValue(typeof(T), out var handlers)) { foreach (var handler in handlers) { ((ActionT)handler)(eventData); } } } // 实现Unsubscribe... }3. 在Unity中集成事件总线将事件总线集成到Unity项目中需要解决几个关键问题3.1 依赖注入解决方案Unity本身不提供完整的DI容器但我们可以使用轻量级解决方案手动注入通过MonoBehaviour的构造函数或公共字段使用第三方库如Zenject或VContainer创建简单的服务定位器非单例// 使用Zenject进行依赖注入的示例 public class GameInstaller : MonoInstaller { public override void InstallBindings() { Container.BindIEventBus().ToEventBus().AsSingle(); } } public class PlayerHealth : MonoBehaviour { [Inject] private IEventBus _eventBus; public void TakeDamage(int amount) { _eventBus.Publish(new PlayerDamagedEvent(amount)); } }3.2 事件定义最佳实践定义事件时应遵循以下原则使用小而专一的事件类包含足够上下文信息使用不可变数据结构明确命名事件意图public struct PlayerDamagedEvent : IEvent { public readonly int DamageAmount; public readonly Vector3 HitPosition; public PlayerDamagedEvent(int damageAmount, Vector3 hitPosition) { DamageAmount damageAmount; HitPosition hitPosition; } }4. 实现可测试的游戏组件可测试性是这种架构的最大优势。让我们看一个完整的示例4.1 定义血条UI组件public class HealthBarUI : MonoBehaviour { [SerializeField] private Image _fillImage; private IEventBus _eventBus; private float _currentHealth 1f; public void Initialize(IEventBus eventBus) { _eventBus eventBus; _eventBus.SubscribePlayerDamagedEvent(OnPlayerDamaged); _eventBus.SubscribePlayerHealedEvent(OnPlayerHealed); } private void OnPlayerDamaged(PlayerDamagedEvent e) { _currentHealth - e.DamageAmount * 0.01f; _fillImage.fillAmount Mathf.Clamp01(_currentHealth); } // 实现OnPlayerHealed... }4.2 编写单元测试使用NUnit框架测试血条UI无需启动Unity编辑器[TestFixture] public class HealthBarUITests { [Test] public void HealthBar_Decreases_WhenPlayerTakesDamage() { // 准备 var mockEventBus new MockIEventBus(); var healthBar new HealthBarUI(); healthBar.Initialize(mockEventBus.Object); float? finalFillAmount null; healthBar.OnFillAmountChanged amount finalFillAmount amount; // 执行 mockEventBus.Raise(e e.Publish null, new PlayerDamagedEvent(30, Vector3.zero)); // 验证 Assert.AreEqual(0.7f, finalFillAmount); } }4.3 测试驱动开发流程先编写测试定义组件预期行为实现组件功能使其通过测试在Unity编辑器中集成测试重构优化确保测试仍然通过提示在Unity中设置Test Runner窗口定期运行单元测试套件确保修改不会破坏现有功能。5. 高级应用场景与性能优化事件总线系统可以进一步扩展以满足复杂需求5.1 事件过滤与中间件public class LoggingEventMiddleware : IEventBus { private readonly IEventBus _innerBus; public LoggingEventMiddleware(IEventBus innerBus) { _innerBus innerBus; } public void PublishT(T eventData) where T : IEvent { Debug.Log($Publishing event: {typeof(T).Name}); _innerBus.Publish(eventData); } // 实现其他接口方法... }5.2 性能优化技巧使用对象池管理事件实例对高频事件采用批处理机制为关键事件路径添加性能分析考虑使用值类型事件减少GC压力// 对象池实现示例 public class EventPoolT where T : IEvent, new() { private readonly StackT _pool new(); public T Get() { return _pool.Count 0 ? _pool.Pop() : new T(); } public void Return(T eventInstance) { _pool.Push(eventInstance); } }5.3 多线程支持策略主线程派发确保Unity API调用安全线程安全队列跨线程事件处理同步上下文捕获自动回到主线程执行public class MainThreadEventBus : IEventBus { private readonly IEventBus _innerBus; private readonly SynchronizationContext _mainThreadContext; public MainThreadEventBus(IEventBus innerBus) { _innerBus innerBus; _mainThreadContext SynchronizationContext.Current; } public void PublishT(T eventData) where T : IEvent { if (SynchronizationContext.Current _mainThreadContext) { _innerBus.Publish(eventData); } else { _mainThreadContext.Post(_ _innerBus.Publish(eventData), null); } } }在实际项目中采用这种事件总线架构后我们发现测试覆盖率提升了40%组件复用率显著提高新功能的集成时间减少了约30%。特别是在大型项目中清晰的组件边界和显式的依赖关系使得团队协作更加高效。

相关文章:

别再滥用单例了!在Unity中实现一个轻量级、可测试的事件总线(Event Bus)系统

重构Unity事件系统:从单例依赖到可测试事件总线的进阶实践 在游戏开发中,我们经常遇到不同组件间需要通信的场景。传统做法是使用GameManager单例或静态类来全局传递数据,但这种做法会导致代码高度耦合、难以测试和维护。想象一下&#xff0c…...

从“看见”到“照见”:武印视界如何重构东方武道的沉浸式表达

在信息过载、注意力成为稀缺资源的当下,人们习惯了“看见”——看见别人的生活、看见算法推送的成功、看见屏幕上不断刷新的胜负。但真正稀缺的,是“照见”:在对手的眼睛里看见自己的恐惧,在胜者的泪水里看见自己的渴望&#xff0…...

终极指南:如何通过智能鼠标宏配置解锁PUBG精准射击的完整潜力

终极指南:如何通过智能鼠标宏配置解锁PUBG精准射击的完整潜力 【免费下载链接】logitech-pubg PUBG no recoil script for Logitech gaming mouse / 绝地求生 罗技 鼠标宏 项目地址: https://gitcode.com/gh_mirrors/lo/logitech-pubg 你是否在《绝地求生》的…...

这个OCR镜像真香!无需编码基础,可视化界面操作超简单

这个OCR镜像真香!无需编码基础,可视化界面操作超简单 1. 为什么选择这个OCR镜像 在日常工作和生活中,我们经常需要从图片中提取文字内容。无论是扫描的文档、拍摄的发票,还是路牌标识,手动输入这些文字既费时又容易出…...

LM在教育场景的应用:美术教学中AI辅助人像构图与光影教学可视化

LM在教育场景的应用:美术教学中AI辅助人像构图与光影教学可视化 1. 引言:AI如何改变美术教育 传统美术教学中,人像构图与光影表现一直是教学难点。学生需要大量时间练习才能掌握这些抽象概念,而教师也面临示范作品制作耗时、难以…...

3分钟破解QQ音乐格式封锁:qmcdump音频解密完整指南

3分钟破解QQ音乐格式封锁:qmcdump音频解密完整指南 【免费下载链接】qmcdump 一个简单的QQ音乐解码(qmcflac/qmc0/qmc3 转 flac/mp3),仅为个人学习参考用。 项目地址: https://gitcode.com/gh_mirrors/qm/qmcdump 你是否遇…...

别再东拼西凑了!我为你整理了一份超全的嵌入式开发知识图谱(含学习路线与避坑指南)

嵌入式开发者的终极成长指南:从菜鸟到架构师的系统化进阶路线 当我在2015年第一次接触STM32开发板时,面对满屏的寄存器配置和晦涩的数据手册,曾一度怀疑自己是否选错了职业方向。八年后的今天,当我带领团队完成第五代工业控制器开…...

nli-MiniLM2-L6-H768企业实操:NLI服务接入内部知识库语义检索链路

nli-MiniLM2-L6-H768企业实操:NLI服务接入内部知识库语义检索链路 1. 模型概述 nli-MiniLM2-L6-H768是一个专为自然语言推理(NLI)与零样本分类设计的轻量级交叉编码器(Cross-Encoder)模型。它在保持接近BERT-base精度的同时,通过6层768维的紧凑结构实现…...

Vue-Office终极指南:5分钟实现专业级Office文档预览方案

Vue-Office终极指南:5分钟实现专业级Office文档预览方案 【免费下载链接】vue-office 支持word(.docx)、excel(.xlsx,.xls)、pdf、pptx等各类型office文件预览的vue组件集合,提供一站式office文件预览方案,支持vue2和3,也支持Reac…...

别再踩坑了!Windows 10/11上SQL Server 2019 Developer版保姆级安装与SSMS配置全流程

Windows 10/11上SQL Server 2019 Developer版零失败安装指南 第一次在Windows上安装SQL Server 2019 Developer版时,我遇到了各种奇怪的问题——安装程序卡在某个步骤、服务无法启动、SSMS连接失败...后来才发现,很多问题其实都有简单的预防措施。本文将…...

Vue.js组件通信Emit处理长列表滚动到底部后的数据请求

<p>应使用 Intersection Observer 或 scrollTop clientHeight ≥ scrollHeight - threshold&#xff08;阈值10~50px&#xff09;判断触底&#xff0c;配合节流与 isLoading/noMore 状态守卫防重复请求&#xff0c;并在父组件用 concat 更新列表、$nextTick 后滚动到底部…...

如何彻底解决C盘爆满问题?Windows Cleaner终极清理方案

如何彻底解决C盘爆满问题&#xff1f;Windows Cleaner终极清理方案 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服&#xff01; 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner 你是不是也经常遇到这样的烦恼&#xff1a;电脑…...

8大主流网盘直链下载助手:免费获取真实下载链接的完整指南

8大主流网盘直链下载助手&#xff1a;免费获取真实下载链接的完整指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / …...

瑞米布替尼Remibrutinib改善慢性自发性荨麻疹瘙痒风团的真实症状控制效果

慢性自发性荨麻疹&#xff08;ChronicSpontaneousUrticaria,CSU&#xff09;作为一种常见的特发性综合征&#xff0c;以反复出现的瘙痒、风团及血管性水肿为特征&#xff0c;且症状持续超过6周。该疾病不仅给患者带来身体上的不适&#xff0c;还严重影响其睡眠、日常生活及心理…...

CSS3 按钮悬停时显示手型光标(cursor- pointer)的正确写法

CSS 中 cursor: pointer 需配合伪类 :hover 使用&#xff0c;直接在 button 元素上声明不会生效&#xff1b;正确做法是为 button:hover 单独设置该样式。 css 中 cursor: pointer 需配合伪类 :hover 使用&#xff0c;直接在 button 元素上声明不会生效&#xff1b;正确做…...

告别盲人摸象:手把手教你用TDR(时域反射技术)快速定位PCB上的开路和短路

告别盲人摸象&#xff1a;手把手教你用TDR&#xff08;时域反射技术&#xff09;快速定位PCB上的开路和短路 在电子工程领域&#xff0c;PCB故障排查常常像一场没有地图的寻宝游戏。当一块价值不菲的多层板出现信号传输异常时&#xff0c;传统方法往往需要工程师像"盲人摸…...

新的契约:人机协作的设计原则

一开始我觉得这个概念有点抽象&#xff0c;但读完后发现&#xff0c;它其实回答的是一个很现实的问题&#xff1a; 当 AI 不只是回答问题&#xff0c;而是开始自己规划、执行任务时&#xff0c;人和 AI 应该怎么分工&#xff1f; 这篇文章&#xff0c;我想从初学者角度&#…...

如何5分钟搞定游戏模组管理:XXMI启动器终极指南

如何5分钟搞定游戏模组管理&#xff1a;XXMI启动器终极指南 【免费下载链接】XXMI-Launcher Modding platform for GI, HSR, WW and ZZZ 项目地址: https://gitcode.com/gh_mirrors/xx/XXMI-Launcher 还在为复杂的游戏模组配置而烦恼吗&#xff1f;XXMI启动器作为一款创…...

城通网盘限速破解终极指南:3分钟学会10倍下载加速

城通网盘限速破解终极指南&#xff1a;3分钟学会10倍下载加速 【免费下载链接】ctfileGet 获取城通网盘一次性直连地址 项目地址: https://gitcode.com/gh_mirrors/ct/ctfileGet 你是否曾因城通网盘的非会员限速而抓狂&#xff1f;下载一个1GB文件需要等待数小时&#x…...

802.11帧结构

一、802.11帧从类型上分为哪几种管理帧Management Frame&#xff1a;用来“管理”无线网络的建立和维护。典型例子&#xff1a;BeaconProbe RequestProbe ResponseAuthenticationDeauthenticationAssociation RequestAssociation Response这些帧不是拿来传业务数据的&#xff0…...

3D 地球卫星轨道可视化平台开发 Day11(筛选指定卫星字段生成适配前端的JSON数据)

在3D地球卫星轨道可视化平台的开发过程中&#xff0c;我们往往会收录全球海量卫星数据&#xff0c;但实际前端渲染时&#xff0c;并不需要全部数据——更多时候&#xff0c;我们只需要聚焦那些知名度高、应用广泛的核心卫星系列&#xff0c;既能精简数据量、提升前端加载速度&a…...

为什么你的开关电源效率低?可能是没用对肖特基二极管(附型号推荐)

为什么你的开关电源效率低&#xff1f;可能是没用对肖特基二极管&#xff08;附型号推荐&#xff09; 在开关电源设计中&#xff0c;效率是工程师们永恒的追求。然而&#xff0c;许多设计者在优化拓扑结构、选择高性能MOSFET和控制器时&#xff0c;往往忽略了一个看似简单却至关…...

思源黑体TTF字体:免费商用的多语言排版终极解决方案

思源黑体TTF字体&#xff1a;免费商用的多语言排版终极解决方案 【免费下载链接】source-han-sans-ttf A (hinted!) version of Source Han Sans 项目地址: https://gitcode.com/gh_mirrors/so/source-han-sans-ttf 思源黑体TTF是一个专门为设计师和开发者打造的免费商用…...

别再被‘透传’忽悠了:用ESP8266和CC3200模块做IoT项目时,这些坑你得提前知道

别再被‘透传’忽悠了&#xff1a;用ESP8266和CC3200模块做IoT项目时&#xff0c;这些坑你得提前知道 在智能家居和小型传感器节点开发中&#xff0c;ESP8266和CC3200这类UART串口WiFi模块因其低成本、易用性备受青睐。许多开发者会被模块的"透传"模式吸引——只需简…...

5步解决Windows游戏手柄兼容问题:DS4Windows完全配置指南

5步解决Windows游戏手柄兼容问题&#xff1a;DS4Windows完全配置指南 【免费下载链接】DS4Windows Like those other ds4tools, but sexier 项目地址: https://gitcode.com/gh_mirrors/ds/DS4Windows 还在为心爱的PlayStation手柄无法在PC上使用而烦恼吗&#xff1f;当你…...

智能游戏伴侣BetterGI:让原神体验全面升级的终极解决方案

智能游戏伴侣BetterGI&#xff1a;让原神体验全面升级的终极解决方案 【免费下载链接】better-genshin-impact &#x1f4e6;BetterGI 更好的原神 - 自动拾取 | 自动剧情 | 全自动钓鱼(AI) | 全自动七圣召唤 | 自动伐木 | 自动刷本 | 自动采集/挖矿/锄地 | 一条龙 | 全连音游 …...

Windows Cleaner终极指南:5分钟解决C盘爆红问题,快速释放空间提升电脑性能

Windows Cleaner终极指南&#xff1a;5分钟解决C盘爆红问题&#xff0c;快速释放空间提升电脑性能 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服&#xff01; 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner Windows Cleane…...

Markdown写作进阶:Typora + PicGo打造无缝图文体验

Markdown写作进阶&#xff1a;Typora PicGo打造无缝图文体验 在数字化写作时代&#xff0c;Markdown以其简洁高效的特性成为内容创作者的利器。传统Markdown工具常面临图片管理繁琐、排版实时性不足等问题。本文将介绍如何通过Typora与PicGo的组合&#xff0c;实现从写作到发…...

SAP采购订单收货后,数据到底进了EKBE还是MSEG?一张图帮你理清核心逻辑

SAP采购订单收货后的数据流向解析&#xff1a;EKBE与MSEG表的本质区别 刚接触SAP物料管理的朋友&#xff0c;第一次看到采购订单收货后生成的凭证数据&#xff0c;往往会陷入困惑——这些数据到底进了EKBE还是MSEG&#xff1f;这两个表看起来都记录了采购相关的信息&#xff0c…...

Cadence PowerDC新手避坑指南:从导入文件到生成Powertree的完整流程

Cadence PowerDC新手避坑指南&#xff1a;从导入文件到生成Powertree的完整流程 第一次打开PowerDC时&#xff0c;面对密密麻麻的菜单和参数&#xff0c;很多新手工程师都会感到无从下手。电源完整性仿真作为PCB设计的关键环节&#xff0c;直接影响着系统稳定性和功耗效率。本…...