编码准则

本文档概述了适用于 Unity 的全球锁定工具的建议编码准则。 这些建议大多遵循 MSDN 的推荐标准


脚本许可证信息标头

所有发布到适用于 Unity 的世界锁定工具的脚本都应附加标准许可证标头,具体如下所示:

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

任何未附加许可证标头就提交的脚本文件都将被拒绝。

函数/方法摘要标头

发布的所有公共类、结构、枚举、函数、属性、字段都应按其用途和用法来描述,具体如下所示:

    /// <summary>
    /// The Controller definition defines the Controller as defined by the SDK / Unity.
    /// </summary>
    public struct Controller
    {
        /// <summary>
        /// The ID assigned to the Controller
        /// </summary>
        public string ID;
    }

此规则可确保针对各个类、方法和属性正确生成和分发相应文档。

任何在没有正确摘要标记的情况下提交的脚本文件都将被拒绝。

命名空间规则

所有类和扩展都应按命名空间界定适用范围,并从以下命名空间中进行适当择取。

Microsoft.MixedReality.WorldLocking.Core - 满足世界锁定工具的基本服务的基础代码。

Microsoft.MixedReality.WorldLocking.Tools - 作为世界锁定工具开发功能的补充功能的可选功能。 例如用于诊断的可视化效果和应用程序事件处理程序的基线实现。

Microsoft.MixedReality.WorldLocking.Examples - 用于演示如何使用世界锁定工具功能的特定实现以及相关优点。

通过扩展到新的子命名空间,可以对上述命名空间之一中的相关功能进行分组。

应做事项

namespace Microsoft.MixedReality.WorldLocking.Examples.Placement
{
    // Interface, class or data type definition.
}

省略接口、类或数据类型的命名空间将导致更改被阻止。

空格与制表符

参与此项目时,请务必使用 4 个空格而不是制表符。

此外,请确保为 if / while / for 等条件/循环函数添加空格

不要

private Foo () // < - space between Foo and ()
{
    if(Bar==null) // <- no space between if and ()
    {
        DoThing();
    }
    
    while(true) // <- no space between while and ()
    {
        Do();
    }
}

private Foo()
{
   if (Bar==null)
   {
       DoThing();
   }
   
   while (true)
   {
       Do();
   }
}

间距

请勿在方括号和圆括号之间添加额外空格:

不要

private Foo()
{
    int[ ] var = new int [ 9 ];
    Vector2 vector = new Vector2 ( 0f, 10f );
}

private Foo()
{
    int[] var = new int[9];
    Vector2 vector = new Vector2(0f, 10f);
}

命名约定

始终对公共/受保护的/虚拟属性使用 PascalCase,对专用属性和字段使用 camelCase

这种情况唯一的例外是对于要求字段由 JsonUtility 序列化的数据结构。

不要

public string myProperty; // <- Starts with a lower case letter
private string MyProperty; // <- Starts with an uppercase case letter

public string MyProperty;
protected string MyProperty;
private string myProperty;

访问修饰符

始终对所有字段、属性和方法声明访问修饰符。

默认情况下,所有 Unity API 方法应为 private,除非需要在派生类中重写这些方法。 在这种情况下应使用 protected

字段应始终为 private,具有 publicprotected 属性访问器。

不要

// protected field should be private
protected int myVariable = 0;

// property should have protected setter
public int MyVariable { get { return myVariable; } }

// No public / private access modifiers
void Foo() { }
void Bar() { }

public int MyVariable { get; protected set; } = 0;

private void Foo() { }
public void Bar() { }
protected virtual void FooBar() { }

使用大括号

请始终在每个语句块后使用大括号,并将其放在下一行。

禁止事项

private Foo()
{
    if (Bar==null) // <- missing braces surrounding if action
        DoThing();
    else
        DoTheOtherThing();
}

不要

private Foo() { // <- Open bracket on same line
    if (Bar==null) DoThing(); <- if action on same line with no surrounding brackets 
    else DoTheOtherThing();
}

