← ClaudeAtlas

unity-csharplisted

写 Unity C# 时使用。生命周期、协程、GC、性能、序列化的实战规范。
Wade-DevCode/awesome-coding-skills-cn · ★ 3 · AI & Automation · score 78
Install: claude install-skill Wade-DevCode/awesome-coding-skills-cn
# Unity C# 最佳实践 ## 何时用 - 开始编写任何 Unity MonoBehaviour 或 ScriptableObject 脚本前。 - Review 他人 Unity C# 代码,发现有 Update 里调 GetComponent 或每帧 new 对象时。 - 接到性能优化任务:帧率抖动、GC Alloc 频繁、Profiler 里 Update 调用栈过深。 - 设计新的配置数据结构,考虑用 ScriptableObject 还是 public 字段时。 - 协程逻辑出现卡死、泄漏、或销毁对象后仍在跑的诡异现象时。 ## 核心规则 ### 1. 慎用 Update:每帧逻辑最小化 **规则:** Update 里只放真正需要每帧响应的逻辑;能用事件、协程或 InvokeRepeating 驱动的,就不放进 Update;任何 GetComponent、Find、tag 比较一律在 Awake/Start 里缓存,不在 Update 里调用。 **为什么:** AI 和新手最常见的模式是把所有逻辑堆进 Update——"反正每帧都会跑到"。结果是:GetComponent 每帧反射查找,100 个对象就是 100 次反射;倒计时用 `timer -= Time.deltaTime` 没问题,但 UI 刷新、动画触发、状态机判断全挤在里面,Profiler 一看 Update 占 80% CPU。更隐蔽的问题:新人把 `FindObjectOfType<GameManager>()` 放进 Update,场景里一有几十个对象就掉帧,排查时根本不知道从哪查。 **怎么做:** - 初始化引用全部放 `Awake`(自身组件)或 `Start`(跨对象引用)。 - 定时逻辑用 `InvokeRepeating` 或协程 `WaitForSeconds`,不用 `Update` 里的计数器模拟低频事件。 - 状态变化用 C# event / UnityEvent 通知,订阅方在变化时响应,而不是每帧 poll `if (state == X)`。 - 真正需要每帧的(移动插值、输入读取),保留在 Update,但代码量要极简,复杂运算提取为方法并在注释说明频率必要性。 --- ### 2. 防 GC 抖动:高频路径零分配 **规则:** Update、FixedUpdate、协程的热路径里禁止出现 `new`(含 LINQ、字符串拼接、装箱);高频复用的对象用对象池管理;容器在初始化时预分配容量。 **为什么:** Unity 使用 Mono / IL2CPP 的 GC,GC 触发时会造成明显的帧率刺尖(spike)。AI 写的代码里最常见的杀手:`string.Format($"Score: {score}")` 每帧执行一次,`enemies.Where(e => e.isAlive).ToList()` 每帧生成一个新 List,`new Vector3(...)` 看起来是值类型不会 GC——但装箱到 `object` 参数时就会。新手则喜欢在子弹生成时 `Instantiate`、销毁时 `Destroy`,百发子弹就是百次 GC 压力。 **怎么做:** - 用 `StringBuilder` 或 `TMP_Text.SetText(format, arg)` 替代字符串拼接。 - LINQ 仅用于编辑器工具或低频初始化代码,运行时热路径改用显式 for 循环。 - 子弹、特效、UI 元素用 `O