Unity Coding Standards
Introduction
Establishing a coding standard is essential to the long-term health of a project. While there are certain
general rules that are almost universally good to follow, other rules can be a matter of personal
preference.
However, the most important thing is that all of these rules are followed, once established.
Over time, the codebase will develop a sort of “second language” that will allow current engineers to
read and understand new code easily, and new engineers to ramp-up and familiarize themselves with
the codebase quickly.
It may seem like some rules put up some annoying hoops for you to jump through just to get a simple
feature working. That’s good. These hoops may seem arduous, but they will force you to write good
code.
Good code is:
- Easily readable and understandable, especially to other engineers who have not written it
- Flexible, and easy to modify or add to
- Sufficiently optimized for the needs of the project
- Written to expose appropriate properties for non-developers to tinker with in a way that makes
sense (in Unity)
Good code is NOT:
- Written with a motivation to have as few lines and characters as possible
- Over-optimized to the point where the code becomes over-engineered and difficult to parse for
non-authors - Covered in comments explaining all aspects of it in detail. Reading the code should be
self-explanatory (if written properly) and comments should only be used to explain parts of code
that necessitate the use of more advanced/confusing practices.
With all of this being said, here are the rules that have been established for Unity projects as of January
2024 (projects started before this date may not fully adhere to them).
Code Formatting
Indentation
Rule: Code should be indented with 4 spaces, not tabs. If you’re not a fan of mashing the spacebar
(understandably), you can configure Visual Studio to automatically replace any tabs you have with the
designated number of spaces instead.
Motivation: This is mostly personal preference, however in my experience using spaces tends to make
files and diffs easier to read on Git.
Example:
Spaces
Rule: Each distinct component of a line of code (variable name, expression character, keyword, etc.)
should be separated by a space.
Motivation: Spreading out elements of code with a single space can make it easier for engineers to
parse code without it feeling claustrophobic (this is admittedly a personal preference).
Example:
Brackets
Rule: All brackets should be on a newline, and should not be put on the same line as a class name,
conditional statement, etc.
Motivation: Placing brackets on newlines makes it easier for developers parse code that is particularly
nest-y. Compartmentalizing code based on its indentation is easier to do when the brackets are
essentially used as “markers” for this separation.
Example:
Loops and If Statements
Rule 1: All loops and if statements, even if they execute a single line of code, should be enclosed in
brackets.
Motivation: There is no functional purpose for writing single-line loops or if statements without
brackets other than to save a trivial amount of characters and lines. Instead, it makes it more difficult to
compartmentalize code while reading it, and makes it easier to accidentally break if another engineer
tries to modify it (and forgets to add brackets that they assumed would be there).
Example:
Else If Statements
Rule: Else If and Else statements should be started on a newline, and should not be defined on the
same line of a closing bracket.
Motivation: Formatting statements like this makes it easier for engineers to parse and mentally
compartmentalize code, which is worth sacrificing a trivial number of extra lines.
Example:
Compound Conditional Statements
Rule: If a loop or if statement evaluates more than one condition at once, enclose those conditions with
parentheses.
Motivation: Mostly personal preference. However, enclosing each condition in a compound statement
makes it easier for engineers to determine which values are involved with which statement at a glance,
especially for more complicated compound statements.
Example:
Private Values
Rule: By default, C# designates all variables and functions as “private” unless designated otherwise by a
keyword such as “public” or “protected.” However, we will be explicitly designating our values using the
“private” keyword whenever it is applicable.
Motivation: This is once again a matter of personal preference. However, using the “private” keyword
often results in code “lining up better” (particularly with lists of variables) and the color-coding that is
applied to most development environments can make these values easier to find, all of which results in
“easier to read” code.
Example:
Serialized Variables
Rule: When exposing a variable in Unity using the [SerializeField] Attribute, put it on a newline above
the desired variable.
Motivation: This can make lists of variables easier to parse, as it keeps all of the variable declarations at
the same indentation. It also makes it easier to see which variables are exposed in the Unity Inspector at
a glance.
Example:
Code/Naming Conventions
Loops and If Statements
Rule: Loops and conditional statements should be defined as explicitly as possible using two-sided
expressions such as ==, >=, and <=. Avoid using != when possible, as well as evaluating a Boolean or
other values by simply negating it with a !.
Motivation: Standardizing the way loops and conditional statements are defined lowers the risk of other
engineers misinterpreting the condition. It also makes code easier to follow, because the purpose of the
condition is explicitly described, and the data types of the variables involved are easy to identify.
Example:
Variables
Ideally, if an engineer is looking over code written by someone else, they should be able to immediately
comprehend what each variable represents, as well as its scope within the context of the code they see
it in. With that in mind, we should strive to adhere to the following conventions when declaring and
referencing variables.
Naming
Variable names should describe as accurately as possible the value it contains, regardless of how long it
is. Ideally, an engineer will be able to understand what the variable is used for by simply reading the
name, and not having to search for references of it to see how it’s used in context.
Avoid abbreviating any words in a variable name, especially when it is not clear what word the
abbreviation is meant to replace. The only exception to this rule is for variables that are declared solely
to be incrementors in a for loop (ex: for (int i = 0; i < 10; i++))
Public Class Variables
Variables declared as public with a class-wide scope should be written in camelCase.
When referencing these variables within their declared class, they should be prefaced using the “this.”
identifier to indicate their class-wide scope.
Protected Class Variables
Variables declared as protected with a class-wide scope should be written in camelCase, then ended with
an underscore.
When referencing these variables, they should be prefaced using the “this.” identifier, to indicate their
class-wide scope.
Private Class Variables
Variables declared as private with a class-wide scope should be prefaced by an underscore, then written
in camelCase.
When referencing these variables, they should be prefaced using the “this.” identifier, to indicate their
class-wide scope.
Function Parameters
Function parameters should be prefaced by an underscore, then written in camelCase.
Function Variables
Local variables declared within a function should be written in camelCase.
Static Variables
Variables declared as static should have each word in its name capitalized.
When referencing static variables, always preface the variable name with the name of the owning class
(even when you reference the static variable within the owning class).
Constant Variables
Constant variables should be written in ALL CAPS, with underscores separating each word in the name.
When referencing constant variables, follow the same pattern as private, public, and static variables with
regards to prefacing identifiers.
Functions
Function names should describe its purpose as accurately as possible. Ideally, an engineer should be
able to know what a function is supposed to do within a block of logic without having to look at its
definition. With this in mind, functions should be declared with the following conventions:
- Each word in a function name should be capitalized.
- Avoid using abbreviations in a function name. Long descriptive names are better than short,
abbreviated names. - Whenever a function is referenced within the scope it was declared in, it should be prefaced
with the “this.” identifier. - Whenever possible, function names should start with a verb that describes what the function
“does” (Ex: Get, Set, Access, Calculate, etc.)- If you find it difficult to name a function with a single verb, check to see if the function
can actually be split into multiple smaller functions.
- If you find it difficult to name a function with a single verb, check to see if the function
Classes
Class names should describe its purpose as accurately as possible. Ideally, an engineer should be able to
understand the purpose of a class and have a relatively good idea of the functionality it contains by
simply reading its name. With this in mind, classes should be declared using the following conventions:
- Each word in a class name should be capitalized.
- Avoid using abbreviations in a class name. Long descriptive names are better than short,
abbreviated names.
Misc. Suggestions
The following are more subjective suggestions rather than hard-and-fast rules. However, you will likely
find that following them will result in code that is cleaner and easier to read and understand at a glance.
Compartmentalize Complex Logic in a Function
If you are writing an algorithm (or part of an algorithm) that is several lines of code long, consider
putting all of the specific logic into its own (properly named) function. Even if the function is only ever
called once, it will be extremely helpful for other engineers (or yourself, in the future), as they will not be
unnecessarily forced to parse and understand how a complex process works if they don’t have to.
Instead, they can read a descriptive function name, trust that it does what it says, and move onto other
parts of the code that they actually intend to investigate or modify.
Make Use of the [HideInInspector] Attribute
If a public variable is not meant to be set or modified in the Unity inspector, denote it with the
[HideInInspector] attribute before its declaration. This will not only make components in the Unity
editor far less cluttered, but it will also make it explicitly clear to other team members tinkering with the
project which values are “allowed” to be experimented with without causing any technical issues.
Don’t Be Afraid to Copy
When in doubt about how parts of code should be formatted or named, it’s best to emulate what you
already see in the codebase. In the end, it’s better to have a consistent coding style across the project
than it is to have a “correct” style (a futile endeavor to pursue). Alternatively, you can simply ask
another engineer on the team for advice on naming/formatting.