跳到主要内容

4 篇博文 含有标签「Best Practices」

Development best practices and tips

查看所有标签

跨场景事件:没人聊但人人踩的持久化问题

TinyGiants
GES Creator & Unity Games & Tools Developer

你的 AudioManager 播放背景音乐。它订阅了 OnLevelStart,在玩家进入新区域时切换曲目。你把 AudioManager 放在 DontDestroyOnLoad 对象上保持跨场景存活。开发期间一切正常,因为你一直在同一个场景里测试。

然后有人第一次从关卡 1 加载到关卡 2。音乐不切了。AudioManager 还活着 —— DontDestroyOnLoad 尽职了 —— 但事件订阅没有存活下来。或者更糟:旧的订阅还在,指向关卡 1 里已经被销毁的事件触发方,下次有东西尝试调用它时你会在游戏中途收到一个 MissingReferenceException

这就是持久化问题,每个有多个场景的 Unity 项目迟早都会撞上。

上线才发现的事件系统坑:内存泄漏、数据污染、递归陷阱

TinyGiants
GES Creator & Unity Games & Tools Developer

你一直在每次测试 5 分钟。跑得好好的。然后 QA 提了个 Bug:"30 分钟游玩过程中内存持续增长。加载 6 个场景后帧率从 60 降到 40。"你去 Profile。一个应该只有 12 个监听器的事件上注册了 847 个。每次场景加载都添加了新的订阅但从未移除旧的。对象被销毁了,但它们的委托引用还活着,把已经死掉的 MonoBehaviour 钉在内存里,垃圾回收器碰都碰不到。

或者这个:"第二次进入 Play Mode 后血量数值不对。第一次运行没问题。"你按 Play,测战斗,停止。再按 Play,玩家以 73 HP 开始而不是 100。上一个会话的 ScriptableObject 状态泄漏了,因为没人重置它。

再或者经典的:游戏卡了 3 秒,然后 Unity 崩溃。事件 A 的监听器触发了事件 B,事件 B 的监听器触发了事件 A。栈溢出。但有时候它不崩溃 —— 只是卡住,在一个不产生任何可见错误的死循环里吃 CPU。

这些不是假设。这些是我见过的上线游戏里的真实 Bug。根本原因都一样:事件系统的模式单独看没问题,但到了规模化的时候就崩了。

执行顺序的隐患:'谁先响应'背后的 Bug

TinyGiants
GES Creator & Unity Games & Tools Developer

玩家受到 25 点伤害。血量系统扣血。UI 刷新血条。但血条显示的是 100 而不是 75。你盯着代码看了 20 分钟才反应过来:UI 的监听器比血量系统的监听器先执行了。UI 读到的是旧值,渲染完了,血量系统才完成扣血。等数据正确的时候,这一帧已经画完了。

你刚刚发现了"执行顺序 Bug"。如果你用事件驱动架构上过线,八成已经在不知情的情况下发布过好几个这样的 Bug。它们在测试环境下表现正常 —— 因为脚本恰好按正确顺序初始化了 —— 然后到了线上就炸,因为 Unity 换了个加载顺序。

这不是什么边缘情况,而是大多数事件系统的结构性缺陷 —— 包括 Unity 的 UnityEvent 和标准 C# event 委托。一旦搞明白为什么,你就再也回不去了。

当你的项目有 200 个事件:为什么组织管理会崩溃

TinyGiants
GES Creator & Unity Games & Tools Developer

你开了一个新 Unity 项目,创建了十个事件。OnPlayerDeathOnScoreChangedOnLevelComplete。命名合理,扔进一个文件夹,继续干活。日子很美好。你脑子里装得下整个事件结构。

快进六个月。你有了 200 个事件。Project 窗口变成了一面 ScriptableObject 文件墙。你需要 OnPlayerHealthDepleted——还是叫 OnPlayerHPLow?还是 OnPlayerHealthZero?你在列表里上下滚动,眯着眼看一堆全以 OnPlayer 开头的名字。三分钟后你放弃了,直接创建了一个新的,因为你甚至不确定你要的那个事件是不是已经存在了。

每个事件驱动的 Unity 项目最终都会走到这一步。不是因为事件模式本身有问题,而是因为没人构建过大规模管理事件的工具。Unity 给了你 Animation 窗口、Shader Graph、Timeline、Input System 调试器。事件呢……只有 Project 窗口。