Files
2026-10-14 19:33:29 +08:00

114 lines
5.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: unity-development-rules
description: Unity 开发规则(Unity Vision 项目)。在本项目里写或改 C# 脚本、预制体、场景时适用,包含节点引用接线等硬性约定。
---
# Unity 开发规则
本项目的硬性约定。动手写脚本、改预制体或场景之前先对照这里。
## 节点引用必须在 Inspector / 预制体里指定,不能硬编码到代码
脚本要访问场景或预制体里的节点(Transform、Text、Image、其他组件……)时,一律用 `[SerializeField]` 字段,由 Inspector 或预制体把引用接好。
**不允许**——在代码里按名字 / 标签 / 路径找节点:
```csharp
// ❌ 节点名字写死在代码里
target = transform.Find("FPS").GetComponent<TMP_Text>();
target = GameObject.Find("FPS").GetComponent<TMP_Text>();
foreach (var t in GetComponentsInChildren<TMP_Text>(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>();
_animation.clip = someClip;
_animation.Play();
// ❌ 运行时自己补一个 Animation 组件
gameObject.AddComponent<Animation>();
```
**为什么**:项目统一一套动画播放方式,动画和程序看同一处。混用会让同一个物体上既有 `Animator` 又有 `Animation`,两边驱动同一批属性,播放互相打架、排查困难。
**例外**:用户明确说明这次要用 `Animation` 才用,且只限那一次改动。
## 只做用户点名要做的事
用户说改哪里就只改哪里。下面这些都算「没说过的事」,不要顺手做:
- 给预制体 / 物体加组件(尤其是为了让自己写的代码能跑起来而补一个组件)
- 加用户没提过的功能或容错(自动回收、时长兜底、默认值兜底……)
- 动用户已经定好的行为或时序(例如特效 / 动画的播放时机)
- 保存场景、保存预制体、改其它资源
不改就做不下去时,先说明「为什么必须动它」并问一句,确认后再动。
## 预制体资产存不下场景对象引用
预制体上的脚本要指向场景里的物体(玩家、UI、相机……)时,预制体资产里这个字段只能是空的,
必须在**每个场景实例上各接一次**;指向资源(预制体、Clip、材质)的字段才可以直接在预制体上设。
接的时候留意目标是不是「真正会移动 / 变化」的那个节点:玩家根节点不动、真正移动的是子节点时,要接子节点。
## 「编译干净」要先确认脚本真的被 Unity 导入
改完脚本后,确认对应的 `*.cs.meta` 已生成、类型能在编辑器程序集里查到,再看编译结果。
否则 Unity 还没导入这个文件,控制台没报错不代表它编译通过。