Unity & C# code convention. Everything is typed in the English language.
Always try to make your code less coupled / dependant on other code, and try to adhere to SRP (Single Responsability Princple) doing so will prevent scripts from breaking entire systems if something breaks. Also try to make your code DRY (Dont Repeat Yourself)
The namespace name is written in PascalCasing. The lines in the namespaces should should not exstend a chracter count of 120. Every class, scriptableObject and struct needs to be inside of a namespace.
namespace ExampleNamespace
{
public class ExampleScript : MonoBehaviour
{
}
}
using UnityEngine;
namespace ExampleNamespace.ScriptableObjects
{
public class ExampleScriptableObject : ScriptableObject
{
}
}
using Systems;
namespace ExampleNamespace
{
public struct ExampleStruct
{
}
}
When using namespaces we put the default namespaces first then a white space followed by our namespaces.
using System;
using System.Collections;
using UnityEngine;
using FrameWork;
using FrameWork.Enums;
using FrameWork.Extensions;
using Player;
The Class name is written in PascalCasing. If the function GetComponent is used to get a component from this gameObject you use RequireComponent above the class. I suggest using the 'sealed' and 'abstract' keywords to minmize confusion.
[RequireComponent(typeof(ExampleComponent))]
public sealed class ExampleScript : MonoBehaviour
{
}
public abstract class BaseExampleScript : MonoBehaviour
{
}
public class NonBaseExampleScript : BaseExampleScript
{
}
Class members should be grouped into sections:
- Constant Fields
- Static Fields
- Fields
- Constructors
- Properties
- Events
- Delegates
- LifeCycle Methods (Awake, OnEnable, OnDisable, OnDestroy, IEnumerator)
- Public Methods
- Protected Methods
- Private Methods
- Nested types
Within each of these groups order by access:
- public
- serializedFields
- internal
- protected
- private
All functions and events perform some form of action, whether its getting info, calculating data, or causing something to explode. Therefore, all functions should start with verbs. They should be worded in the present tense whenever possible. They should also have some context as to what they are doing.
When writing a function that does not change the state of or modify any object and is purely for getting information, state, or computing a yes/no value, it should ask a question. This should also follow the verb rule.
This is extremely important as if a question is not asked, it may be assumed that the function performs an action and is returning whether that action succeeded.
The Function name is written in PascalCasing.
private void ExampleFunction()
{
}
Access modifiers are always written with functions.
void ExampleFunction()
{
Debug.Log("Not allowed");
}
private void ExampleFunction()
{
Debug.Log("I'm a private function.");
}
protected void ExampleFunction()
{
Debug.Log("I'm a protected function.");
}
public void ExampleFunction()
{
Debug.Log("I'm a public function.");
}
Public & protected functions require a summary including the parameters and returns.
/// <summary>
/// Function description.
/// </summary>
/// <param name="parameter">Parameter value to pass.</param>
/// <returns>What the function return.</returns>
public int ExampleFunction(string parameter)
{
Return 0;
}
/// <summary>
/// Function description.
/// </summary>
protected void ExampleFunction()
{
Debug.log("I am example!");
}
When there is more than 2 parameter, we add this rule for readability. This does defeat the rule below.
// Good
private void ExampleFunction(int firstNumber, int secondNumber)
{
}
// Good
private void ExampleFunction(
int firstNumber,
int secondNumber,
float numberWithComma,
ExampleComponent targetClass,
bool isTrue,
double funnyNumber)
{
}
// Bad
private void ExampleFunction(int firstNumber, int secondNumber, float numberWithComma, ExampleComponent targetClass, bool isTrue, double funnyNumber)
{
}
When there is only 1 line of code inside a function you can use a lambda expression.
When the lambda expression is over the character limit of 120. You need to break up the code in local variables.
public void ExampleFunction() => SecondExampleFunction();
// with parameters
public void ExampleFunction(
int firstNumber,
int secondNumber,
float numberWithComma,
ExampleComponent targetClass,
bool isTrue,
double funnyNumber)
=> SecondExampleFunction();
When writing a function that does not change the state of or modify any object and is purely for getting information, state, or computing a yes/no value, it should ask a question. This should also follow the verb rule.
This is extremely important as if a question is not asked, it may be assumed that the function performs an action and is returning whether that action succeeded.
A variable is almost always private. If you need the value make a getter for it. This is also why serialized have a '_' exception.
Access modifiers are always written with variables.
// Allowed
private int _variableExample0;
protected int p_variableExample1;
protected internal int pi_variableExample;
public int variableExample3;
// Not allowed
int _variableExample4;
Private variable names always start with an '_' (Except when serialized) after which it is written in camelCasing. If the variable is accessible in the Unity Inspector, and it's an int or float it needs the Range attribute.
private Object _variableExample;
[SerializeField] private Object secondVariableExample;
[SerializeField, Range(0, 10)] private int thirdVariableExample;
[SerializeField, Range(0, 1)] private float fourthVariableExample;
Public variable names are written in camelCasing. If not a number, char, string or bool, it needs to have the Tooltip attribute.
[Tooltip("Explaination of this varible.")] public Object variableExample;
Readonly variable names are written the same as public variables so in camelCasing.
public readonly Object variableExample;
Constant variable names are written in FULL_CAPITALS with snake_casing.
public const int EXAMPLE_CONSTANT_VALUE;
Internal variable names always start with 'i_' after which it is written in camelCasing.
internal int i_variableExample;
Protected variable names always start with 'p_' after which it is written in camelCasing.
protected int p_variableExample;
Internal & protected variable names always start with 'pi_' after which it is written in camelCasing.
protected internal int pi_variableExample;
Temporary variables inside a function always need to be written out and are written in camelCasing.
private void ExampleFunction()
{
float temporaryFloat = 1f;
int temporaryInt = 1;
double temporaryDouble = 1.00;
}
Temporary constants inside a function always need to be written out and are written in FULL_CAPITALS with snake_casing.
private void ExampleFunction()
{
const float TEMPORARY_FLOAT = 1f;
const int TEMPORARY_INT = 1;
const double TEMPORARY_DOUBLE = 1.00;
}
Property names are written in PascalCasing.
public int ExampleInteger
{
get => _exampleInterger;
set
{
if(value < 0) _exampleInterger = 0;
}
}
We don't do that here. It's crucial to note that Hungarian notation is considered a suboptimal practice in coding standards.
// good
private int _targetAmount;
private ExampleComponent _system;
private ExampleStruct _currentStruct;
//bad
private int _intTargetAmount;
private ExampleComponent _exampleComponetSystem;
private ExampleStruct _exampleStructCurrentStruct;
This also apply to collections. They follow the same naming rules as mentioned before, but should be named as a plural noun.
// good
'Enemies', `Targets` and `Hats`
// bad
'DictionaryEnemies', `TargetList` and `HatArray`
The struct name is written in PascalCasing and everything inside the struct follows the usual code conventions.
public struct ExampleStruct
{
public double x;
public double y;
}
The enum name is written in PascalCasing while the constants are in FULL_CAPITALS with snake_casing.
enum ExampleEnum
{
FIRST_CONSTANT,
SECOND_CONSTANT
}
Always have the default type at the top. For example all food starts raw.
enum CookedState
{
RAW,
COOKED,
BURNED
}
When there is only 1 line of code after an if statement it comes right after it and same with the else.
if (_exampleBoolean)
ExampleFunction();
else
SeccondExampleFunction();
if (_exampleBoolean)
return;
If either the if or the else in the statement contains multiple lines of code, the if and the else do not need brackets both.
if (_exampleBoolean)
ExampleFunction();
else
{
SeccondExampleFunction();
ThirdExampleFunction();
}
When the condition has multiple conditions, make new lines for it.
// bad example
if (_exampleBoolean && 0 == 0 || true)
ExampleFunction();
// good example
if (_exampleBoolean
&& 0 == 0
|| true)
ExampleFunction();
// good example
if (_exampleBoolean && _otherExampleBoolean
|| true)
ExampleFunction();
// also good example
bool canBeCalled = _exampleBoolean && 0 == 0 || true;
if (canBeCallled)
ExampleFunction();
I highly recommend ternary operators when dynamically change 1 variable. Also, a note, don't make them to big, no ternary operator in ternary operator.
// bad example
if (_exampleBoolean)
_exampleFloat = 1;
else
_exampleFloat = 69;
// good example
_exampleFloat = _exampleBoolean ? 1 : 69;
For better performance (even very small) we make the length its own (local)variable.
int listLength = _exampleList.Length;
for (int i = 0; i < listLength; i++)
{
}
Scriptable objects holds data and/or settings, this needs to be reflected in the name. Do not forget the CreateAssetMenu attribute and put it in the correct namespace.
[CreateAssetMenu(fileName = "NewGunData", menuName = "Gun Data")]
public sealed class GunData : ScriptableObject
{
private int _maxAmmoAmount;
public int currentAmmoAmount;
public void ResetAmmoAmount() => currentAmmoAmount = _maxAmmoAmount;
public int MaxAmmoAmount() => _maxAmmoAmount;
}
Just in case, here is a bad way to make a scriptable object.
[CreateAssetMenu(fileName = "ScriptableObject / Song info")]
public sealed class SongInfo : ScriptableObject
{
[field: SerializeField, Tooltip("This is the MP3 file that should play")] public AudioClip Song { get; private set; }
[field: SerializeField, Tooltip("The person who made the song")] public string Artist { get; private set; }
[Serializable] public struct LyricNode
{
[field: SerializeField, TextArea(5, 50)] public string TextPart { get; private set; }
[field: SerializeField, Tooltip("The delay till the next lyric node")] public float TimeStamp { get; private set; }
[field: SerializeField, Range(0.05f, 1f)] public float Speed { get; private set; }
}
[field: SerializeField] public LyricNode[] Nodes { get; private set; }
}
Code style is a personal preference. It is needed for a group project, so here is a style that we use.
Want to look at a class with good code style?
There needs to be line between the namespaces in use and the current namespace. We also splits the standard namespaces and our namespaces.
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using Player;
using UI.Score;
namespace Framework
{
// example class
}
A good usage of brackets:
namespace ExampleNamespace
{
public class ExampleScript : MonoBehaviour
{
private void ExampleMethod()
{
}
}
}
A bad usage of brackets:
namespace ExampleNamespace{
public class ExampleScript : MonoBehaviour{
private void ExampleMethod(){
} }
}
Around an if and loop there needs to be an empty line above and below.
float exampleFloat;
if (_exampleBoolean)
exampleFloat = 1;
ExampleMethod(exampleFloat);
int listLength = _exampleList.Length;
for (int i = 0; i < listLength; i++)
{
Debug.LogError($"Item {listLenght} is {_exampleList[listLength].name}.");
}
ExampleMethod();
A region has a line between its content.
#region Private variables
private int _targetAmount;
private ExampleComponent _system;
private ExampleStruct _currentStruct;
#endregion
#region Public functions
public void ExampleMethod()
{
Debug.Log("Example")
}
#endregion
Have a line in between functions. This is how it should be done:
/// <summary>
/// Function description.
/// </summary>
/// <param name="parameter">Parameter value to pass.</param>
/// <returns>What the function return.</returns>
public int ExampleFunction(string parameter)
{
Return 0;
}
private void ExampleFunction()
{
Debug.log("I am example!");
}
Not like this:
/// <summary>
/// Function description.
/// </summary>
/// <param name="parameter">Parameter value to pass.</param>
/// <returns>What the function return.</returns>
public int ExampleFunction(string parameter)
{
Return 0;
}
private void ExampleFunction()
{
Debug.log("I am example!");
}