Skip to main content
摘要:本文聚焦「Unity 代码毒药:那些毁灭项目的反模式」,梳理核心概念、关键方法与落地实践。
引言:「能跑就行」是通往重构地狱的特快列车。 本文档记录了那些在 Demo 阶段省事,但在生产阶段会导致项目崩盘的代码习惯。

1. 单例滥用 (The Singleton Hell)

单例模式是 Unity 开发者最爱用的模式,也是最容易导致架构腐烂的毒药。

❌ 典型毒药

  • 后果:
    1. 竞态条件 (Race Conditions): AwakeStart 的执行顺序在不同对象间是不确定的(除非手动设置 Script Execution Order,但那是另一个坑)。
    2. 紧耦合: 所有系统都如果不引用 Instance 就无法工作,导致无法单独测试某个模块。
    3. 场景切换崩溃: 忘记处理 DontDestroyOnLoad 导致的重复实例,或者引用了已销毁的单例。

✅ 解药:依赖注入 (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)

  • 不要传递 objectstring 参数。
  • 使用结构体定义事件 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.cs
  • PlayerCombat.cs
  • PlayerAnimation.cs
  • PlayerStats.cs 使用一个 Player 脚本作为外观(Facade)来协调这些组件,或者使用 ECS / 状态机。

7. 滥用 Public 变量

❌ 典型毒药

  • 后果: 任何脚本都能修改 Health。当你发现血量莫名其妙变成 0 时,你根本不知道是哪个脚本改的。

✅ 解药:SerializeField + 属性