Skip to content

Code Style

Valk edited this page Apr 28, 2024 · 107 revisions

This page may be out of date. I'm always changing the way I code. Please talk to me directly.

Access modifiers, properties and fields

// Variables accessed from other scripts should be public properties with the exception of Godot
// exports as shown below
public int Property { get; }

// Always use fields when using Godot exports because properties can break with custom defined
// resource types and properties with the get modifier alone cannot be done.
// Below are two examples of what is acceptable
[Export] public int Width;
[Export] int secretId;

// Variables that exist only within the scope of a script should be private fields
// Please do not add the private access modifier on private fields or functions
int field;

void MyFunction() 
{
  //...
}

When to use 'var'

// Always write out the entire type unless the below scenario applies

// Using var here is okay because 'options.Actions' returns 
// 'Dictionary<int, Dictionary<StringName, Array<InputEvent>>>'
// which is rather a bit much to write out
var actions = options.Actions;

Global and local usings

// These are always defined in the main manager script as 'global usings' so there is no need
// to add them to every script
using Godot;
using System;

// Some usings should be local as they are specific to a file
using Newtonsoft.Json;

// As long as the namespace is defined, global usings will be included.
// The line below is called a file-scoped namespace; notice there are no '{ }'
namespace MyProject;

When to use CallDeferred() and GUtils.WaitOneFrame()

// Lets say 'XYZ' is a user defined class

XYZ.CallDeferred("...");

// The following is undesired because there is no way of using
// nameof(FunctionName) here which means you can not ctrl click
// on FunctionName making it tedious to find the function definition.

// Consider waiting one frame instead or finding an alterative

Use C# Delegate Events over Godot Signals

// My reason to use C# Delegate Events over Godot Signals is the same with CallDeferred, 
// you cannot Ctrl + Click to go to the original definition.
// Below is an example of how to setup and use a C# delegate

public partial class UIJob : Node
{
    // Define the event
    public event Action<int> RaccoonCountChanged;

    void OnAddRaccoonButtonPressed()
    {
        // 'raccoonCount' is a private field defined above
        // Add 1 to 'raccoonCount' and notify all listeners that this event was fired
        RaccoonCountChanged?.Invoke(++raccoonCount);
    }
}

public partial class UIInfo : Node
{
    // Listen and do something when the event is fired
    uiJob.RaccoonCountChanged += count => 
    {
        UpdateUI(count);
    }
}

Completely avoid the use of static

public partial class Player : CharacterBody2D
{
    // Static members exist for the duration of the applications life which can 
    // increase memory usage
    //
    // Static removes the 'modularity' making testability harder and a programmer 
    // is more prone to using a static where it should not be used
    //
    // By making this lives property static, this assumes that there will only be
    // one player which will make implementing multiplayer much harder later on
    public static int Lives { get; set; }

    // The Service Provider pattern can be used to avoid static
    // Non-persistent services are cleaned up just before the scene changes
    public override void _Ready()
    {
        Global.Services.Add(this, persistent: false);
    }
}

// Then in whatever class you want to get player do
Player player = Global.Services.Get<Player>();
int lives = player.Lives; // of course 'Lives' is a non-static property

Indentation

// Avoid this
public void Foo(){GD.Print("Bar");}

// This is okay
public void Foo()
{
    // Use 4 spaces over tabs
    GD.Print("Bar");
}

// Please separate very large indentation commits from more sophisticated commits