-
Notifications
You must be signed in to change notification settings - Fork 0
Code convention
Unity & C# code convention. Everything is typed in the English languegue.
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 Methodes
- 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 of an function you can use a lambda expresion.
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 varbile 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;
public int variableExample2;
// Not allowed
int _variableExample3;
Private variable names always start with an '_' (Except when serialized) after which it is written in camelCasing. If the variable is accisable 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 has 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 of an 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 of an 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.
enum CookedState
{
NONE,
RAW,
COOKED
}
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 condtions, 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 recommand ternary operators when dynamicly change 1 varible. 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 it's own (local)varible.
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 an 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.
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
namespace Framework {}
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!");
}
Links | Description |
---|---|
What-is-made | Here is a list of the team members with their roles and what they have made |
Asset Naming Conventions | An in depth convention for naming files |
Code Convention | A code convention used for this project |
Definition of done | When is something done? Here's a list of criteria of the definition of done. |
Functional Design | A description about how to play the game |
Git workflow | This is our git workflow, with branch naming |
Scrum | A page with all our scrum notes and files |
Software in use | Here's a list of the software we use to make this project |
Technical Design | An in depth explanation of how complex systems work in the game |
Full code documentation | The full documentation of each feature and class |
User testing | User testing video's of the game |
- Bas de Reus - Lead developer
- Dylan Vermeulen - Scrummaster & artist
- Lisa van Boven - Lead artist & notulist
- Martijn van der Meer - Product owner & developer
- Taquila van Houwelingen - Artist & notulist
- William Soijer - Technical artist & developer