arrow-left

All pages
gitbookPowered by GitBook
1 of 10

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

One declaration per source file

Exactly one class, struct, enum, or interface per source file, although inner classes are encouraged where scoping appropriate.

Language

Use US English spelling.

BAD:

string colour = "red";

GOOD:

string color = "red";

The exception here is MonoBehaviour as that's what the class is actually called.

Brace Style

All braces get their own line as it is a C# convention:

BAD:

class MyClass {
    void DoSomething() {
        if (someTest) {
          // ...
        } else {
          // ...
        }
    }
}

GOOD:

class MyClass
{
    void DoSomething()
    {
        if (someTest)
        {
          // ...
        }
        else
        {
          // ...
        }
    }
}

Conditional statements are preferred to be enclosed with braces, irrespective of the number of lines required. Some cases can be pardoned.

BAD:

if (someTest)
    doSomething();  

if (someTest) doSomethingElse();

GOOD:

if (someTest) 
{
    DoSomething();
}  

if (someTest)
{
    DoSomethingElse();
}

Access Level Modifiers

Access level modifiers should be explicitly defined for classes, methods and member variables. This includes defining private even though C# will implicitly add it.

Use the least accessible access modifier, except for public member that is expected to be used by other classes in the future.

hashtag
Fields & Variables

Prefer single declaration per line.

BAD:

GOOD:

General class structure

A class must respect the following order, from top to bottom

  • Events and delegates declaration.

  • Private enums declaration.

string username, twitterHandle;
string username;
string twitterHandle;
Private internal classes.
  • Member variables.

  • Properties.

  • Methods.

  • BAD:

    GOOD:

     public sealed class Container : NetworkActor
        {
            public event ContainerContentsHandler OnContentsChanged;
    
            public Vector2Int Size;
            public AttachedContainer AttachedTo { get; set; }
            
            [SyncObject]
            private readonly SyncList<StoredItem> _storedItems = new();
            
            private readonly object _modificationLock = new();
            
            public float LastModification { get; private set; }
            
            public delegate void ContainerContentsHandler(Container container, IEnumerable<Item> oldItems,IEnumerable<Item> newItems, ContainerChangeType type);
    
            ...
     public sealed class Container : NetworkActor
        {
            public event ContainerContentsHandler OnContentsChanged;
            public delegate void ContainerContentsHandler(Container container, IEnumerable<Item> oldItems,IEnumerable<Item> newItems, ContainerChangeType type);
      
            public Vector2Int Size; 
            private readonly object _modificationLock = new();
            
            [SyncObject]
            private readonly SyncList<StoredItem> _storedItems = new();
            
            public AttachedContainer AttachedTo { get; set; }
            public float LastModification { get; private set; }
       
    
            ...

    Spacing

    Spacing is especially important to make code more readable.

    hashtag
    Indentation

    Indentation should be done using tabs — never spaces .

    Blocks

    Indentation for blocks uses tabs for optimal readability:

    BAD:

    GOOD:

    Line Wraps

    Indentation tab for line wraps should use 4 spaces (not the default 8):

    BAD:

    GOOD:

    hashtag
    Line Length

    Lines should be no longer than 100 characters long.

    hashtag
    Vertical Spacing

    There should be just one or two blank lines between methods to aid in visual clarity and organization. Whitespace within methods should separate functionality, but having too many sections in a method often means you should refactor into several methods.

    for (int i = 0; i < 10; i++) 
    {
      Debug.Log("index=" + i);
    }
    for (int i = 0; i < 10; i++) 
    {
        Debug.Log("index=" + i);
    }
    CoolUiWidget widget =
            someIncrediblyLongExpression(that, reallyWouldNotFit, on, aSingle, line);
    CoolUiWidget widget =
        someIncrediblyLongExpression(that, reallyWouldNotFit, on, aSingle, line);

    Declarations

    Applying attributes on all network related methods.

    Whenever someone add a script implementing NetworkBehaviour, they have access to the following attributes, indicating if a given method should be server only, client only, or both :

    • [Server] for server only code.

    • [Client] for client only code.

    • [ServerOrClient] for both.

    Make sure no methods goes without an attribute in a script inheriting NetworkBehaviour. The goal is to increase readability and to generate warnings or exceptions when clients or server are attempting to call something they should not.

    Methods with [ServerOrClient] attributes should stay uncommon, they make debugging harder. If possible, try to refactor the code to avoid them.

    Switch Statements

    Switch-statements come with default case by default (heh). If the default case is never reached, be sure to remove it.

    If the default case is an unexpected value, it is encouraged to log and return an error

    BAD

    switch (variable) 
    {
        case 1:
            break;
        case 2:
            break;
        default:
            break;
    }

    GOOD

    switch (variable) 
    {
        case 1:
            break;
        case 2:
            break;
    }

    BETTER

    switch (variable) 
    {
        case 1:
            break;
        case 2:
            break;
        default:
            Debug.LogError("Unexpected value when...");
            return;
    }

    Common Patterns and Structure

    This section includes some rules of thumb for design patterns and code structure

    hashtag
    Error handling

    Avoid throwing exceptions. Instead log and error. Methods returning values should return null in addition to logging an error

    BAD:

    GOOD:

    hashtag
    Finding references

    Don't use Find or in other ways refer to GameObjects by name or child index when possible. Reference by reference is less error prone and more flexible. Expect people to set the fields in the inspector and log warnings if they don't.

    BAD:

    hashtag
    RequireComponent

    Prefer RequireComponent and GetComponent over AddComponent. Having the components in the inspector let's us edit them. AddComponent limits us.

    BAD:

    GOOD:

    hashtag
    Properties with backing fields

    Properties can be used for access control to fields, and when using backing fields they can be private and let us change them in the inspector. Consider when a fields should be public and prefer properties with backing fields.

    Sometimes it's just nice to see them for debugging, even if we don't change them, so consider making more of your private fields visible.

    OKAY:

    BETTER:

    public List<Transform> FindAThing(int arg){
        ...
        if (notFound) {
            throw new NotFoundException();
        }
    }
    public List<Transform> FindAThing(int arg){
        ...
        if (notFound) {
            Debug.LogError("Thing not found");
            return null;
        }
    }
    private GameObject _someMember;
    
    private void Start() {
        _someMember = GameObject.Find("ObjectName");
    }
    public class : MonoBehaviour
    {
        private AudioSource _audioSource;
    
        private void Start() {
            _audioSource = gameObject.AddCompenent<AudioSource>();
        }
    }
    [RequireComponent(typeof(AudioSource))]
    public class : MonoBehaviour
    {
        private AudioSource _audioSource;
    
        private void Start() {
            _audioSource = GetCompenent<AudioSource>();
        }
    }
    public GameObject SomeMember;
    [SerializeField] private GameObject _someMember;
    public GameObject SomeMember => _someMember;