--- 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); } ``` **边界**:这条只约束游戏运行时代码。编辑器工具、一次性批处理脚本需要按结构查找节点时可以照常查找。 ## Inspector 里的字段名怎么显示成中文 **`[InspectorName]` 管不了普通字段**:Unity 6 的文档对这个特性写的是 "enum value declarations"(只讲枚举值),实测把 `[InspectorName("...")]` 加在 `[SerializeField]` 字段上,字段的 `displayName` 仍是英文原名。别用它来改字段标签。 想让字段在 Inspector 里显示中文,只有这几条路: 1. **中文字段名**(要精确控制标签时用这个)。C# 允许 Unicode 标识符,字段名写中文,Inspector 里就显示这个中文——编辑器不会给中文插空格、术语表也不会翻译它(实测 `NicifyVariableName("可到达半径")` → `可到达半径`)。**改名前必须加 `[FormerlySerializedAs("旧名")]`**:Unity 按字段名反序列化,直接改名会清空 Inspector 里已经接好的引用。 ```csharp // 语义是「玩家可到达的半径」,Inspector 里就显示「可到达半径」 [Tooltip("玩家可到达的半径,单位米")] [FormerlySerializedAs("reachableRadius")] [SerializeField] private float 可到达半径 = 5f; ``` 2. **自定义 Editor**:`[CustomEditor(typeof(Xxx))]` 里 `EditorGUILayout.PropertyField(prop, new GUIContent("可到达半径"))`。字段少又必须换标签时才用,字段一多就是维护负担。 3. **不改标签,用 `[Tooltip]` 补说明**:字段名保持英文(项目现状),鼠标悬停能看到中文说明。字段名本身够清楚时,这条就够了。 **中文名按「这个字段在这个项目里干什么」来定,不要直译英文名**:表示玩家可到达的半径,就写「可到达半径」,不要写成「半径」或「可达半径值」——同一个 Inspector 里经常并列出现好几个半径 / 距离 / 目标,标签本身就得能分清谁是谁。 **顺带一个坑**:编辑器语言是中文时,Unity 会按**内置术语表**翻译常见字段名(`prefab` → 预制件、`radius` → 半径 之类),只覆盖常见词——字段名一旦不是标准词,标签就原样显示英文。所以 Inspector 上看到的中文未必写在代码里,别去 `.cs` 里搜。 ## 动画一律用 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 还没导入这个文件,控制台没报错不代表它编译通过。