Skip to content

Some basic refactors

Intro

Now we got some basic understanding of how Godot works with C#, we can continue doing some minor refactoring to make things a little easier to understand.

One thing that made the code harder to understand was the _Process method. Godot's naming isn't clear. We needed extra comments to explain what it does. Relying on comments is not ideal however, especially early on. Their style varies widely and they lack the structure of method names. This even makes it tough for AI tools to interpret code consistently.

reminder

A comment is code that is meant as a commentary rather than something that executes.

Since we can't easily change how Godot named things, one other way is to apply what we learned in the previous page about inheritance.

Extracting a base Node class

In order to deal with the method naming, open a C# file in your Godot project. It doesn't matter which file as we'll also going to be using some refactoring options we got from the installed extensions for a smoother experience.

In this page, we'll going to use Main.cs as an example. Update whichever file you choose with the following lines after the using statements:

public partial class CleanNode : Node
{
    public virtual void OnReady() { }
    public override sealed void _Ready() => OnReady();

    public override sealed void _Process(double timeElapsedSincePrevFrame) =>
        ProcessFrame(timeElapsedSincePrevFrame);

    public virtual void ProcessFrame(double timeElapsedSincePrevFrame) {}
}
public partial class CleanNode : Node
{
    public virtual void OnReady() { }
    public override sealed void _Ready() => OnReady();

    public override sealed void _Process(float timeElapsedSincePrevFrame) =>
        ProcessFrame(timeElapsedSincePrevFrame);

    // Strictly speaking the following should also use a `float` type
    //  (e.g.: `float timeElapsedSincePrevFrame`)
    // C# automatically converts values between floats and doubles however, so 
    // you can use a double here to prepare for Godot 4!
    public virtual void ProcessFrame(double timeElapsedSincePrevFrame) {}
}

Don´t worry about the virtual & sealed keywords for now. That comes later. Also if you need an explanation about the => syntax, its basically a way to tell C#, hey this method is just a wrapper for a single statement. This is called method body expressions in C# terms.

Refactoring the node structure

After you updated your code, put cursor somewhere inside the CleanNode text like this:

Cursor inside clean node text

Then click on the light bulb or press [CTRL]+[.] to open an options menu available for that part of code. From the menu that opens, choose move type to CleanNode.cs:

Move type to clean node option

Now, before moving on, open both Main.cs and Intro.cs and replace any instance of Node as the base class with CleanNode.

Main.cs should have line that looks like this:

public partial class Main : CleanNode

Intro.cs should have a line that looks like this:

public partial class Intro : CleanNode

If you did this correctly you should see an error in the VSCode editor right where _Ready & _Process overridden in Main.cs & Intro.cs. The error says something like:

cannot override inherited member 'CleanNode._Ready()' because it is sealed

That is because of the sealed keyword in the CleanNode class. If you remove it the error goes away, but that's not what we want. The sealed class was intentional because we want to force new method names in inherited classes.

What we instead want to do is to use the new methods introduced by the base class called CleanNode in our case. Instead of overriding _Ready & _Process inside Main.cs & Intro.cs we need to override OnReady & ProcessFrame. Also we don't need the extra comments anymore as the new methods are now more descriptive.

This is how your Main.cs should look like:

using Godot;

public partial class Main : CleanNode
{
    public override void OnReady()
    {
        GD.Print("Main scene started ...");
    }

    public override void ProcessFrame(double timeElapsedSincePrevFrame)
    {
    }
}

Your Intro.cs should look like this:

using Godot;

public partial class Intro : CleanNode
{
    public override void OnReady()
    {
    }

    public override void ProcessFrame(double timeElapsedSincePrevFrame)
    {
        GetTree().ChangeSceneToFile("res://Main.tscn");
    }
}
using Godot;

public partial class Intro : CleanNode
{
    public override void OnReady()
    {
    }

    public override void ProcessFrame(double timeElapsedSincePrevFrame)
    {
        GetTree().ChangeScene("res://Main.tscn");
    }
}

Commit changes into git

As the last step you can commit your changes into git with a message like: Extracted CleanNode class for better readability

You can do this with the same steps explained in the testing git in VSCode page.

If you did this properly your git graph will update with something like this:

Git graph update

Some notes regarding refactoring

So far this tutorial touched on certain refactors you can do to improve your code. Please note however, that refactoring is a continuos process. For example you may have followed this tutorial and let the Intro scene open the Main scene. The reason behind that is, while working on the game this tutorial is derived from, there was initially only 1 scene. Main may therefore have been a reasonable name at the start. Later an Intro scene was introduced however that needed to come before the gameplay mechanics activated. Since those where in a "Main" scene, the most straightforward update was to open Main from Intro. While this doesn't matter to the players of your game, it could cause confusion when developing, because Main indicates the start of something. You may end up in a similar situation, so its important to continuously review the project structure to set things up in a sensible way.

There is a lot more to cover, but that is enough for this page. In the upcoming pages we're going to do some modelling, apply some additional configuration to further simplify the workflow. After that, we’ll return to the intro scene and continue working toward the final game logic and setup, including some modeling steps along the way.