Skip to content

Latest commit

 

History

History
190 lines (168 loc) · 5.95 KB

STYLE.md

File metadata and controls

190 lines (168 loc) · 5.95 KB

Style Guide

Intro

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
}

Properties

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

References

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

Sample

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
}