Рекомендации по программированию
В этом документе приведены рекомендации по написанию кода для World Locking Tools для Unity. Большинство из этих рекомендаций соответствуют рекомендуемым стандартам из MSDN.
Заголовки с информацией о лицензии сценария
Ко всем сценариям, опубликованным в World Locking Tools для 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 — код, выполняющий основные функции World Locking Tools.
Microsoft.MixedReality.WorldLocking.Tools — дополнительные функции, дополняющие разработку с World Locking Tools. Примерами являются диагностические визуализации и базовые реализации обработчиков событий приложения.
Microsoft.MixedReality.WorldLocking.Examples — конкретные реализации, демонстрирующие использование возможностей World Locking Tools и полученные преимущества.
Связанные функции в одном из указанных выше пространств имен можно сгруппировать путем расширения до нового подпространства имен.
Рекомендуется
namespace Microsoft.MixedReality.WorldLocking.Examples.Placement
{
// Interface, class or data type definition.
}
Если пропустить пространство имен для интерфейса, класса или типа данных, изменение будет заблокировано.
Пробелы и знаки табуляции
При участии в этом проекте обязательно используйте четыре пробела вместо знака табуляции.
Кроме того, убедитесь, что добавлены пробелы для условных или циклических функций, например 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;
Модификаторы доступа
Всегда объявляйте модификатор доступа для всех полей, свойств и методов.
Все методы API Unity должны быть по умолчанию равны
private
, если их не нужно переопределять в производном классе. В этом случае следует использоватьprotected
.
Поля всегда должны иметь значение
private
, с методами доступа к свойствамpublic
илиprotected
.
Не рекомендуется
// 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
}
Проверяйте использование перечислений для битовых полей
Если для перечисления существует возможность требовать несколько состояний в качестве значения, например,Ручность = слева и справа. Затем для правильного использования перечисление нужно дополнить элементами 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;
Рекомендуется
// 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" каждый раз
Unity будет создавать новый материал каждый раз при использовании ".material", что приведет к утечке памяти при неправильной очистке.
Не рекомендуется
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
, чтобы использовать API, относящихся к UWP и не относящимся к Unity. Это определение не позволит им выполняться в Редакторе или на неподдерживаемых платформах. Это определение эквивалентноUNITY_WSA && !UNITY_EDITOR
и должно использоваться вместо него. - Используйте
UNITY_WSA
, чтобы использовать API Unity, зависящие от UWP, например пространство именUnityEngine.XR.WSA
. Это будет выполняться в Редакторе, если платформа имеет значение UWP, а также в созданных приложениях UWP.
Эта таблица поможет вам выбрать нужный вариант #if
в зависимости от ваших сценариев использования и ожидаемых параметров сборки.
Определение | UWP IL2CPP | UWP .NET | Редактор |
---|---|---|---|
UNITY_EDITOR |
False | False | True |
UNITY_WSA |
True | True | True |
WINDOWS_UWP |
True | True | False |
UNITY_WSA && !UNITY_EDITOR |
True | True | False |
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, чтобы избежать затрат ресурсов, связанных с преобразованиями часовых поясов.