arrow-left

All pages
gitbookPowered by GitBook
1 of 20

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Methods

Methods are written in PascalCase. For example DoSomething().

chevron-rightMethods that are used as the OnChange callback of a [SyncVar]hashtag

Should have the Sync prefix, as in:

chevron-rightMethods that listen to an eventhashtag

Should have the prefix Handle, as in:

chevron-rightMethods that are preceded by the [Server] attributehashtag

Do not need to have the Server prefix on them. This is still something we are thinking about, both uses are fine for now.

As in:

chevron-rightMethods that are preceded by the [ServerRpc] attributehashtag

Need to have the Cmd prefix on them, as in:

chevron-rightMethods that are preceded by the [ObserversRpc] attributehashtag

Need to have the Rpc prefix on them, as in:

[SyncVar(OnChange = nameof(SyncUsername))] string _username;
private void SyncUsername(string oldUsername, string newUsername, bool asServer) {}
clownBomb.OnExploded += HandleClownBombExploded;
private void HandleClownBombExploded() 
{
    Honk();
}
[Server]
public void ServerKillPlayer() {}
[Server]
public void KillPlayer() {}
[ServerRpc]
public void CmdSpawnItem(GameItems item) 
{
    SpawnItem(item);
}
[ObserversRpc]
private void RpcSpawnVisualProjectives() 
{
   _particleSystem.Play();   
}

Nomenclature

On the whole, naming should follow C# standards.

Classes & Interfaces

Classes are written in PascalCase. For example RadialSlider.

Interfaces should always have the prefix I, as in IOpenable.

Fields

Private fields have the _ prefix, static, const and public fields are in UpperCamelCase.

For example:

public class MyClass 
{
    public int PublicField;
    private int _packagePrivate;
    private int _myPrivate;
    protected int _myProtected;
}

BAD:

private int myPrivateVariable

GOOD:

private int _myPrivateVariable

hashtag
Field Position

All fields should be at the top of the class, before any methods.

Namespaces

Namespaces are all PascalCase, multiple words concatenated together, without hypens ( - ) or underscores ( _ ):

Namespaces should be used for major systems, such as Atmospherics, or Electrics. Everything else should be without namespace.

BAD:

com.ress3dclient.scripts.structures

GOOD:

SS3D.Systems.Tilemaps.Structures

The C# Style Guide

This style guide is based on this other C# style guidearrow-up-right and expands upon it.

You can use whatever file editor you want, but Visual Studioarrow-up-right is probably the most popular and recommended choice. It's free, has many features, and integrates well with Unity and Git.

If you're a student, we recommend getting a copy of JetBrain's Riderarrow-up-right which adds a lot of code hints and shortcuts that will significantly improve your code quality.

Our overarching goals are clarity, readability and simplicity. Also, this guide is written to keep Unity in mind.

hashtag
Inspiration

This style guide is based on C# and Unity conventions, and discussions within the development team.

hashtag
Automation

We recently added an file to automate the code review process, it is a file you can add to or and it will apply the formatting and style we use to your analyzers, telling you when something is out of order.

EditorConfigarrow-up-right
Visual Studioarrow-up-right
Riderarrow-up-right

Declarations

Delegates

Delegates are written in PascalCase.

When declaring delegates, DO add the suffix EventHandler to names of delegates that are used in events.

BAD:

public delegate void Click()

GOOD:

public delegate void ClickEventHandler()

DO add the suffix Callback to names of delegates other than those used as event handlers.

BAD:

public delegate void Render()

GOOD:

hashtag
⚠️ IMPORTANT!

Using built.in C# features, such as Action, is encouraged in place of delegates.

Brace Style

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

BAD:

GOOD:

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

BAD:

GOOD:

Events

Prefix event methods with the prefix On.

BAD:

GOOD:

Misc

In code, acronyms should be treated as words. For example:

BAD:

GOOD:

Parameters

Parameters are written in camelCase.

BAD:

GOOD:

Single character values are to be avoided except for temporary looping variables. You can also use _ for unused variables, m for and e for .

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

One declaration per source file

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

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.

public delegate void RenderCallback()
class MyClass {
    void DoSomething() {
        if (someTest) {
          // ...
        } else {
          // ...
        }
    }
}
class MyClass
{
    void DoSomething()
    {
        if (someTest)
        {
          // ...
        }
        else
        {
          // ...
        }
    }
}
if (someTest)
    doSomething();  

if (someTest) doSomethingElse();
public static event CloseCallback Close;
public event Action Changed;
public static event CloseCallback OnClose;
publc event Action OnChanged;
XMLHTTPRequest
String URL
findPostByID
XmlHttpRequest
String url
findPostById
void DoSomething(Vector3 Location)
void DoSomething(Vector3 location)
network messages
event bus's events

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;
}

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.

Prefer single declaration per line.

BAD:

GOOD:

string username, twitterHandle;
string username;
string twitterHandle;
if (someTest) 
{
    DoSomething();
}  

if (someTest)
{
    DoSomethingElse();
}

General class structure

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

  • Events and delegates declaration.

  • Private enums declaration.

  • Private internal classes.

  • Member variables.

  • Properties.

  • Methods.

BAD:

GOOD:

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.

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 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);

        ...
for (int i = 0; i < 10; i++) 
{
  Debug.Log("index=" + i);
}
 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; }
   

        ...
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);
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;