Соглашения об именовании
Соглашения об именовании
В выборе имени заключено больше смысла, чем кажется. Имя показывает, какое место сущность занимает в системе: что это, к чему относится и какую роль выполняет. Имена переменных, классов и методов — не просто метки: они несут смысл. Удачные соглашения об именовании напрямую влияют на то, насколько легко читатель программы поймет заложенную в нее идею. При выборе имен учитывайте следующие рекомендации.
Имена идентификаторов Идентификатор — это любое имя, присвоенное типу (классу, интерфейсу, структуре, делегату или перечислению), члену типа, переменной либо пространству имен. Хотя C# допускает специальные знаки, символы Unicode и обратную косую черту в идентификаторах, избегайте их. Они могут мешать работе некоторых инструментов командной строки Unity. Необычные символы также снижают совместимость с разными платформами.
Стили регистра В имени переменной нельзя использовать пробелы: C# разделяет ими идентификаторы. Для составных имен и фраз в исходном коде применяют разные стили регистра. Существует несколько общепринятых соглашений об именовании и регистре.
Стиль camelCase (camelCase) В стиле camelCase фразы записывают без пробелов и знаков препинания, а каждое следующее слово начинают с прописной буквы. Первая буква остается строчной. Так оформляют локальные переменные и параметры методов. Например: examplePlayerController maxHealthPoints endOfFile
Стиль PascalCase (PascalCase) PascalCase — разновидность camelCase, в которой первая буква тоже прописная. При разработке на Unity этот стиль используют для имен классов, открытых полей и методов. Например:
ExamplePlayerController MaxHealthPoints EndOfFile
Стиль snake_case (snake_case) В этом стиле пробелы между словами заменяют символами подчеркивания. Например: example_player_controller max_health_points end_of_file
Стиль kebab-case (kebab-case) Здесь пробелы между словами заменяют дефисами, словно нанизывая слова на «шампур». Например: example-player-controller Max-health-points end-of-file naming-conventions-methodology Стиль kebab-case широко применяется в веб-технологиях, особенно в CSS. Мы также рекомендуем использовать его в USS для UI Toolkit, о чем подробнее расскажем далее.
Венгерская нотация В таком имени переменной или функции обычно кодируется ее назначение либо тип. Например:
int iCounter string strPlayerName Венгерская нотация — устаревшее соглашение, которое редко применяется при разработке на Unity.
Поля и переменные При именовании переменных и полей соблюдайте следующие правила: — Используйте существительные для имен переменных. Имя должно быть содержательным, понятным и однозначным, поскольку переменная обозначает объект или состояние. Исключение составляют переменные типа bool, о которых сказано ниже. — Начинайте имена логических переменных с глагола. Такие переменные содержат true или false и часто отвечают на вопрос: бежит ли игрок, окончена ли игра? Глагол делает смысл имени очевиднее. Обычно за ним следует описание или условие, например isDead, isWalking или hasDamageMultiplier. — Выбирайте осмысленные имена и не сокращайте их без необходимости, кроме общепринятых математических обозначений. Имя должно ясно выражать назначение, легко произноситься и находиться поиском. Это важно не только для коллег: содержательные имена дают больше контекста инструментам ИИ и помогают им точнее генерировать код и рекомендации. Например, свойство HorizontalAlignment читается лучше, чем AlignmentHorizontal.
Однобуквенные переменные допустимы в циклах и математических выражениях, но в остальных случаях не сокращайте имена. Ясность важнее нескольких секунд, сэкономленных на пропущенных буквах.
При прототипировании возникает соблазн использовать короткие бессодержательные имена, однако это не сэкономит время, если позднее код придется рефакторить. Выбирайте осмысленные имена с самого начала.
Не рекомендуется
Рекомендуемый вариант
Примечания
int dint elapsedTimeInDaysИзбегайте однобуквенных сокращений, кроме счётчиков и выражений. Указывайте единицу измерения.
int hp, int hp,tName, string int mvmtSpeedint healthPoints, int healthPoints, string teamName, int movementSpeed string teamName,int mvmtSpeed
int movementSpeedИмена переменных должны передавать смысл. Выбирайте легко произносимые имена, по которым удобно выполнять поиск.
int getMovementint SpeedgetMovementSpeedint movementSpeedИспользуйте существительные. Глаголы оставляйте для методов, кроме логических переменных (см. ниже).
bool dead bool deadbool isDead, bool isDead, bool isPlayerDeadИмена логических переменных формулируйте как вопрос, на который можно ответить true или false.
string tName,bool isPlayerDead— Используйте PascalCase (MyPropertyName) для открытых полей, а camelCase (myPrivateVariable) — для закрытых переменных. Вместо открытых полей можно применять свойства с открытым методом доступа get (см. раздел «Форматирование» ниже). — Рассмотрите возможность использования префиксов или иной системы обозначений. Некоторые руководства советуют начинать имена закрытых полей с подчеркивания (_), чтобы отличать их от локальных переменных. В наших руководствах используются префиксы m_ для закрытых полей, k_ для констант и s_ для статических переменных: так назначение переменной видно сразу. Например, movementSpeed превращается в m_movementSpeed. Допустимо сочетать префикс с PascalCase, например m_MovementSpeed, однако в современном C# такой вариант встречается реже. Другой вариант — различать поля и локальные переменные по ключевому слову this и отказаться от префикса. У открытых полей и свойств префиксов обычно нет. Локальные переменные и параметры оформляют в camelCase без префикса. Многие разработчики отказываются от префиксов и полагаются на редактор: современные IDE поддерживают подсветку, цветовое оформление и подробную контекстную информацию.
— Поля автоматически получают значения по умолчанию. Для числовых типов, например int, это обычно 0; поля ссылочных типов, например объектов, инициализируются значением null, а поля bool — значением false. Поэтому явно присваивать полю его значение по умолчанию обычно не требуется.
— Именуйте константы в PascalCase и добавляйте префикс k_. Это позволяет отличать их от обычных переменных и свойств, а также упрощает чтение и сопровождение кода.
// ПРИМЕР: константы
public class MathConstants { public const int k_MaxItems = 100; }— Последовательно указывайте или опускайте модификаторы доступа. Если модификатор не задан, компилятор считает уровень доступа private. Это допустимо, но выбранного подхода нужно придерживаться во всем коде. В рекомендациях Microsoft предлагается явно указывать private, чтобы уровень доступа был очевиден и не возникало неоднозначности. Другие руководства советуют опускать избыточные модификаторы (не писать private в области типа) и инициализаторы (например, = 0 для int и = null для ссылочных типов). Помните: если член позднее понадобится подклассу, потребуется protected. Мы рекомендуем ради простоты опускать неявные и потому избыточные элементы, такие как private, если это не ухудшает читаемость для вашей команды.
— Ставьте читаемость выше краткости. Как показывает пример из документации Microsoft, имя свойства CanScrollHorizontally лучше, чем ScrollableX, поскольку в имени ScrollableX неясно, что X обозначает горизонтальную ось.
Примеры фрагментов кода Фрагменты кода в этом руководстве сокращены и не предназначены для выполнения. Они демонстрируют только стиль и форматирование. Можно также обратиться к этому примеру руководства по стилю C# для разработчиков Unity — измененной версии Microsoft Framework Design Guidelines. Это лишь один из возможных вариантов организации командного руководства. Просмотрите каждое правило примера и адаптируйте его к предпочтениям команды. Детали отдельного правила менее важны, чем общее согласие соблюдать его последовательно. При разногласиях в вопросах стиля опирайтесь на руководство своей команды.
// ПРИМЕР: открытые и закрытые переменные сгруппированы
public float DamageMultiplier = 1.5f;
public float MaxHealth;
public bool IsInvincible;
private bool m_isDead;private float m_currentHealth;
public void InflictDamage(float damage, bool isSpecialDamage) {
// локальная переменная int totalDamage = damage;
// локальная переменная и открытое поле if (isSpecialDamage) { totalDamage *= DamageMultiplier; }
// локальная переменная и закрытое поле if (totalDamage > _currentHealth) { /// ... }
}— Объявляйте по одной переменной в строке. Код получится менее компактным, но более читаемым.
— Избегайте избыточных имен. Если класс называется Player, его поля не нужно называть PlayerScore или PlayerTarget — достаточно Score и Target.
— Опускайте избыточные инициализаторы: не пишите = 0 для int, = null для ссылочных типов и тому подобное.
— Избегайте шуток и каламбуров. Имена вроде infiniteMonkeys или dudeWheresMyChar могут вызвать улыбку сейчас, но быстро утомят после нескольких десятков прочтений. Что еще важнее, они противоречат цели выбирать имена, раскрывающие контекст. — Избегайте неоднозначности и повышайте читаемость, но используйте var, когда тип очевиден из контекста. При удачных именах переменных намерение и так понятно. Рефакторинг с var проще: конкретный тип абстрагирован, поэтому при его изменении нужно обновлять меньше участков кода. В циклах foreach var гарантирует соответствие переменной итерации типу элементов, выдаваемых перечислителем. Явно указанный несовместимый тип компилятор иногда допускает, что приводит к ошибкам во время выполнения.
// ПРИМЕР: уместное использование
var var powerUps = new List<PowerUps>();
var dictionary = new Dictionary<string, List<GameObject>>();
// НЕ РЕКОМЕНДУЕТСЯ: возможна неоднозначность
var powerUps = PowerUpManager.GetPowerUps();Перечисления Перечисления — это особые типы значений, определенные набором именованных констант. По умолчанию константы имеют тип int и нумеруются начиная с 0. Оформляйте имена перечислений и их значений в PascalCase. Открытое перечисление можно объявить вне класса, сделав доступным глобально. Имя перечисления должно быть существительным в единственном числе, поскольку обозначает одно значение из набора. Не добавляйте к нему префиксы или суффиксы. Примечание. Исключение составляют битовые перечисления с атрибутом System.FlagsAttribute. Их имена обычно ставят во множественное число, поскольку значение может представлять сразу несколько вариантов.
// ПРИМЕР: имя перечисления — существительное в единственном числе
public enum WeaponType { Knife, Gun, RocketLauncher, BFG }
public enum FireMode {
None = 0, Single = 5, Burst = 7, Auto = 8,
}// ПРИМЕР: имя перечисления флагов — во множественном числе (допустимо 1 << bitnum) [Flags] public enum AttackModes { // Десятичное
// ДвоичноеNone = 0,
// 000000Melee = 1,
// 000001Ranged = 2,
// 000010Special = 4,
// 000100MeleeAndSpecial = Melee | Special
// 000101Классы и интерфейсы При именовании классов и интерфейсов соблюдайте следующие стандартные правила: — Именуйте классы существительными или именными словосочетаниями в PascalCase. Так имена типов отличаются от методов, которые называют глагольными выражениями. — Если файл содержит MonoBehaviour, имя исходного файла должно совпадать с именем этого класса. В файле могут находиться и другие внутренние классы, но MonoBehaviour должен быть только один.
— Начинайте имя интерфейса с прописной I, а затем добавляйте прилагательное, описывающее его назначение.
// ПРИМЕР: оформление класса
public class ExampleClass : MonoBehaviour {
public int PublicField;
public static int MyStaticField;
private int m_packagePrivate;
private int m_myPrivate;
private static int m_myPrivate;
protected int m_myProtected;
public void DoSomething() { }
}
// ПРИМЕР: интерфейсы
public interface IKillable { void Kill(); }
public interface IDamageable<T> { void Damage(T damageTaken); }Методы В C# каждая выполняемая инструкция находится в контексте метода. Примечание. В разработке на Unity слова «функция» и «метод» нередко употребляют как синонимы. Однако в C# функцию нельзя написать вне класса, поэтому принят термин «метод».
Методы выполняют действия, поэтому именуйте их по следующим правилам: — Начинайте имя с глагола или глагольного выражения и при необходимости уточняйте контекст, например GetDirection или FindTarget. — Используйте camelCase для параметров. Оформляйте параметры метода так же, как локальные переменные. — Методы, возвращающие bool, должны задавать вопрос. Как и имена логических переменных, начинайте такие методы с глагола, чтобы имя выражало условие true или false, например IsGameOver или HasStartedTurn.
// ПРИМЕР: имя метода начинается с глагола
public void SetInitialPosition(float x, float y, float z) {
transform.position = new Vector3(x, y, z);
}
// ПРИМЕР: метод, возвращающий bool, задает вопрос
public bool IsNewPosition(Vector3 currentPosition) {
return (transform.position == newPosition);
}События и обработчики событий События C# реализуют паттерн «Наблюдатель». Он определяет отношения, при которых один объект — субъект, или издатель, — уведомляет зависимые объекты, называемые наблюдателями, или подписчиками. Так субъект сообщает наблюдателям об изменении состояния без жесткой связанности между объектами. Подробнее о «Наблюдателе» и других паттернах для проектов Unity рассказывается в электронной книге «Совершенствуйте код с помощью шаблонов проектирования и SOLID».
Для событий и связанных с ними методов субъекта и наблюдателей существует несколько схем именования. Рекомендуем следующие приемы: — Называйте событие глагольным выражением, точно передающим изменение состояния. Причастием настоящего или прошедшего времени обозначайте событие «до» или «после». Например, OpeningDoor — событие перед открытием двери, а DoorOpened — после него.
— Используйте для событий делегаты System.Action. В большинстве игровых сценариев достаточно Action либо обобщенных вариантов Action<T...>. Они позволяют передавать от 0 до 16 параметров разных типов и не возвращают значения (void). Готовые делегаты сокращают объем кода.
Примечание. Можно также использовать делегаты EventHandler и EventHandler<TEventArgs>. Команда должна заранее договориться о едином способе реализации событий.
// ПРИМЕР: события // используется делегат System.Action
public event Action OpeningDoor;// событие «до»public event Action DoorOpened;// событие «после»
public event Action<int> PointsScored;
public event Action<CustomEventArgs> ThingHappened;— Начинайте имя метода, вызывающего событие в субъекте, с On. Субъект обычно вызывает событие из метода с таким префиксом, например OnOpeningDoor или OnDoorOpened.
// вызывает событие, если есть подписчики
public void OnDoorOpened() { DoorOpened?.Invoke(); }
public void OnPointsScored(int points) { PointsScored?.Invoke(points); }
— Рассмотрите схему, в которой имя метода обработки события в наблюдателе начинается с имени субъекта и символа подчеркивания (_). Если субъект называется GameEvents, методы наблюдателей могут называться GameEvents_OpeningDoor и GameEvents_DoorOpened. Такой метод называется методом обработки события;
не путайте его с делегатом EventHandler.— Создавайте собственный EventArgs только при необходимости. Если событию нужно передавать пользовательские данные, определите новый тип EventArgs, унаследованный от System.EventArgs, либо пользовательскую структуру.
// при необходимости определите EventArgs // ПРИМЕР: неизменяемая пользовательская структура для передачи ID и Colorpublic struct CustomEventArgs {
public int ObjectID { get; }
public Color Color { get; }
public CustomEventArgs(int objectId, Color color) {
this.ObjectID = objectId;
this.Color = color;
}
}Пространства имен Используйте пространства имен, чтобы классы, интерфейсы, перечисления и другие типы не конфликтовали с одноименными сущностями из других пространств имен или глобального пространства. Они также предотвращают конфликты со сторонними ресурсами из Asset Store. При использовании пространств имен: — Используйте PascalCase без специальных символов и подчеркиваний. — Добавляйте директиву using в начало файла, чтобы не повторять префикс пространства имен. — Создавайте вложенные пространства имен. Разделяйте уровни оператором точки (.), чтобы организовать скрипты по иерархическим категориям. Например, логические компоненты игры можно распределить между MyApplication.GameFlow, MyApplication.AI, MyApplication.UI и другими пространствами. — Некоторые разработчики строят пространства имен по структуре папок проекта. Логическая группировка связанных классов и компонентов упрощает поиск и понимание организации кодовой базы.
namespace Enemy {
public class Controller1 : MonoBehaviour { ... }
public class Controller2 : MonoBehaviour { ... }
}В коде эти классы обозначаются соответственно как Enemy.Controller1 и Enemy.Controller2. Чтобы не вводить префикс каждый раз, добавьте директиву using:
using Enemy; Встретив имена классов Controller1 и Controller2, компилятор поймет, что речь идет об Enemy.Controller1 и Enemy.Controller2. Если скрипт должен обращаться к одноименным классам из разных пространств имен, различайте их по префиксу. Например, если классы Controller1 и Controller2 находятся также в пространстве имен Player, указывайте полные имена Player.Controller1 и Player.Controller2, чтобы избежать конфликта. Иначе компилятор сообщит об ошибке.
Naming conventions
There’s a deep psychology involved in giving something a name. A name tells us how that entity fits into the world. What is it? Who is it? What can it do for us? The names of your variables, classes, and methods aren’t mere labels. They carry weight and meaning. Good naming style impacts how someone reading your program can comprehend the idea you’re trying to convey. Here are some guidelines to consider for naming.
Identifier names An identifier is any name you assign to a type (class, interface, struct, delegate, or enum), member, variable, or namespace. Avoid special characters (backslashes, symbols, Unicode characters) in your identifiers, even though C# permits them. These can interfere with certain Unity command-line tools. Steer clear of unusual characters to ensure compatibility with most platforms.
Casing terminology You can’t define variables with spaces in the name because C# uses the space character to separate identifiers. Casing schemes can alleviate the problem of using compound names or phrases in source code. There are several well-known naming and casing conventions.
Camel case (camelCase) Also known as camel caps, camel case is the practice of writing phrases without spaces or punctuation, separating words with a single capitalized letter. The very first letter is lowercase. Local variables and method parameters are camel case. For example: examplePlayerController maxHealthPoints endOfFile
Pascal case (PascalCase) Pascal case is a variation of camel case, where the initial letter is capitalized. Use this for class, public fields and method names in Unity development. For example: ExamplePlayerController MaxHealthPoints EndOfFile
Snake case (snake_case) In this case, spaces between words are replaced with an underscore character. For example: example_player_controller max_health_points end_of_file
Kebab case (kebab-case) Here, spaces between words are replaced with dashes. The words appear on a “skewer” of dash characters. For example: example-player-controller Max-health-points end-of-file naming-conventions-methodology The Kebab-case is widely used in web technologies and namely for CSS. We are also recommending it for use with UI Toolkit USS as we will be covering later in the guide.
Hungarian notation The variable or function name often indicates its intention or type. For example: int iCounter string strPlayerName Hungarian notation is an older convention and is not common in Unity development.
Fields and variables Consider these rules for your variables and fields: —
Use nouns for variable names: Variable names should be descriptive, clear, and unambiguous because they represent a thing or state. So use a noun when naming them except when the variable is of the type bool (see below).
Prefix Booleans with a verb: These variables indicate a true or false value. Often they are the answer to a question, such as – is the player running? Is the game over? Prefix them with a verb to make their meaning more apparent. Often this is paired with a description or condition, e.g. isDead, isWalking, hasDamageMultiplier, etc.
Use meaningful names. Don’t abbreviate (unless it’s math): Your variable names should reveal their intent. Choose names that are easy to pronounce and search for – not just for your colleagues but also to provide extra context to the code for when using AI tools, as this can contribute to more accurate code generation and suggestions. Choose identifier names that are easily readable. For example, a property named HorizontalAlignment is more readable than AlignmentHorizontal. Single letter variables are fine for loops and math expressions, but otherwise, don’t abbreviate. Clarity is more important than any time saved from omitting a few vowels. You might be tempted to use short “junk” names when prototyping, but this won’t save you time if you need to refactor the code at a later date. Pick meaningful names from the beginning.
Examples to avoid
Use instead
Notes
int dint elapsedTimeInDaysAvoid single letter abbreviations unless a counter or expression. Be specific about the measurement unit.
int hp,int healthPoints, string teamName,Variable names reveal intent. Make names searchable and pronounceable.
string tName, int mvmtSpeed
int movementSpeedint getMovementSpeedint movementSpeedUse nouns. Reserve verbs for methods unless it’s a bool (below).
bool deadbool isDead,Booleans ask a question that can be answered true or false.
bool isPlayerDead —Use pascal case (MyPropertyName) for public fields. Use camel case (myPrivateVariable) for private variables: For an alternative to public fields, use Properties with a public getter (see Formatting below).
Consider using prefixes or special encoding: Some guides suggest adding a prefix to private member variables with an underscore (_) to differentiate them from local variables. In our style guides we use prefixes for private member variables (m_), constants (k_), or static variables (s_), so the name can reveal more about the variable at a glance. For example, movementSpeed becomes m_movementSpeed. Mixing PascalCase with the prefix such as m_MovementSpeed is also an option but is generally less commonly used in modern C#. Alternatively, use the this keyword to distinguish between member and local variables in context and skip the prefix. Public fields and properties generally don’t have prefixes. Local variables and parameters use camel case with no prefix. Many developers eschew these and rely on the editor instead. IDEs today support highlighting, color coding, and rich context.
Fields are automatically initialized to their default values: Default value is typically 0 for numeric types like int, while reference type fields (e.g., objects) are initialized to null by default, and bool fields are initialized to false by default. Given this, explicitly setting a field to its default value is generally unnecessary.
Name constant variables with k_ as prefix and in PascalCase: This helps to distinguish constants from regular variables or properties, and makes the code easier to read and maintain.
// EXAMPLE: constants
public class MathConstants { public const int k_MaxItems = 100; }
Specify (or omit) access level modifiers consistently: If you leave off the access modifier, the compiler will assume the access level to be private. This works well, but be consistent in how you omit the default access modifier.MSFT guidelines recommended to explicitly specify private to make the access level clear and to avoid any ambiguity. Other guides argue you should drop redundant access specifiers (leave off ‘private’ at type scope) and, similarly, drop redundant initializers (i.e. no ‘= 0’ on the ints, ‘= null’ on ref types, etc.). Remember that you’ll need to use protected if you want this in a subclass later. We recommend to leave out things that are implicit and thus redundant (such as private) for simplicity if you agree that it doesn’t negatively affect readability for you. Favor readability over brevity: As this example from the MSFT documentation shows, the property name CanScrollHorizontally is better than ScrollableX (an obscure reference to the X-axis).
Example code snippets The code snippets in this guide are non-functional and abbreviated. They’re presented here to show style and formatting. You can also reference this example C# style sheet for Unity developers, which is a modified version of Microsoft’s Framework Design Guidelines. This represents just one example of how you can set up your team’s style guide. Review each rule in the example style guide and customize it to your team’s preferences. The specifics of an individual rule are less important than having everyone agree to follow it consistently. When in doubt, rely on your team’s own guide to settle any style disagreements.
// EXAMPLE:
public and private variables are grouped together. public float DamageMultiplier = 1.5f;
public float MaxHealth;
public bool IsInvincible;
private bool m_isDead;private float m_currentHealth;
public void InflictDamage(float damage, bool isSpecialDamage) {
// local variable int totalDamage = damage;
// local variable versus
public member variable if (isSpecialDamage) { totalDamage *= DamageMultiplier; }
// local variable versus
private member variable if (totalDamage > _currentHealth) { /// ... }
}
—Use one variable declaration per line: It’s less compact, but enhances readability.
Avoid redundant names: If your class is called Player, you don’t need to create member variables called PlayerScore or PlayerTarget. Trim them down to Score or Target.
Drop redundant initializers (i.e. no ‘= 0’ on the ints, ‘= null’ on ref types, etc.).
Avoid jokes or puns: While they might elicit a chuckle now, the infiniteMonkeys or dudeWheresMyChar variables won’t hold up after a few dozen reads, and more importantly, it violates our previous stated goal of naming revealing context.
While avoiding ambiguity and always looking for ways to improve readability, you can use var when the type is clear from the context: With good naming ambiguity should be less of an issue because variable names already convey the intent. Refactoring is simpler with var since it abstracts away the specific type, reducing the number of places where code needs to be updated when types change. In foreach loops, var ensures that the iteration variable matches the type provided by the enumerator. If you explicitly declare a mismatched type, the compiler may allow it, leading to runtime errors.
// EXAMPLE: good use of
var var powerUps = new List<PowerUps>();
var dictionary = new Dictionary<string, List<GameObject>>();
// AVOID: potential ambiguity
var powerUps = PowerUpManager.GetPowerUps();Enums Enums are special value types defined by a set of named constants. By default, the constants are integers, counting up from 0. Use Pascal case for enum names and values. You can place
public enums outside of a class to make them global. Use a singular noun for the enum name as it represents a single value from a set of possible values. They should have no prefix or suffix. Note: bitwise enums marked with the System.FlagsAttribute attribute are the exception to this rule. You typically pluralize these as they represent more than one type. // EXAMPLE: enums use singular nouns
public enum WeaponType { Knife, Gun, RocketLauncher, BFG }
public enum FireMode {
None = 0, Single = 5, Burst = 7, Auto = 8,
}
// EXAMPLE: but a bitwise enum is plural (you can also use the 1 << bitnum style)
[Flags] public enum AttackModes {
// Decimal// BinaryNone = 0,
// 000000Melee = 1,
// 000001Ranged = 2,
// 000010Special = 4,
// 000100MeleeAndSpecial = Melee | Special
// 000101Classes and interfaces Follow these standard rules when naming your classes and interfaces: —
Use Pascal case nouns or noun phrases for class names: This distinguishes type names from methods, which are named with verb phrases.
If you have a Monobehaviour in a file, the source file name must match: You may have other internal classes in the file, but only one Monobehaviour should exist per file.
Prefix interface names with a capital I: Follow this with an adjective that describes the functionality.
// EXAMPLE: Class formatting
public class ExampleClass : MonoBehaviour {
public int PublicField;
public static int MyStaticField;
private int m_packagePrivate;
private int m_myPrivate;
private static int m_myPrivate;
protected int m_myProtected;
public void DoSomething() { }
}
// EXAMPLE: Interfaces
public interface IKillable { void Kill(); }
public interface IDamageable<T> { void Damage(T damageTaken); }Methods In C#, every executed instruction is performed in the context of a method. Note: “function” and “method” are often used interchangeably in Unity development. However, because you can’t write a function without incorporating it into a class in C#, “method” is the accepted term. Methods perform actions, so apply these rules to name them accordingly: —
Start the name with a verb or verb phrases: Add context if necessary. e.g. GetDirection, FindTarget, etc.
Use camel case for parameters: Format parameters passed into the method like local variables.
Methods returning bool should ask questions: Much like Boolean variables themselves, prefix methods with a verb if they return a true-false condition This phrases them in the form of a question, e.g. IsGameOver, HasStartedTurn.
// EXAMPLE: Methods start with a verb
public void SetInitialPosition(float x, float y, float z) {
transform.position = new Vector3(x, y, z);
}
// EXAMPLE: Methods ask a question when they return bool
public bool IsNewPosition(Vector3 currentPosition) {
return (transform.position == newPosition);
}Events and event handlers Events in C# implement the observer pattern. This software design pattern defines a relationship in which one object, the subject (or publisher), can notify a list of dependent objects called observers (or subscribers). Thus, the subject can broadcast state changes to its observers without tightly coupling the objects involved. You can learn more about using the observer and other design patterns in your Unity projects in the e-book Level up your code with design patterns and SOLID. Several naming schemes exist for events and their related methods in the subject and observers. Try these practices: —
Name the event with a verb phrase: Choose a name that communicates the state change accurately. Use the present or past participle to indicate events “before” or “after.” For example, specify “OpeningDoor” for an event before opening a door or “DoorOpened” for an event afterward.
Use the System.Action delegate for events: In most cases, the Action<T> delegate can handle the events needed for gameplay. You may pass anywhere from 0 to 16 input parameters of different types with a return type of void. Using the predefined delegate saves code. Note: You can also use the EventHandler or EventHandler<TEventArgs> delegates. Agree as a team on how everyone will implement events. // EXAMPLE: Events // using System.Action delegate public event Action OpeningDoor;
// event before
public event Action DoorOpened;// event afterpublic event Action<int> PointsScored;
public event Action<CustomEventArgs> ThingHappened;Prefix the event raising method (in the subject) with “On”: The subject that invokes the event typically does so from a method prefixed with “On,” e.g. “OnOpeningDoor” or “OnDoorOpened.”
// raises the Event if you have subscribers
public void OnDoorOpened() { DoorOpened?.Invoke(); }
public void OnPointsScored(int points) { PointsScored?.Invoke(points); }Consider prefixing the event handling method (in the observer) with the subject’s name and underscore (_): If the subject is named “GameEvents,” your observers can have a method called “GameEvents_OpeningDoor” or “GameEvents_DoorOpened.” Note that this is called the “event handling method”, not to be confused with the EventHandler delegate.
Create custom EventArgs only as necessary: If you need to pass custom data to your Event, create a new type of EventArgs, either inherited from System.EventArgs or from a custom struct.
// define an EventArgs if needed // EXAMPLE: read-only, custom struct used to pass an ID and Color
public struct CustomEventArgs {
public int ObjectID { get; }
public Color Color { get; }
public CustomEventArgs(int objectId, Color color) {
this.ObjectID = objectId;
this.Color = color;
}
}Namespaces Use namespaces to ensure that your classes, interfaces, enums, etc. won’t conflict with existing ones from other namespaces or the global namespace. Namespaces can also prevent conflicts with third-party assets from the Asset Store. When applying namespaces: —
Use PascalCase without special symbols or underscores.
Add a using directive at the top of the file to avoid repeated typing of the namespace prefix.
Create sub-namespaces as well. Use the dot(.) operator to delimit the name levels, allowing you to organize your scripts into hierarchical categories. For example, you can create MyApplication.GameFlow, MyApplication.AI, MyApplication.UI, and so on to hold different logical components of your game.
Some prefer to have namespaces that reflect the folder structure of the project as having logically grouped related classes and components together, also makes it easier to find and understand the structure of the codebase.
namespace Enemy {
public class Controller1 : MonoBehaviour { ... }
public class Controller2 : MonoBehaviour { ... }
}
In code, these classes are referred to as Enemy.Controller1 and Enemy.Controller2, respectively. Add a using line to save typing out the prefix: using Enemy;
When the compiler finds the class names Controller1 and Controller2, it understands you mean Enemy.Controller1 and Enemy.Controller2. If the script needs to refer to classes with the same name from different namespaces, use the prefix to differentiate them. For instance, if you have a Controller1 and Controller2 class in the Player namespace, you can write out Player.Controller1 and PlayerController2 to avoid any conflicts. Otherwise, the compiler will report an error.