Skip to content

Best Practices

Justin Orringer edited this page Jun 22, 2022 · 1 revision

Below are the general style guidelines that should be followed throughout the project code.

Naming Conventions

Code Naming Conventions

Token Type Style
Namespace PascalCase
Class PascalCase
Enums EPascalCase
Interfaces IPascalCase
Public Field/Member Variable camelCase
non-Serialized Proteced/Private Field/Member Variable _camelCase
Serialized Proteced/Private Field/Member Variable camelCase
Property PascalCase
Class Method PascalCase
Method Parameter camelCase
Local Variables camelCase

Asset and File Names

Note: All files and assets should be formatted using PascalCase along with the appropriate prefix and/or post-fix specified below.

Asset or File Type Prefix Post-fix Notes
Scripts None None Generally, a script file should primarily contain a single class type, and the name of the file should match the name of that class.
Materials None None
Textures and Sprites None None
Spritesheets None None
Prefabs None None
Audio Sources None None
Animations None None
Models None None
Fonts None None
Scenes None None
ScriptableObject Instances None None

Git Branch Names

All git feature branches should be written in kebab-case and in the form unityid/feature-name.

Tabs vs Spaces

Spaces. It is better for consistent readability. A tab should be equal to 4 spaces.

NOTE: While I know this is a hotly debated topic, I am willing to die on this hill.

Scope Brackets {} and if/else if/else Statements

Scope brackets for namespaces, classes, methods, if/else if/else statements, for statements, etc. should start on a new line as shown below.

namespace BuildABot
{
    class MyClass : Monobehaviour
    {
        public void MyMethod()
        {
            for (int i = 0; i < 5; i++)
            {
                // ...
            }
        }
    }
}

When writing if/else if/else statements with only a single line of instructions, brackets can be skipped entirely and all of the code can be placed on the same line as shown here:

if (someCondition) doTheThing();

Documentation and Method Comments

Generally speaking, every function should be documented since so many eyes and experience levels will be looking at the code of the project. At the very least, all public and protected functions should have some amount of documentation associated with them. While it is understandable to skip documentation on small functions with highly descriptive names and low complexity or in the sake of time when under crunch, it is generally encourage to document as you go for longer term code quality and potential documentation pages generation.

In Unity and C#, something similar to JavaDoc comments are used. Here is an example from the Utility class:

/**
 * Executes the provided action after the specified delay.
 * <param name="context">The monobehaviour context used to execute the action.</param>
 * <param name="seconds">The number of seconds to wait before performing the action.</param>
 * <param name="action">The action to perform.</param>
 * <returns>The coroutine enumerator.</returns>
 */
public static IEnumerator DelayedFunction(MonoBehaviour context, float seconds, Action action)
{
    // ...
}

Basically, HTML tags are used in place of things like @param. Otherwise it is pretty similar.

Additionally, if you prefer you can use /// at the start of each line instead of the JavaDoc style.

/// Executes the provided action after the specified delay.
/// <param name="context">The monobehaviour context used to execute the action.</param>
/// <param name="seconds">The number of seconds to wait before performing the action.</param>
/// <param name="action">The action to perform.</param>
/// <returns>The coroutine enumerator.</returns>

vs the JavaDoc style

/**
 * Executes the provided action after the specified delay.
 * <param name="context">The monobehaviour context used to execute the action.</param>
 * <param name="seconds">The number of seconds to wait before performing the action.</param>
 * <param name="action">The action to perform.</param>
 * <returns>The coroutine enumerator.</returns>
 */

The former is closer to traditional C# documentation style. I personally prefer the JavaDoc style, but either is fine and should both be understood by C# IDEs. Just be consistent.

Local Variable Types and var

When declaring local variables, you either have the option of explicitly declaring the type of the variable or you can use var instead to allow the compiler to automatically deduce the type of the variable based on what is assigned to its value. While both are acceptable, it should be preferred to use explicit type names for better code readability. If the type of the variable is not readily known or is excessively verbose (such as the pair types used when iterating maps), it is ok to use var.

For anyone unfamiliar with this syntax, here is an example of declaring strings in each way:

string someText = "Hello World";
var someOtherString = "Goodbye World";

Namespaces

All classes and types declared in the project should be declared inside of a namespace named BuildABot. If a more granular level of encapsulation is desired, use the namespace BuildABot.SomeSubsystem. More granular namespaces should not contain just a single type, and should be reserved for larger systems. Generally, only something that you could see being broken off into a standalone package should have a deeper namespace.

It is important that we keep our types within our own namespace to avoid polluting the global namespace and potentially conflicting with existing types or content from third party libraries or other vendors.

Examples:

using UnityEngine;

namespace BuildABot
{
    public class MyClass
    {
        //...
    }
}
using UnityEngine;

namespace BuildABot.SomeSubsystem
{
    public class MyOtherClass
    {
        //...
    }
}

Classes that share a namespace will automatically have access to each other, but any classes outside of the namespace will either need to include a using declaration at the top of their file or method like

using BuildABot;

or they can simply append the namespace at the front of the type when using it like

BuildABot.MyClass.SomeMethod();