关于 theone-unity-standards
面向 Unity 6 与 C# 9 项目的开发和代码审查技能,按代码质量、现代 C# 模式、架构及性能设定规范。它提供 VContainer 或 TheOne.DI 等架构指引及审查清单。
主要功能
- 01可空类型与警告处理规范
- 02现代 C# 编码模式指引
- 03依赖注入与事件架构规范
- 04通过 Controller 访问数据
- 05Unity 性能与代码审查清单
适用场景
- 01编写或重构 Unity C# 代码
- 02审查代码变更与拉取请求
- 03设置项目依赖注入和事件架构
- 04检查资源释放与热路径分配
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ the1studio/ theone-training-skills --skill theone-unity-standards安装后在 Claude Code 中输入/theone-unity-standards确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/csharp/linq-patterns.mdmodern-csharp-features.mdperformance-optimizations.mdquality-hygiene.mdreferences/review/architecture-review.mdcsharp-quality.mdperformance-review.mdunity-specifics.mdreferences/unity/data-controllers.mdintegration-patterns.mdsignalbus-events.mdtheone-framework.mdunitask-patterns.mdvcontainer-di.md详细介绍
TheOne Studio Unity 开发规范
⚠️ **Unity 6(C# 9):**所有模式和示例均兼容 Unity 6,该版本使用 C# 9。不会使用 C# 10+ 功能。
技能用途
此技能以 代码质量优先 为原则,贯彻 TheOne Studio 全面的 Unity 开发规范:
优先级 1:代码质量与规范(最重要)
- 可空引用类型、访问修饰符、修复所有警告
- 抛出异常(绝不记录错误日志)
- TheOne.Logging.ILogger(运行时,不设保护、不加前缀、不在构造函数中记录日志)与 Debug.Log(仅限编辑器)
- readonly/const、nameof、using 指令作用域
- 不写行内注释(使用描述性名称)
优先级 2:现代 C# 模式
- 优先使用 LINQ 而不是循环、扩展方法、表达式体
- 空合并运算符、模式匹配
- 现代 C# 功能(records、init、with)
优先级 3:Unity 架构
- VContainer + SignalBus 或 TheOne.DI + Publisher/Subscriber
- Data Controllers 模式(绝不直接访问数据)
- 资源管理、UniTask 异步模式
优先级 4:性能与审查
- LINQ 优化、避免分配
- 代码审查指南
此技能的触发场景
- 编写或重构 Unity C# 代码
- 使用依赖注入实现 Unity 功能
- 处理事件和信号
- 访问或修改游戏数据
- 审查代码变更或拉取请求
- 设置项目架构
快速参考指南
需要哪方面的帮助?
| 优先级 | 任务 | 参考资料 |
|---|---|---|
| 🔴 优先级 1:代码质量(首先检查) | ||
| 1 | 可空类型、访问修饰符、警告、异常 | 代码质量与规范 ⭐ |
| 1 | TheOne.Logging.ILogger(不设保护、不加前缀、不在构造函数中记录日志)与 Debug.Log | 代码质量与规范 ⭐ |
| 1 | readonly/const、nameof、using 作用域、不写行内注释 | 代码质量与规范 ⭐ |
| 🟡 优先级 2:现代 C# 模式 | ||
| 2 | LINQ、扩展方法、var 的用法 | LINQ 模式 |
| 2 | 表达式体、空合并、模式匹配 | 现代 C# 功能 |
| 2 | Records、init、with 表达式 | 现代 C# 功能 |
| 🟢 优先级 3:Unity 架构 | ||
| 3 | VContainer 依赖注入 | VContainer DI |
| 3 | SignalBus 事件系统 | SignalBus 事件 |
| 3 | Data Controllers 模式(绝不直接访问数据) | Data Controllers |
| 3 | Service/Bridge/Adapter 集成 | 集成模式 |
| 3 | TheOne.DI + Publisher/Subscriber 替代方案 | TheOne 框架 |
| 3 | UniTask 异步/await 模式 | UniTask 模式 |
| 🔵 优先级 4:性能与审查 | ||
| 4 | 性能、LINQ 优化、内存分配 | 性能 |
| 4 | 架构审查(DI、事件、控制器) | 架构审查 |
| 4 | C# 质量审查(LINQ、空值处理) | C# 质量 |
| 4 | Unity 专项审查(组件、生命周期) | Unity 专项 |
| 4 | 性能审查(内存分配、热点路径) | 性能审查 |
🔴 关键:代码质量规则(首先检查!)
⚠️ 强制质量标准
始终在编写任何代码之前落实以下要求:
- 启用可空引用类型 - 不允许有可空性警告
- 使用最低限度的访问修饰符 - 默认使用 private
- 修复所有警告 - 对编译器警告零容忍
- 出错时抛出异常 - 绝不记录错误日志,改为抛出异常
- 运行时使用 TheOne.Logging.ILogger - 不使用条件保护(#if)、不添加前缀([prefix])、绝不在构造函数中使用;仅在编辑器脚本中使用 Debug.Log
- 字段使用 readonly - 标记不会重新赋值的字段
- 常量使用 const - 常量应使用 const,而非 readonly
- 字符串使用 nameof - 绝不硬编码属性名或参数名
- 在最深层作用域中使用 using 指令 - 尽可能在方法级别使用 using
- 不写内联注释 - 使用描述性名称;代码应能自我说明
示例:优先落实质量标准
// ✅ EXCELLENT: All quality rules enforced
#nullable enable // 1. Nullable enabled
public sealed class PlayerService
{
private readonly TheOne.Logging.ILogger logger;
private const int MaxHealth = 100;
public PlayerService(TheOne.Logging.ILogger logger)
{
this.logger = logger;
}
public Player GetPlayer(string id)
{
using System.Text.Json;
return players.TryGetValue(id, out var player)
? player
: throw new KeyNotFoundException($"Player not found: {id}");
}
private void LoadGameData()
{
this.logger.Info("Game data loaded");
}
}
#if UNITY_EDITOR
public class EditorTool
{
public void Process()
{
Debug.Log("Processing..."); // 5. Debug.Log OK in editor
}
}
#endif
⚠️ Unity 架构规则(质量标准之后)
选择一种框架技术栈
每个项目只选择一种技术栈:
选项 1:VContainer + SignalBus
- ✅ 使用 VContainer 进行依赖注入
- ✅ 使用 SignalBus 处理事件
- ✅ 在构造函数上使用 [Preserve] 特性
选项 2:TheOne.DI + 发布者/订阅者
- ✅ 使用 TheOne.DI 包装器
- ✅ 使用 IPublisher/ISubscriber 处理事件
- ✅ 在构造函数上使用 [Inject] 特性
通用规则(适用于两种技术栈):
- ✅ 使用数据控制器(绝不直接访问数据)
- ✅ 使用 UniTask 执行异步操作
- ✅ 在 Dispose 中卸载资源
- ✅ 运行时使用 TheOne.Logging.ILogger(不加保护、不添加前缀、不在构造函数中记录日志),仅在编辑器中使用 Debug.Log
简要示例
🔴 质量优先
// ✅ EXCELLENT: Quality rules enforced
#nullable enable
public sealed class PlayerController
{
private readonly TheOne.Logging.ILogger logger;
private const int MaxRetries = 3;
public PlayerController(TheOne.Logging.ILogger logger)
{
this.logger = logger;
}
public Player LoadPlayer(string id)
{
if (!File.Exists(id))
throw new FileNotFoundException($"Player file not found: {id}");
this.logger.Info($"Loading player {id}");
return DeserializePlayer(id);
}
}
#if UNITY_EDITOR
public class EditorHelper
{
public void Log() => Debug.Log("Editor only"); // Debug.Log OK in editor
}
#endif
🟡 现代 C# 模式
// ✅ GOOD: LINQ instead of loops
var activeEnemies = allEnemies.Where(e => e.IsActive).ToList();
// ✅ GOOD: Expression bodies
public int Health => this.currentHealth;
// ✅ GOOD: Null-coalescing
var name = playerName ?? "Unknown";
// ✅ GOOD: Pattern matching
if (obj is Player player) player.TakeDamage(10);
Unity 架构(VContainer)
using UnityEngine.Scripting;
using VContainer.Unity;
public sealed class GameService : IInitializable, IDisposable
{
private readonly SignalBus signalBus;
private readonly LevelDataController levelController;
[Preserve]
public GameService(SignalBus signalBus, LevelDataController levelController)
{
this.signalBus = signalBus;
this.levelController = levelController;
}
void IInitializable.Initialize()
{
this.signalBus.Subscribe<WonSignal>(this.OnWon);
}
void IDisposable.Dispose()
{
this.signalBus.TryUnsubscribe<WonSignal>(this.OnWon);
}
}
Unity 架构(TheOne.DI)
public sealed class GameService : IAsyncEarlyLoadable, IDisposable
{
private readonly IPublisher<WonSignal> publisher;
private IDisposable? subscription;
[Inject]
public GameService(
IPublisher<WonSignal> publisher,
ISubscriber<WonSignal> subscriber)
{
this.publisher = publisher;
this.subscription = subscriber.Subscribe(this.OnWon);
}
public void Dispose()
{
this.subscription?.Dispose();
}
}
代码审查清单
快速验证(提交前)
🔴 代码质量(首先检查):
- 已启用可空引用类型(#nullable enable)
- 所有访问修饰符均正确(默认使用 private)
- 编译器警告为零
- 出错时抛出异常(不记录错误日志)
- 区分 TheOne.Logging(运行时)和 Debug.Log(仅限编辑器)
- 不会重新赋值的字段使用 readonly
- 常量使用 const
- 使用 nameof,而非字符串字面量
- using 指令位于最深层作用域
- 没有内联注释(代码能自我说明)
🟡 现代 C# 模式:
- 使用 LINQ,而非手动循环
- 简单成员使用表达式体
- 使用空合并运算符
- 类型检查使用模式匹配
- 适当使用现代 C# 特性
🟢 Unity 架构:
- 正确使用 VContainer/TheOne.DI
- 正确使用 SignalBus/Publisher-Subscriber
- 仅通过控制器访问数据
- 在 Dispose 中取消订阅所有信号
- 在构造函数上使用 [Preserve] 或 [Inject] 特性
🟢 Unity 特定要求:
- 在 Dispose 中卸载已加载的资源
- 不在 Update/运行时循环中使用 Find/GetComponent
- 使用 TryGetComponent,而非 GetComponent + null 检查
- 生命周期方法的顺序正确
🔵 性能:
- Update/FixedUpdate 中没有分配
- 避免在热点路径中使用 LINQ
- 未修改集合时使用 .ToArray(),而非 .ToList()
应避免的常见错误
❌ 不要:
- 忽略可空性警告 → 启用 #nullable 并修复所有警告
- 记录错误而不是抛出异常 → 遇到错误时抛出异常
- 在运行时代码中使用 Debug.Log → 使用 TheOne.Logging.ILogger(Debug.Log 仅用于编辑器)
- 为日志添加条件保护 → ILogger 会在内部处理 #if
- 手动添加日志前缀 → ILogger 会自动处理 [prefix]
- 在构造函数中记录日志 → 切勿在构造函数中记录日志(保持快速且无副作用)
- 使用 logger?.Method() → 使用 logger.Method()(DI 保证其不为 null)
- 添加冗余日志 → 仅保留必要的日志
- 省略访问修饰符 → 始终使用限制性最强的访问修饰符(默认为 private)
- 硬编码字符串 → 使用 nameof() 表示属性名和参数名
- 让字段保持可变 → 尽可能使用 readonly/const
- 使用 Zenject → 使用 VContainer
- 直接使用 MessagePipe → 使用 SignalBus
- 直接访问数据模型 → 使用 Controllers
- 忘记取消订阅信号 → 实现 IDisposable
- 添加行内注释 → 改用描述性名称
✅ 应该:
- 启用可空引用类型(#nullable enable)
- 遇到错误时抛出异常(绝不记录错误日志)
- 在运行时代码中使用 TheOne.Logging.ILogger(不加条件保护、不加前缀、不在构造函数中记录日志)
- 直接使用 logger.Method()(不使用空条件运算符,DI 保证其不为 null)
- Debug.Log 仅用于编辑器(#if UNITY_EDITOR)
- 使用限制性最强的修饰符(默认为 private)
- 使用描述性名称(不添加行内注释)
- 使用 nameof() 表示字符串字面量
- 对不可变字段使用 readonly/const
- 使用 VContainer 或 TheOne.DI 进行依赖注入
- 对所有事件使用 SignalBus 或 Publisher/Subscriber
- 使用 Controllers 访问所有数据
- 始终实现 IDisposable 并取消订阅
- 添加 [Preserve] 或 [Inject],防止代码剥离
- 使用 LINQ 进行集合操作
审查严重程度
🔴 严重(必须修复)
- 未修复可空性警告 — 启用 #nullable 并修复所有警告
- 记录错误而不是抛出异常 — 遇到错误时应抛出异常,而不是记录日志
- 在运行时代码中使用 Debug.Log — 必须使用 TheOne.Logging.ILogger(Debug.Log 仅用于编辑器)
- 对日志使用条件保护 — ILogger 会在内部处理 #if,请移除条件保护
- 手动添加日志前缀 — ILogger 会自动处理 [prefix],请移除前缀
- 在构造函数中记录日志 — 切勿在构造函数中记录日志(保持快速且无副作用)
- 对 logger 使用空条件运算符 — 使用 logger.Method(),而不是 logger?.Method()
- 缺少访问修饰符 — 所有成员都必须显式声明修饰符
- 忽略编译器警告 — 对警告零容忍
- 行内注释 — 改用描述性名称
- 使用 Zenject 而不是 VContainer
- 使用 MessagePipe 而不是 SignalBus
- 直接访问数据(未使用 Controllers)
- 内存泄漏(未取消订阅)
- 未实现 IDisposable
🟡 重要(应该修复)
- 缺少 readonly/const — 尽可能将字段声明为 readonly/const
- 硬编码字符串 — 使用 nameof() 表示属性名和参数名
- using 指令未置于最内层作用域 — 尽可能在方法级别使用 using
- 冗余且不必要的日志 — 仅保留必要的日志
- 使用冗长代码而不是 LINQ
- 缺少 [Preserve] 或 [Inject] 特性
- 热路径中的性能问题
- 业务逻辑缺少单元测试
- 公共 API 缺少 XML 文档
🟢 可改进(建议)
- 可以使用表达式主体
- 可以使用空条件运算符
- 可以使用模式匹配
- 可以改进命名
- 可以使用现代 C# 特性简化代码
详细参考
C# 编码规范
- LINQ 模式 - LINQ、扩展方法、
var的使用 - 现代 C# 功能 - 表达式主体、空合并、模式匹配、记录
- 质量与规范 - 可空类型、访问修饰符、日志记录、异常
- 性能优化 - Unity 模式、LINQ 性能
Unity 架构
- VContainer 依赖注入 - 完整的 VContainer 依赖注入指南
- SignalBus 事件 - SignalBus 事件系统模式
- 数据控制器 - 数据控制器实现
- 集成模式 - 用于第三方 SDK 的 Service/Bridge/Adapter
- TheOne 框架 - TheOne.DI + Publisher/Subscriber 替代方案
- UniTask 模式 - 使用 UniTask 实现异步/await
代码审查
- 架构审查 - VContainer、SignalBus、Controllers 违规项
- C# 质量审查 - LINQ、表达式主体、空值处理
- Unity 特性审查 - 组件访问、生命周期、清理
- 性能审查 - 分配、热点路径中的 LINQ
总结
此技能为 TheOne Studio 提供全面的 Unity 开发规范:
- C# 卓越实践:现代、简洁、专业的 C# 代码
- Unity 架构:VContainer+SignalBus 或 TheOne.DI+Publisher 模式
- 代码质量:严格执行质量、规范和性能规则
- 代码审查:用于拉取请求审查的完整清单
使用上方的快速参考指南,找到所需的特定模式。
评论
支持 Markdown