Unity编码规范
本文最后更新于 2026年2月23日 晚上
Unity编码规范
结构规范
让最常用的成员排在前列,让最相关的成员排在一起,便于快速索引有效信息和提示成员功能。
确定类成员的布局方式
- 按访问修饰符排序:公开->保护->私有[SerializeField]->私有
- 按是否为静态排序:静态->非静态
- 按具体类型排序:数据定义->事件->字段->属性->函数
- 按逻辑关系排序:Unity事件、成对函数等
示例:
1 | |
命名规范
增强可读性,避免形式主义,追求美观简洁。
- 统一采用英语命名,不要用汉语拼音。
1 | |
- 私有成员全部采用“驼峰命名法”
- 非私有成员全部采用“帕斯卡命名法”(首字母大写的驼峰命名法)
- 确保利用驼峰分割名称后,可从翻译软件中得到对应中文。
1 | |
- 名称怕短不怕长,除非是
html这类事实标准,否则不要缩写! - 进行缩写时,对缩写单次采用首字母大写,其余小写的形式。
- 不要使用纯简单的字母、数字等无意义名称,名称必须能自注释。
1 | |
- 不要故意使用匈牙利命名法(添加多余的前后缀),现代IDE能区分类型信息。(单纯是为了避免重名等需求,不受影响)
- 不要使用下划线,有了驼峰命名法,没有下划线也能分割内容。
1 | |
- 针对具体类型有一些专门的命名要求
| 类型 | 描述 | 示例 |
|---|---|---|
| 接口 | 对应类名添加I前缀。 |
IInterface |
| 泛型 | 对应类型添加T前缀。 |
TGeneric |
| 函数 | 表示动作,采用主动语态。 | OpenDoor |
| 事件 | 表示动作,采用被动语态。 触发在对应函数开头或期间使用进行时,触发在函数结束使用过去时。 |
DoorOpening、DoorOpened |
| 事件函数(用于实现事件的函数) | 表示动作,采用被动语态,在对应事件命名的基础上添加on前缀。 |
OnDoorOpening、OnDoorOpened |
| 字段 | 名词和可选的形容词,采用被动语态。由于字段不允许公开,所以永远为小驼峰命名法。 | isDoorOpening |
| 属性 | 通常与字段对应,故与对应字段同名。且通常属性是公开类型,故一般会采用大写开头。 | IsDoorOpening |
- 针对具体功能也有些专门的命名要求(这些要求不分类型,字段名、类名等都要遵守):
| 功能 | 要求 |
|---|---|
| 负责UI相关的元素(对应MVC中的V) | +UI |
| 负责驱动模块,推进游戏逻辑的元素(对应MVC中的C) | +Controller |
| 用于实例化的预制体 | +Prefab |
实现规范
使用优秀的代码设计策略,增加程序的可维护性。
功能性要求
这些要求会影响你的功能实现,但有助于不同功能间的开发协作。
- 非常量字段永远不允许公开,应使用属性代替。
1 | |
- 基于
[SerializeField](或[RequireComponent])实现依赖注入,而非不受控的依赖获取。
1 | |
- 在
Awake(或OnEnable)阶段完成功能模块初始化,存在依赖时配合[DefaultExecutionOrder]控制。 - 不要基于构造函数、析构函数等C#事件处理数据,应全部在Unity事件中处理,否则部分Unity功能会无法使用。
- 尽可能禁用编辑器选项
Reload Domain和Reload Scene,从而加快编辑器运行速度,提高开发迭代效率。
1 | |
可读性要求
这些要求不会影响功能和程序设计,但可以使代码美观,便于读写。
- 省略默认代码(如私有访问修饰符),让系统自动提供,从而简化代码减少阅读量。
1 | |
- 区分“取反”和“判否”,在判否时显式写明为与否定的比较。
1 | |
- 避免使用 var,应显式写明类型信息。
1 | |
- 当有多个if判断时,使用基于级联的布局方式,而非嵌套。
1 | |
- 代码中的任何元素(变量函数等)应保持最小作用域,仅放在确实要用的环境中。
1 | |
- 优先使用新式的C#语法糖和功能,减少代码量,统一实现方式。
1 | |
注意:部分新式写法存在性能和兼容性问题,例如:Linq 可能产生 GC;空条件运算符对Unity对象无效。故要根据实际情况选择性使用,甚至在性能密集场合,手写的排序比 List.Sort 更高效(因为Sort也有GC)。
如果偷懒不想阅读 C# 文档,建议使用一下 Rider IDE,远比 VS 等在代码提示和检查上智能的多(毕竟以前都没有免费版),用一遍可以学到很多知识。
设计性要求
这些要求影响你如何设计你的代码实现,能大幅增加代码的健壮性,减少问题概率且易于调整扩展。
检错方法
- 避免无法预估的if判断
如果你的代码实现里用到很多if判断,且你无法确定他们的最大数量,可以预见性的,他们未来还会继续增加。那你不应该使用if实现,而是改用委托,由外部调用者实现原本的if代码。
- 杜绝复制粘贴的编码方式
如果你写代码时出现了成段的复制粘贴行为,那说明你的代码需要优化。你应该将你复制的代码进行封装,考虑改用可以共用的函数实现,这样后期修改才不会出现“一个调整,到处要改”的情况。
- 保持树型代码引用关系
如果你有两个不相关的功能 A、B,将A的代码删除后,B却报错了,那你的代码就存在依赖问题。不同功能间的依赖应该是树状的,有明显的层级隔离,一个枝杈不可能影响到另一个枝杈。如果难以理解,那就想这么一个需求,你的每一个功能模块都应该可以做到随时移植到其他项目,且仅依赖必要的其他功能代码。
开发方法
- 基本原则:通过“高内聚低耦合”实现“模块化”
很多人把自己的功能称为模块,但事实上他们并没有实现模块化。“模块化”在《软件工程》中有专门的定义,逻辑上的功能独立并不能说明你的代码是模块化的。无论多复杂的编码规范技巧,本质都是为了实现“模块化”,实现“高内聚低耦合”,包括我们上面已经提到的几点方案。
- 开发技巧:设计模式
市面上有很多类似的技巧和规范教我们如何高效管理项目,例如《设计模式》就是其中一种,建议看看:【软件开发】设计模式个人解读
- 开发框架:MVC框架
如果你的代码实现了模块化,那必然就会自动使用上MVC框架,因为MVC就代表了三个不同的模块:
- M(模型):核心功能,类似于API的存在。
- V(视图):用户界面,与M分离,用于想用户呈现特定的数据交互形式。
- C(控制器):驱动游戏逻辑,将各个模块桥接,激活。
如果偷懒,MV可以合成一个模块(但那样不利于自动化测试,因为V只能人工测试),但C一定是分离出来的。一个应用说到底就是由一堆功能模块和驱动模块构成的。
- 避免形式主义,不要写没有提到的功能。
很多首次接触代码设计规范的人,会陷入一种形式主义,一开始就想把代码设计的尽善尽美,很小的一个功能,却花大量的事件用于构建框架。但通常的结果是,这些复杂的框架不仅没起到效果,还严重影响维护和新功能的开发。代码设计应该是动态调整的,不会为未提出的需求做过多准备,但一旦有需求也能迅速改进适应。
没人是先知,人总是活在当下,程序也是一样。项目刚开始时,你可以用一些if,可以少量复制粘贴,可以不用父类抽象,这没问题,因为这样写最简单省时间,只要功能没有改动,这就是当前状态最易于理解的代码。直到功能开始复杂,你就该意识到需要换个策略应对变化了。用上那些你学到的设计技巧,在一次一次迭代中将程序不断调整完善,因地制宜,一砖一瓦,稳稳的做出每个当下的最合适的项目形状。