private Foo()
{
    if (Bar==true)
    {
        DoThing();
    }
    else
    {
        DoTheOtherThing();
    }
}

应将各公共类、结构及枚举放入其自己的文件中。

如果类、结构或枚举可以成为专用的,那么可以包含在同一文件中。 此包含可以避免 Unity 的编译问题,并确保发生正确的代码抽象。 还可以在代码需要更改时减少冲突和中断性变更。

不要

public class MyClass
{
    public struct MyStruct() { }
    public enum MyEnumType() { }
    public class MyNestedClass() { }
}

// Private references for use inside the class only
public class MyClass
{
   private struct MyStruct() { }
   private enum MyEnumType() { }
   private class MyNestedClass() { }
}

应做事项

MyStruct.cs

// Public Struct / Enum definitions for use in your class.  Try to make them generic for reuse.
public struct MyStruct
{
   public string Var1;
   public string Var2;
}

MyEnumType.cs

public enum MuEnumType
{
    Value1,
    Value2 // <- note, no "," on last value to denote end of list.
}

MyClass.cs

public class MyClass
{
    private MyStruct myStructreference;
    private MyEnumType myEnumReference;
}

对枚举排序以进行适当扩展。

如果枚举将来可能扩展,则基于枚举的默认值排序就非常重要了。 这种排序可确保枚举索引不受新增内容的影响。

不要

public enum SDKType
{
    WindowsMR,
    OpenVR,
    OpenXR,
    None, <- default value not at start
    Other <- anonymous value left to end of enum
}

   /// <summary>
   /// The SDKType lists the VR SDK's that are supported by the MRTK
   /// Initially, this lists proposed SDK's, not all may be implemented at this time (please see ReleaseNotes for more details)
   /// </summary>
   public enum SDKType
   {
       /// <summary>
       /// No specified type or Standalone / non-VR type
       /// </summary>
       None = 0,
       /// <summary>
       /// Undefined SDK.
       /// </summary>
       Other,
       /// <summary>
       /// The Windows 10 Mixed reality SDK provided by the Universal Windows Platform (UWP), for Immersive MR headsets and HoloLens. 
       /// </summary>
       WindowsMR,
       /// <summary>
       /// The OpenVR platform provided by Unity (does not support the downloadable SteamVR SDK).
       /// </summary>
       OpenVR,
       /// <summary>
       /// The OpenXR platform. SDK to be determined once released.
       /// </summary>
       OpenXR
   }

使枚举名称以“Type”结尾

枚举名称应使用 Type 后缀来清楚地反映其性质。

不要

public enum Ordering
{
    First,
    Second,
    Third
}
public enum OrderingEnum
{
    First,
    Second,
    Third
}

public enum OrderingType
{
    First = 0,
    Second,
    Third
}

了解枚举在位域中的使用

如果枚举有可能需要多个状态作为值,例如 Handedness = Left & Right。 那么需使用 BitFlag 修饰枚举,才能正确使用枚举

Handedness.cs 文件对此有一个具体实现

不要

public enum Handedness
{
    None,
    Left,
    Right
}

[flags]
public enum HandednessType
{
   None = 0 << 0,
   Left = 1 << 0,
   Right = 1 << 1,
   Both = Left | Right
}

最佳做法,包括 Unity 建议

此项目的一些目标平台需要考虑性能。 请记住,在紧密更新循环或算法中频繁调用的代码中分配内存时,请始终谨慎操作。

封装

如果需要从类或结构外部访问字段,请始终使用私有字段和公共属性。 请确保将私有字段和公共属性放在一起。 这样一来,可以轻松地一看便知支持该属性的内容以及该字段可通过脚本修改。

如果需要能够在检查器中编辑字段,最佳做法是遵循封装规则并将支持字段序列化。

这种情况的唯一例外是要求字段由 JsonUtility 序列化的数据结构,其中数据类需要具有所有公共字段才能使序列化正常工作。

不要

public float MyValue;

// private field, only accessible within script (field is not serialized in Unity)
private float myValue;

Do

// Enable private field to be configurable only in editor (field is correctly serialized in Unity)
[SerializeField] 
private float myValue;

