--- name: unity-development-rules description: Unity 开发规则(Unity Vision 项目)。在本项目里写或改 C# 脚本、预制体、场景时适用,包含节点引用接线等硬性约定。 --- # Unity 开发规则 本项目的硬性约定。动手写脚本、改预制体或场景之前先对照这里。 ## 节点引用必须在 Inspector / 预制体里指定,不能硬编码到代码 脚本要访问场景或预制体里的节点(Transform、Text、Image、其他组件……)时,一律用 `[SerializeField]` 字段,由 Inspector 或预制体把引用接好。 **不允许**——在代码里按名字 / 标签 / 路径找节点: ```csharp // ❌ 节点名字写死在代码里 target = transform.Find("FPS").GetComponent(); target = GameObject.Find("FPS").GetComponent(); foreach (var t in GetComponentsInChildren(true)) if (t.name == "FPS") return t; ``` **允许**: ```csharp [SerializeField] private TMP_Text target; // ✅ 引用由 Inspector / 预制体提供 ``` **为什么**:节点改名或挪位置以后,硬编码的查找会静默失效——不报错、就是不工作;而且引用关系藏在代码里,看 Inspector 看不出到底接没接上。 **引用为空时**:不要写运行时兜底查找,直接报错,让接线问题第一时间暴露。 ```csharp private void Awake() { if (target == null) Debug.LogError("[Xxx] 没有指定 target,请在 Inspector 里拖入。", this); } ``` **边界**:这条只约束游戏运行时代码。编辑器工具、一次性批处理脚本需要按结构查找节点时可以照常查找。 ## 动画一律用 Animator 播,不用 legacy 的 Animation 组件 要在物体上播放动画时,走 `Animator`(状态 / 触发器 / 参数): ```csharp [SerializeField] private Animator animator; // ✅ 引用由 Inspector / 预制体提供 ``` **不允许**——用老的 `Animation` 组件播动画: ```csharp // ❌ legacy 的 Animation 组件 _animation = GetComponent(); _animation.clip = someClip; _animation.Play(); // ❌ 运行时自己补一个 Animation 组件 gameObject.AddComponent(); ``` **为什么**:项目统一一套动画播放方式,动画和程序看同一处。混用会让同一个物体上既有 `Animator` 又有 `Animation`,两边驱动同一批属性,播放互相打架、排查困难。 **例外**:用户明确说明这次要用 `Animation` 才用,且只限那一次改动。 ## 只做用户点名要做的事 用户说改哪里就只改哪里。下面这些都算「没说过的事」,不要顺手做: - 给预制体 / 物体加组件(尤其是为了让自己写的代码能跑起来而补一个组件) - 加用户没提过的功能或容错(自动回收、时长兜底、默认值兜底……) - 动用户已经定好的行为或时序(例如特效 / 动画的播放时机) - 保存场景、保存预制体、改其它资源 不改就做不下去时,先说明「为什么必须动它」并问一句,确认后再动。 ## 预制体资产存不下场景对象引用 预制体上的脚本要指向场景里的物体(玩家、UI、相机……)时,预制体资产里这个字段只能是空的, 必须在**每个场景实例上各接一次**;指向资源(预制体、Clip、材质)的字段才可以直接在预制体上设。 接的时候留意目标是不是「真正会移动 / 变化」的那个节点:玩家根节点不动、真正移动的是子节点时,要接子节点。 ## 「编译干净」要先确认脚本真的被 Unity 导入 改完脚本后,确认对应的 `*.cs.meta` 已生成、类型能在编辑器程序集里查到,再看编译结果。 否则 Unity 还没导入这个文件,控制台没报错不代表它编译通过。