While this style isn't absolutely required, it is recommended for higher quality code. If you want to see a full style guide example, skip to the end. Please don't hesitate to open a new issue if you have questions!
When making your Unity scripts, break down your variables into three regions:
- Properties: These are scripts that change during runtime and are modified by state.
- Settings: These are variables that are set before the scene begins and are referenced in the code. They shouldn't change.
- References: These are references that must be populated in the Inspector before playing the scene. Shouldn't be null. You can see an example of this below:
public class Example : MonoBehavior {
#region Properties
public int counter = 0;
#endregion
#region Settings
public int threshold = 15;
#endregion
#region References
public GameObject RefToObjInScene;
#endregion
}
For properties, we may want variables to be accessible to other scripts, but not publically modifiable. We can enforce this by using C# autogenerated getter/setters, like this:
#region Properties
protected int _counter = 0;
public int Counter {
get { return _counter; }
protected set { _counter = value; }
}
#endregion
When viewed, by other scripts, they will be able to use Counter
to get values, but not modify them. However, you may have
noticed that when setting variables to protected or private, they are hidden from Unity's Inspector by default. We can force
it to be shown via an Attribute,
which can only be used on variables. In this case, we'll use the UnityEngine-specific attribute [SerializeField]
like this:
#region Properties
[SerializeField]
protected int _counter = 0;
public int Counter {
get { return _counter; }
protected set { _counter = value; }
}
#endregion
In addition, we will also want to use some other attributes as well, such as [Header("Some Header Title")]
and
[Tooltip("Viewable when hovered in the Inspector")]
like this:
#region Properties
[SerializeField,
Tooltip("Value that counts"),
Header("Properties")]
protected int _counter = 0;
public int Counter {
get { return _counter; }
protected set { _counter = value; }
}
#endregion
In addition, we can also use Visual Studio XML-markup tags to document our code like so, but keep in mind that attributes must go after comments!
#region Properties
[SerializeField,
Tooltip("Value that counts"),
Header("Properties")]
protected int _counter = 0;
/// <summary>Counts in seconds, incremented in Update()</summary>
public int Counter {
get { return _counter; }
protected set { _counter = value; }
}
/// <summary>Example with both Tooltip and XML Comment that programmer will see</summary>
[Tooltip("Example with both Tooltip and XML Comment that Unity user will see")]
public bool IsCounting;
#endregion
We assume that references will always be populated in the Inspector, but this might not necessarily happen. Ensure that your
references are checked as soon as you can, which I typically do in a new region called Unity Methods
and Awake()
like this:
#region Unity Methods
/// <summary>Checks references and sets initial values</summary>
/// <exception cref="System.ArgumentNullException">Thrown when ball is null</exception>
protected virtual void Awake() {
// Check references
if (ball == null)
throw new ArgumentNullException("Ball is not set in inspector!");
// Set initial values
Counter = 0;
}
#endregion
Finally, you can see an example below that utilizes all of these principles.
// System
using System;
// Unity Engine
using UnityEngine;
using UnityEngine.UI;
// External
using Valve.VR;
using TMPro;
/// <summary>
/// Longer example that explains the purpose of this class.
/// It can include multiple lines.
/// </summary>
public class Example : MonoBehavior {
#region Properties
[SerializeField,
Tooltip("Value that counts by 1 to MaxCount."),
Header("Properties")]
protected int _counter;
/// <summary>Counter that counts by 1, but is clamped at MaxCount!</summary>
public int Counter {
get { return _counter; }
protected set {
if (_counter = Mathf.Clamp(value, 0, MaxCount) == 0)
ResetCounter();
}
}
#endregion
#region Settings
/// <summary>Maximum amount that Counter can reach</summary>
[Tooltip("Maximum amount that Counter can reach"), Header("Settings")]
public int MaxCount = 5;
#endregion
#region References
[Header("References")]
public GameObject ball;
#endregion
#region Unity Methods
/// <summary>Checks references and sets initial values</summary>
/// <exception cref="System.ArgumentNullException">Thrown when ball is null</exception>
protected virtual void Awake() {
// Check references
if (ball == null)
throw new ArgumentNullException("Ball is not set in inspector!");
// Set initial values
Counter = 0;
}
#endregion
#region Public Methods
/// <summary>Wrapper function for UI button to change MaxCount</summary>
/// <param name="delta">Amount to modify MaxCount by</param>
public void SetMaxCount(int delta) {
MaxCount += delta;
}
#endregion
#region Methods
/// <summary>Resets other components or something</summary>
private void ResetCounter() {
Counter = 0;
// Pretend resetting the counter involves some other stuff
}
/// <summary>Returns a value</summary>
/// <returns>A random float between 0 and 1</returns>
private float GetRandomFloat01() {
return Random.Range(0,1);
}
#endregion
}