不要

private float myValue1;
private float myValue2;

public float MyValue1
{
    get{ return myValue1; }
    set{ myValue1 = value }
}

public float MyValue2
{
    get{ return myValue2; }
    set{ myValue2 = value }
}

// Enable field to be configurable in the editor and available externally to other scripts (field is correctly serialized in Unity)
[SerializeField]
[ToolTip("If using a tooltip, the text should match the public property's summary documentation, if appropriate.")]
private float myValue; // <- Notice we co-located the backing field above our corresponding property.

/// <summary>
/// If using a tooltip, the text should match the public property's summary documentation, if appropriate.
/// </summary>
public float MyValue
{
    get{ return myValue; }
    set{ myValue = value }
}

尽可能使用 for 而不是 foreach

在某些情况下,需要使用 foreach,例如在遍历 IEnumerable 时。 但出于性能考虑,应尽可能避免使用 foreach。

不要

foreach(var item in items)

int length = items.length; // cache reference to list/array length
for(int i=0; i < length; i++)

将值进行缓存,并尽可能在场景/预制件中序列化这些值。

考虑到 HoloLens,最好优化场景或预制件中的性能和缓存引用,以限制运行时内存分配。

不要

void Update()
{
    gameObject.GetComponent<Renderer>().Foo(Bar);
}

[SerializeField] // To enable setting the reference in the inspector.
private Renderer myRenderer;

private void Awake()
{
   // If you didn't set it in the inspector, then we cache it on awake.
   if (myRenderer == null)
   {
       myRenderer = gameObject.GetComponent<Renderer>();
   }
}

private void Update()
{
   myRenderer.Foo(Bar);
}

缓存对材料的引用,不要每次都调用 ".material"。

每次使用 ".material" 时,Unity 都会创建新材料,如果未正确清理,则会导致内存泄漏。

不要

public class MyClass
{
    void Update() 
    {
        Material myMaterial = GetComponent<Renderer>().material;
        myMaterial.SetColor("_Color", Color.White);
    }
}

// Private references for use inside the class only
public class MyClass
{
   private Material cachedMaterial;

   private void Awake()
   {
       cachedMaterial = GetComponent<Renderer>().material;
   }

   void Update() 
   {
       cachedMaterial.SetColor("_Color", Color.White);
   }
   
   private void OnDestroy()
   {
       Destroy(cachedMaterial);
   }
}

或者,使用 Unity 的 "SharedMaterial" 属性,该属性不会每次引用材料时都创建新材料。

使用平台依赖编译来确保工具包不会中断另一个平台上的生成

  • 使用 WINDOWS_UWP 来使用 UWP 特定的非 Unity API。 此规定会阻止它们尝试在编辑器中或不受支持的平台中运行。 这等效于 UNITY_WSA && !UNITY_EDITOR 并应用于促进。
  • 使用 UNITY_WSA 来使用 UWP 特定的 Unity API,如 UnityEngine.XR.WSA 命名空间。 当平台设置为 UWP 时,这将在编辑器以及内置 UWP 应用中运行。

此图表可帮助你根据自己的用例和所需的生成设置来决定要使用哪个 #if

定义 UWP IL2CPP UWP .NET 编辑器
UNITY_EDITOR False False True
UNITY_WSA True True True
WINDOWS_UWP True True
UNITY_WSA && !UNITY_EDITOR True True
ENABLE_WINMD_SUPPORT True True 错误
NETFX_CORE False True False

首选 DateTime.UtcNow,而非 DateTime.Now

DateTime.UtcNow 比 DateTime.Now 更快。 在以前的性能调查中,我们发现使用 DateTime.Now 会增加大量开销,尤其是在 Update() 循环中使用时。 其他人遇到了相同的问题

首选使用 DateTime.UtcNow,除非实际需要本地化时间(原因可能是你想要在用户的时区显示当前时间)。 如果要处理相对时间(即上次更新和现在之间的增量),则最好使用 DateTime.UtcNow 以避免执行时区转换的开销。