From deab7535b2f44bdd4dc30eda5a82047c1ab7f341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=B5=A9=E5=A4=A7=E4=BA=BA?= Date: Thu, 13 Aug 2026 15:50:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20skills/apply-unity-csharp-?= =?UTF-8?q?style/SKILL.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/apply-unity-csharp-style/SKILL.md | 192 +++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 skills/apply-unity-csharp-style/SKILL.md diff --git a/skills/apply-unity-csharp-style/SKILL.md b/skills/apply-unity-csharp-style/SKILL.md new file mode 100644 index 0000000..c2e7ecd --- /dev/null +++ b/skills/apply-unity-csharp-style/SKILL.md @@ -0,0 +1,192 @@ +--- +name: apply-unity-csharp-style +description: 按统一的 Unity C# 代码风格编写、修改和审查代码,覆盖排版、命名、成员组织、控制流、注释、异步与协程、生命周期、资源所有权,以及状态机、行为树、对象池、事件、UI 和编辑器工具等实现。用于用户要求应用本代码风格、统一 Unity C# 实现,或检查代码风格一致性时。 +--- + +# 应用 Unity C# 代码风格 + +## 执行原则 + +1. 先理解目标行为、公共 API、序列化数据和资源所有权,再调整实现。 +2. 对新代码完整应用本规范;对已有代码只修改任务涉及的区域,不进行无关格式化。 +3. 正确性、公共 API 兼容性和序列化兼容性高于格式统一。 +4. 不把兼容性保留的旧命名或旧排版扩散到新代码。 +5. 审查时区分明确偏离、兼容性保留和可接受的上下文差异,不把个人偏好报告为缺陷。 + +## 文件格式与排版 + +- 使用 UTF-8 无 BOM 和 CRLF 行尾。 +- 使用 4 个空格缩进,不使用 Tab。 +- 使用 Allman 大括号;类型、方法、包含访问器实现的属性和控制块的 `{` 独占一行。无访问器实现的自动属性除外。 +- 所有控制块都写大括号,包括单行 `if`、`else`、`for`、`foreach` 和 `while`。 +- 不声明 `namespace`。 +- 将 `using` 放在文件顶部,移除未使用的引用。 +- 逗号后加空格,二元运算符两侧加空格。 +- 用空行分隔字段、生命周期、公开 API、私有实现和清理逻辑等不同职责段。 +- 定义方法、构造函数或本地函数时,将完整参数列表与函数声明保持在同一行;即使参数较多,也不在参数之间换行。 +- 函数调用和普通表达式过长时,可以按清晰的逻辑单元拆行,并对齐闭合括号。 +- 仅在大量同类 API 确实需要导航时使用 `#region`,短类不使用。 + +## 命名 + +- 类型、枚举、枚举成员、方法和常规属性使用 `PascalCase`。 +- 接口以 `I` 开头。 +- 私有字段和受保护字段使用无前缀 `camelCase`;不添加 `_`、`m_` 等前缀。 +- 局部变量和参数使用 `camelCase`。 +- 泛型类型参数使用 `T` 或角色明确的 `TContext`、`TNode`、`TAction`、`TItem`。 +- 返回 `Task` 或 `Task` 的方法以 `Async` 结尾。 +- 布尔字段和属性使用状态或判断语义,如 `isRunning`、`hasLoaded`、`CanClose`。 +- 集合名称同时表达内容和用途,如 `activeItems`、`pendingCallbacks`、`handleCache`。 +- 回调参数使用 `callback`、`onComplete`、`onError`;事件处理方法使用 `On<事件>` 或 `Handle<事件>`。 +- 构造参数与字段同名时使用 `this.field = field`,不要为字段添加前缀。 +- 使用职责后缀表达类型角色,如 `Manager`、`System`、`Data`、`Context`、`Operation`、`Node`、`Handle`、`Window`、`Tool`、`Generator`、`Monitor`、`Container`。 + +## 字段、属性与常量 + +- 需要在 Inspector 中暴露的字段声明为 `public camelCase`。 +- 普通公开属性使用 `PascalCase`,并尽量限制 setter,如 `{ get; }` 或 `{ get; private set; }`。 +- 自动属性的 `get`/`set` 均无方法体时,将属性声明和所有访问器保持在同一行,如 `public int Count { get; set; }`、`public bool IsReady { get; private set; }` 和 `public string Name { get; }`。 +- 任一属性访问器包含方法体或表达式逻辑时,使用多行 Allman 格式,不把实现压缩到属性声明行。 +- 轻量数据容器可使用公开字段;同一数据类型内保持命名方式一致。 +- 私有常量使用 `PascalCase`。 +- 私有 `static readonly` 字段使用 `camelCase`。 +- 不为单纯追求封装而改名或迁移已有序列化字段。 +- 不直接暴露内部可变集合;需要诊断或查询时返回副本或只读视图。 + +## 文件与成员组织 + +按以下顺序组织成员: + +1. `using`。 +2. 紧密相关的文件级枚举、接口或数据类型。 +3. 主类型。 +4. 嵌套辅助类型。 +5. 常量、静态字段、实例字段、属性和事件。 +6. 构造函数和 Unity 生命周期方法。 +7. 初始化方法和公开 API。 +8. 私有执行方法、回调和工具方法。 +9. 取消订阅、停止协程、释放资源等清理方法。 + +让 `MonoBehaviour`、`EditorWindow` 和主要公开类型与文件名一致。只有类型紧密耦合且不值得独立复用时,才在同一文件放置辅助类型。 + +## C# 表达习惯 + +- 右侧类型清晰时使用 `var`,如 Unity API 返回值、LINQ 结果和 `out var`。 +- 类型本身承担重要语义或右侧不够明显时写显式类型。 +- 集合初始化可使用目标类型 `new()`;在同一字段组或方法内保持一致。 +- 使用对象初始化器表达数据对象或上下文的一次性装配。 +- 使用字符串插值生成带变量的日志和异常文本。 +- 使用 `?.` 调用可选回调和可释放对象。 +- 使用 `nameof` 代替可由编译器提供的类型名或成员名字符串。 +- Unity 对象可使用 `if (!obj)` 判断;普通托管对象使用 `obj == null`。 +- 需要同时查询并读取字典值时使用 `TryGetValue`。 +- 表达式体只用于极简单的只读属性;方法使用完整块体。 +- 有意不等待 `Task` 时显式写 `_ =`。 +- 避免 `async void`;只有事件回调或无法改变的接口签名允许使用。 + +## 控制流与错误处理 + +- 用提前返回处理空值、重复状态、无操作分支和失败前置条件,保持主路径扁平。 +- 协程失败或取消时使用 `yield break`。 +- 状态必须恢复时使用 `try`/`finally`。 +- 异步流程获得资源所有权后,使用 `try`/`catch` 在失败分支回滚句柄、实例或引用计数。 +- 对调用方违反契约、类型不匹配和非法状态抛出异常。 +- 对可恢复的运行时失败记录日志、清理状态,并返回失败结果或触发错误回调。 +- 不吞掉异常;若必须忽略单项失败,用注释说明原因并保证整体流程仍可预测。 + +## 注释、日志与异常文本 + +- 新注释、XML 文档、日志和异常文本使用简体中文;技术专名保留英文。 +- 为公共契约、资源所有权、生命周期、状态切换和不直观约束编写 `/// `。 +- 需要时补充 `param`、`typeparam` 和 `returns`,说明调用约束而不是复述标识符。 +- 行注释放在相关代码上方,解释目的、边界和原因,不逐行翻译代码。 +- 不保留被注释掉的旧实现;自动生成标记和任务明确要求保留的替代实现除外。 +- 按严重程度使用 `Debug.Log`、`Debug.LogWarning`、`Debug.LogError` 和 `Debug.LogException`。 +- 日志和异常包含定位问题所需的对象名、资源键、状态类型或操作阶段。 + +## Unity 生命周期与清理 + +- Unity 生命周期函数声明为 `private`。 +- 在 `OnEnable` 订阅的事件必须在 `OnDisable` 取消订阅。 +- 在进入状态时订阅的回调必须在退出状态时取消订阅。 +- 停止协程后将对应字段设为 `null`;正常结束、提前结束和销毁路径保持一致。 +- 静态实例只在确实指向当前对象时清空。 +- 销毁对象前先处理子对象、集合登记、回调和资源租约。 +- 编辑器事件、运行时事件、`UnityEvent` 和自定义事件都要成对移除。 +- 仅用于编辑器或开发构建的监视逻辑使用条件编译包围完整类型或完整功能块。 + +## 异步与资源所有权 + +- 使用 `Task`/`Task` 表示异步结果,使用小型包装方法兼容回调式 API。 +- 合并同一键的并发请求,使用 pending 集合或回调列表记录等待者。 +- 完成、失败和取消后都移除 pending 状态。 +- 每次获取句柄、租约或引用计数都必须有对应释放或失败回滚。 +- 获得资源后先记录所有权,再把结果暴露给其他对象。 +- 缓存替换、对象销毁和异常退出时释放旧资源。 +- 回调本身可能抛出异常时隔离回调错误,避免破坏内部清理流程。 + +## 常用架构模式 + +### 状态机 + +- 用强类型上下文承载共享数据。 +- 状态在创建阶段缓存状态机,在进入阶段启动工作,在退出阶段停止协程并解除回调。 +- 使用延迟状态请求,在安全边界执行切换,避免在回调中递归切换。 +- 同一帧只接受一个有效切换请求,并由判断顺序表达优先级。 +- 失败状态通过上下文统一记录,再进入收尾状态。 + +### 行为树 + +- 节点按需要实现进入、更新、退出、重置、停止、暂停和恢复阶段。 +- 通过强类型黑板键读写共享数据,通过枚举返回节点状态。 +- 复合节点添加子节点时设置父节点和所属行为树。 +- 基类包含通用处理时,重写方法先调用 `base`。 + +### 对象池 + +- 使用 `Get`/`Push` 表达取出和归还,使用 `Create`、`EnsureCapacity` 和 `Remove` 表达生命周期操作。 +- 取出 Unity 对象后恢复激活状态、父节点、位置、旋转和缩放。 +- 归还对象时设置池父节点并隐藏对象。 +- 异步创建对象池时合并并发回调,并处理创建过程中收到移除请求的情况。 + +### 事件 + +- 事件值使用 `On<事件>` 或明确动作名称。 +- 订阅、取消订阅和分发方法的泛型参数数量保持对称。 +- 订阅方在启用或初始化时订阅,在禁用或销毁时取消订阅。 + +### UI + +- 按控件绑定、初始化、重置、关闭四个阶段组织 UI 生命周期。 +- 异步打开 UI 时先合并重复请求,再创建对象、设置父节点、初始化、登记资源所有权并触发回调。 +- 关闭 UI 时先关闭后代,再调用关闭逻辑、销毁对象、移除登记并释放资源。 +- 控件扩展方法使用动作型名称,如 `SetText`、`GetImage`、`AddOnClick`。 + +### 编辑器工具与代码生成 + +- 编辑器入口使用 `[MenuItem]`。 +- 窗口状态在 `OnEnable` 恢复,在 `OnDisable` 保存。 +- 生成器先验证输入和输出,再生成内容,最后保存并刷新资源数据库。 +- 自动生成内容使用明确标记或 region 边界;刷新时只替换生成区域,不覆盖手写区域。 +- XML 文档进入生成代码前进行转义。 + +## 兼容性 + +- 保留已有公共类型、方法、事件、序列化字段、资源键和生成区标记。 +- 如果必须修正公开命名,提供兼容包装器或明确迁移方案,不直接删除旧入口。 +- 新标识符使用本规范的命名方式,不复制旧代码中的拼写错误、全大写常量、下划线字段或紧凑排版。 +- 不因风格统一改变行为、异常语义、资源所有权或生命周期顺序。 + +## 审查清单 + +- [ ] 使用 UTF-8 无 BOM、CRLF、4 空格和 Allman 大括号。 +- [ ] 未新增 `namespace`,所有控制块都有大括号。 +- [ ] 函数定义的完整参数列表与声明保持在同一行。 +- [ ] 无访问器实现的自动属性保持单行;包含访问器实现的属性使用多行 Allman 格式。 +- [ ] 类型、方法、属性、字段、参数、泛型参数和异步方法命名正确。 +- [ ] 成员按职责组织,没有无关格式化。 +- [ ] 新注释、日志和异常文本使用简体中文并包含必要上下文。 +- [ ] 事件、协程、句柄、租约、引用计数和 Unity 对象在成功与失败路径都完成清理。 +- [ ] 状态机、行为树、对象池、事件、UI 或编辑器工具遵循对应模式。 +- [ ] 未覆盖手写区域,未破坏公共 API 或序列化兼容性。 +- [ ] 未把兼容性保留的旧风格扩散到新代码。