介绍
本套开发规范来自 unity 官方的一个开放项目,涉及到代码中的命名语义,大小写;编程的设计规范;
命名 & 大小写
他们主要是采用的 .NET standards,然后添加了一些自己的改动。所以这里也先介绍一下 .NET 的规范,然后 Open Project 会有一些补充或者改动。
.NET standards
类、结构、接口
- 都使用
PascalCasing
命名法 - 类和结构的语义是名词或者名词词组
- 接口的语义应该是形容词,在少数情况下可以是名词或者名词词组,比如这个类型其实是一个抽象类,而不是接口
- 不要给类的前面加前缀,比如
C
- 可以考虑在派生类的后面加上基类的名字,比如
ArgumentOutOfRangeException
派生自Exception
,这种方式仅作为参考,可根据实际情况在判断用不用 - 接口的前面写一个
I
- 如果你定义了一对接口和类,其中这个类继承自这个接口,那么保证这个类和接口的名称只有
I
的区别 (?)
方法名
- 方法名使用
PascalCasing
命名法 - 语义应该是一个动词或者动词词组,因为方法就是 “干什么” 。
public class String {
public int CompareTo(...);
public string[] Split(...);
public string Trim();
}
属性名
命名:
- 属性名使用
PascalCasing
命名法 - 语义应该是一个名词,名词词组或者形容词,因为属性实质上是一块数据,所以
GetXXX
这种动词词组不适合作为属性的名字。 - 如果是一个集合,那么使用复数形式,而不是
xxxList
或者xxxSet
,关于这一点猜测是因为后期有可能会改集合的类型,之前我就遇到过取名为xxxList
,然后为了不重复改成了 Set 存储。 - boolean 的属性语义为肯定的,比如
Isxxx
,Canxxx
,Hasxxx
,而不是Istxxx
,Cantxxx
,Hastxxx
- 可以考虑使用一个跟类型相同的名称,具体看例子,没懂为什么
设计: - 如果外部不能改变这个值,那么就只给一个
getter
,要注意的是对于mutable reference type
,即使只有一个getter
,或者声明为readonly
(不要这么做),内部也会改变,比如list<xxx>
里面的 xxx 依然可以被改变 - 不要设置只有
setter
的属性,或者setter
的访问级别要比getter
高的属性,比如一个 public 的 setter 和一个 protected 的 getter,如果非要那么做,请使用一个方法代替,比如 SetXXX,XXX 应该与该属性名相同 - 给属性设置默认值的时候,要注意不要导致低效率和安全问题
- 属性的赋值应该是可以以任何顺序的,尽管可能会导致这个物体在一段时间内不能使用
- 如果在 setter 里面抛出了异常,那么就保留之前的值
- getter 应该是简单的,不能抛出异常,也不能有任何前置条件的,如果一定要抛异常,那么考虑将他设置为一个方法。对于索引器可以抛异常,因为我们需要检查他的范围
public enum Color {...}
public class Control {
public Color Color { get {...} set {...} }
}
事件
- 事件使用 PascalCasing 命名法
- 使用动词或者动词词组
- 带有时态,比如抓取一个物体,抓之前可以使用
Grabbing
,抓了之后可以叫Grabbed
,不要使用AfterXXX
和BeforeXXX
来表示时间发生的前后。 - 对于一个委托类型,使用
EventHandler
作为后缀,比如:
public delegate void ClickedEventHandler(object sender, ClickedEventArgs e);
- 如上面的例子所展现的,参数一般第一个为发送者,使用
object
类型,即使能够向下转型成更精确的类型,也使用 object(事件的好处是减少耦合性,但坏处就是很难看到引用关系,所以对于一个回调函数知道是谁调用的它很关键);第二个参数是事件所携带的数据,如果是自定义的数据类,类名请以EventArgs
为后缀。
字段
这里只针对 public static
和 protected static
的字段;对于 public
和 static
protected
的字段,根据规范就不要这么使用,要使用的话应该是通过属性;对于 static
private
或者 internal
的字段,不论是否是 static
都不在这里讨论的范围内。
- 使用 PascalCasing 命名法
- 使用名词,名词词组或者形容词
- 不要在字段的前面加上前缀,比如
g_
或者s_
来区分是否是静态的
参数
- 使用
camelCasing
命名法 - 描述性词语基于他的含而不是类型
开放项目
语义
开放项目的规范是基于 .Net 标准规范,但也有一些补充的或者更改的地方。
- 使用描述性的并且准确的语义来命名,即使这会使名字变得很长,但可读性要大于简洁性
- 不要使用缩写,也是为了可读性
- 对于大众普遍接受的缩写,可以使用,比如
UI
,IO
- 方法名称使用动词或者动词词组
- 属性使用名词、名词词组或者形容词(形容词代表 xxx 的物品,本质还是名词)
- bool 变量的命名跟上面一样,需要使用肯定的,并且可以在前面加上
is
,can
,has
- 如果有多个类型不同,但是描述一样的属性,可以使用描述+类型的命名方式
Color _gameTitleColor;
String _gameTitleString;
TextMeshProUGUI _gameTitleText;
- 命名中不要使用数字,比如
animator1
,animator2
,而应该用语义区分,比如playerAnimator
,enemyAnimator
大小写
- 类,方法,枚举,命名空间,public 字段或者属性使用
PascalCase
- 局部变量,形参使用
camelCase
- 常量使用全大写并用下划线分割
GRAVITY_AMOUNT
编程规范
- 方法除非明确知道它需要被外部使用,不然就设置为
private
- 如果你想要把一个数据显示在 inspector 面板上,但不希望其他的类使用它,那就设置为 private 并且标记为 [SerializeField],不要直接使用 public 为访问修饰符,如果你因此收到了 “这个变量从未被赋值,它将一直使用默认值”的警告,就在最后加上
= default
- 避免使用单例,如果你需要一个集中的类,同时可以被多个物体引用,那么请使用
ScriptableObjects
- 不要使用
var
来申明变量,明确写出它是什么类型的 - 不要使用静态变量,如果你一定要使用它,请兼容 Fast Enter Play Mode,由此可以推断出整个项目应该是关闭域重载的。【关于 Fast Enter Play Mode 也推荐我的翻译 & 学习笔记:Unity 快速进入运行模式( Fast Enter Play Mode)】
- 不要在代码里使用特定的数字,比如主角的移动速度是
xInput * 0.035f
,不要把 0.035f 写在代码里,把它放到一个变量里面,并且取一个语义明确的名字,最好再加个注释——为什么它的值是 0.035 - 对于
using
(比如说using System;
),请在提交代码之前把所有没有用到的命名空间都删除 - 如果你想把自己的代码放到一个命名空间中,那么请使用
UOP1
来表示代码和程序集是属于这个项目的,比如
namespace UOP1.Dialogues.Helpers;
格式
- 使用一个 Tab 来进行缩进,而不是 2 个空格,或者 4 个空格,或者其他数量的空格
- 大括号:如果括号内是空的,那么应该把他们放在同一行,如果大括号内不是空的,那么应该把它们放在不同行,并且纵向对其。像下面的例子一样:
public class EmptyBraces(){ };
public class NonEmptyBraces
{
//...
}
- 相互包含的逻辑单元需要缩进以指示层次关系,像下面的例子一样:
public void FunctionName()
{
if(somethingHappened)
{
//...
}
}
注释
- 重要:不要滥用注释,如果你觉得别人能只通过代码就理解你的意思,那就不要加注释。你可以给自己的变量、类和方法取合适的名字,那样别人可以不需要注释也能理解。
- 在你想注释的代码上面单独用一行来写注释
- 为每一个类都写摘要,特别要说明这个类是干嘛的,(可选)当这个类不是特别直观、可读的时候,你也可以在摘要里详细写一下他是如何工作的。在一般的 IDE 中,你可以合适的位置(比如类的上一行,方法的上一行)输入三个
/
,会出现摘要补全。像下面这样(你可以在 official Microsoft specification 了解更多关于摘要的信息):
/// <summary>
/// This class manages save data
/// </summary>
- 当你觉得某个方法名不能不言自明,或者你想要补充更多关于这个方法的信息时,你也可以在该方法上写一个摘要,形式可以跟上面一样,也可以写在一行:
/// <summary> This function does this... </summary>
public float CalculateBoundingBox(){ }
- 当你有一些功能需要推迟再做时,你可以在注释前面加上
//TODO:
来标注,免得自己以后忘记。注意:这不是说你就可以推送不完备的代码了(笔者:原文是This is not an invite to push broken functionality
,我理解的意思是你可以后面再做主角跳跃的功能,但走动功能相关的代码不能有 Bug) - 不要使用
#region
划分代码块,或者是自定义的代码分割注释,比如//-----------------------
(笔者:猜测是因为#region
如果出现交叉包含的情况不能在编译期检查出来,在打包的时候才会报错)
场景/Hierarchy
组织
- 在 Scene 面板的最外层新建一些空物体来在视觉上区别这些 GameObjects,这些空物体可以是
--- Camera ---, --- Environment ---, --- Lighting ---
等,并且把这些空物体的 Tag 改为EditorOnly
,这样在打包的时候就
可以被剔除 - 可以使用空物体作为容器,但如果它只包含 1-2 个物体,那就不要使用了
- UI
- 尽可能的使用相同的 Canvas,除非 Canvas 属性变了。
- 每一个面板都创建一个 Panel(比如主面板,设置面板,暂停面板等)
- 可以通过 Panel 给组成一个 UI 的元素分组,比如说在 Setting 面板里面的 “音量” 标签和右面的滑动条可以放在一组
- Panel 也可以帮助你把一些元素固定在合适的位置,比如说把背包/技能/属性等按钮固定在屏幕的右下角
命名
- 在 GameObjects 的命名中,不要出现空格
- 使用 PascalCase 命名法,比如:MainCharacter, DoorTrigger
- 如果只使用 PascalCase 命名法会形成混乱,那么可以在其中加入下划线,比如:MainHall_ExitTrigger, BossMinion_AttackWaypoints
- 在使用 prafab 的时候,如果重新命名会更好,那就重新命名。比如说一个 prefab 在文件夹中叫做 “Protagonist_Scene1Variant”,你在使用它的时候(把它放到场景里面的时候)应该重新命名为 “Protagonist”
项目文件
命名
- 规则同 场景/Hierarchy
- 一些物体在处于同一个文件夹或者有其他相关性的时候,看到他们的名字就能自然而然的将其分组:
- 通常来说,你可以在名称前面加前缀,用来表示它是属于谁的资源,比如说:PlayerAnimationController, PlayerIdle, PlayerRun 等
- 如果有一些资源不属于同一个主体,但你想让他们在文件夹中离得很近,而不是分散开来,那你也可以使用相同的前缀 ,比如:在一个存放道具资源的文件夹中,你可以使用 TableRound 和 TableRectangular 来命名,这样他们就会离得很近,而不是使用 RectangularTable 和 RoundTable
- 不要在名称中出现资源类型,比如:你应该使用 ShinyMetal 而不是 ShinyMetalMaterial
文件夹
-
在根目录中你创建的文件夹需要能反映这些资源是属于游戏的什么区域/系统/定位,比如说 Art、UI 等。然后再在里面创建子文件夹来区分不同类型的资源。
可放在根目录的文件夹示例 -
Scene 文件夹一直处在根目录
Scene 文件夹,橘色箭头 -
Scripts 文件夹也应该处于根目录,底下可以根据不同系统划分子文件夹,使得目录清晰,方便检索。定义 ScriptableObjects 的脚本也应该放到这里面。
Scripts 文件夹 -
ScriptableObjects 也应该单独建一个文件夹放在根目录,用来存在对应的 ScriptableObjects 本体。
ScriptableObjects 文件夹 -
如果你写了一个新系统,你可以把一个示例场景放到
示例场景文件夹,绿色箭头/Scenes/Examples
,然后把相关的资源(预制体,时间线,可序列化脚本)在放在里面,而不是把他们放到上面所说的那些文件夹中。
下面是一个示例文件夹结构:
📁 Art
⸺📁 Characters
⸺⸺📁 PigChef
⸺⸺⸺📁 Materials
⸺⸺⸺📁 Prefabs
⸺⸺⸺📁 Textures
⸺📁 Environment
⸺⸺📁 Interiors
⸺⸺📁 Nature
⸺⸺⸺📁 Materials
⸺⸺⸺📁 Prefabs
⸺⸺⸺📁 Textures
⸺⸺📁 Props
📁 Scenes ⬅
⸺📁 Examples ⬅
⸺📁 Locations
⸺📁 Menus
📁 ScriptableObjects (the data) ⬅
⸺📁 Audio
⸺📁 InventoryItems
📁 Scripts ⬅
⸺📁 Audio
⸺📁 Events
⸺📁 InventorySystem
⸺⸺📁 ScriptableObjects (definition)
⸺📁 SceneManagementSystem
📁 UI ⬅
⸺📁 Materials
网友评论