☠️ Unity 代码毒药:那些毁灭项目的反模式
“能跑就行” 是通往重构地狱的特快列车。 本文档记录了那些在 Demo 阶段省事,但在生产阶段会导致项目崩盘的代码习惯。
1. 单例滥用 (The Singleton Hell)
单例模式是 Unity 开发者最爱用的模式,也是最容易导致架构腐烂的毒药。❌ 典型毒药
- 后果:
-
竞态条件 (Race Conditions):
Awake和Start的执行顺序在不同对象间是不确定的(除非手动设置 Script Execution Order,但那是另一个坑)。 -
紧耦合: 所有系统都如果不引用
Instance就无法工作,导致无法单独测试某个模块。 -
场景切换崩溃: 忘记处理
DontDestroyOnLoad导致的重复实例,或者引用了已销毁的单例。
-
竞态条件 (Race Conditions):
✅ 解药:依赖注入 (Dependency Injection) 或 服务定位器 (Service Locator)
-
轻量级解药: 使用一个总入口(Bootstrapper)。
- 工业级解药: 使用 Zenject / VContainer 等 DI 框架。
2. 帧率杀手:Update 中的查找 (The “Find” Trap)
❌ 典型毒药
- 后果: 手机发烫,掉帧严重。
GameObject.Find是非常昂贵的操作。
✅ 解药:缓存引用 (Cache References)
3. 意大利面条事件 (Spaghetti Event Systems)
❌ 典型毒药
- C# Action 满天飞: A 注册了 B,B 注册了 C,C 又注册了 A。
- 无法追踪: 按下攻击键,主角没动。你想 Debug,发现这个事件被 50 个地方监听了,不知道是哪个监听器把怪打死了或者拦截了输入。
✅ 解药:强类型事件总线 (Strongly Typed Event Bus)
-
不要传递
object或string参数。 -
使用结构体定义事件 payload。
- 工具推荐: 使用 UniRx 或由我们自己维护的轻量级 EventBus,且必须带有 Logger 功能,能打印出“谁在什么时候发出了什么事件”。
4. 协程幽灵 (The Coroutine Ghost)
❌ 典型毒药
✅ 解药:严防死守
- 进阶: 使用
UniTask代替协程,支持.ToCancellationToken(this.GetCancellationTokenOnDestroy())自动管理生命周期。
5. 垃圾制造者:Update 中的 LINQ (LINQ in Hot Paths)
❌ 典型毒药
- 后果:
OrderBy会产生大量的临时对象(委托、闭包、迭代器),导致 Garbage Collector (GC) 频繁触发(Stop-The-World),游戏卡顿。
✅ 解药:原生循环
在Update 等热路径(Hot Path)中,老老实实写 for 循环。
6. 上帝类 (The God Class)
❌ 典型毒药
PlayerController.cs有 3000 行代码。- 它负责移动、攻击、播放动画、扣血、UI更新、甚至播放音效。
- 后果: 改一行代码,整个主角崩了。多人协作时,这个文件永远在冲突。
✅ 解药:组合优于继承 (Composition over Inheritance)
拆分!PlayerMovement.csPlayerCombat.csPlayerAnimation.csPlayerStats.cs使用一个Player脚本作为外观(Facade)来协调这些组件,或者使用 ECS / 状态机。
7. 滥用 Public 变量
❌ 典型毒药
- 后果: 任何脚本都能修改
Health。当你发现血量莫名其妙变成 0 时,你根本不知道是哪个脚本改的。