Skip to content

Understanding C# in Godot

Intro

Just like promised we're going to explain what is going on with all that ceremony in those C# scripts. This isn’t a full C# course however! We’ll only explain the code written until now in this tutorial. We'll explain more C# stuff on later pages as we go when necessary. That should keep things intresting & fun without overwhelming you with the boring stuff.

To get started you can take a look at either Intro.cs or Main.cs. It doesn't matter which one you take as they're both similar in structure right now. In this page, however we'll take Main.cs as an example to explain.

'Using statements' & namespaces

At the very top of your script, you’ll see something like:

using Godot;

This line just tells your code, “I want to use Godot’s built-in tools inside.” You’ll see this at the top of almost every script in this tutorial. One such example of a usage is the GD.Print method you used to print a message to the VSCode or the Godot console. In C# terms, this is called using a namespace. In fact you can also create your own namespaces to use in other files like this, but that's a topic for another time.

You can also skip using namespace/using statements, but the downside is that you have to repeat the namepsace for each item you're using. For example Node becomes Godot.Node & GD.Print becomes Godot.GD.Print:

public partial class Main : Godot.Node
{
    public override void _Ready()
    {
        Godot.GD.Print("Main scene started ...");
    }

    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {

    }
}
public partial class Main : Godot.Node
{
    public override void _Ready()
    {
        Godot.GD.Print("Main scene started ...");
    }

    // Called every frame. 'float' is the elapsed time since the previous frame.
    public override void _Process(float delta)
    {

    }
}

Quiz: check the above script

Do you see anything that doesn't seem to be in the original? Take a moment to compare the original with our version without the using statements. You can also copy paste the above content in Main.cs & use Git in VSCode to see what's changed. If you noticed the change but didn't understand the why that's ok. Just read the next hint.

Unnecessary usings statements

What you also see is a using System; statement. This line tells Godot you also want to use some built-in tools from C#/DotNet. This line is however greyed out by VSCode, since we didn't make use of anything from that set.

Inheritance

The next line in our script is this one:

public partial class Main : Godot.Node

Here there are multiple things going on. The most important one is this one:

class Main : Godot.Node

or something like this if you still have the using statements:

class Main : Node

What this basically tells Godot is that this is a class (e.g. a Godot script component) for our Main node & that is a subtype of Node. You can also compare this somewhat to real world hierarchies. For example Tuna is a Fish & Fish is an Animal. The name in our script doesn't matter however, since Godot attaches scripts based on their file rather than the script content. For clarity however it's best practice to keep those names consistent.

Class accesibility modifiers

The other 2 things in the previously mentioned line are public & partial. As for public you can simply remove it. What this is meant to do is to make your class accesible from other projects. This is very handy when writing something similar to Godot classes which you want to use for multiple game projects. Removing this keyword implies to C# that you don't care about the world outside your project & this class is simply an internal thing. In other words

class Main : Godot.Node
is interpreted as
internal class Main : Godot.Node

Another use case for the public keyword is accesibility from test projects, but we'll discuss that on later pages.

As for the partial keyword its something that Godot expects in order to add some additional logic to your class behind the scenes. This keyword is usually combined with something called source generators & is very handy when you're dealing with lots of repetitions that can be automated through a tool.

There are also other use cases like chopping very large files into smaller pieces without breaking the logic. For example, we could split Main.cs like this:

// Main.Startup.cs:
public partial class Main : Godot.Node
{   
    public override void _Ready()
    {
        GD.Print("Main scene started ...");
    }
}
// Main.Processing.cs:
public partial class Main : Godot.Node
{
    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(double delta)
    {
    }
}
// Main.Processing.cs:
public partial class Main : Godot.Node
{
    // Called every frame. 'delta' is the elapsed time since the previous frame.
    public override void _Process(float delta)
    {
    }
}

That is usually considered a bad practice however, since its not a logical way to split up your code. What should be done instead is defining other classes that do split up the work into multiple smaller parts.

Currently however, we don't have a lot going on so it's too early to think about that. Just know that the partial keyword is an essential part of a Godot script class & that Godot won't run without it.

Overriding inherited methods

The next thing on our list is the override keyword. This keyword tells C# that you want your own version of a specific method defined by the parent class. In our case that would be the _Ready & _Process method of the Node class.

This is something we can also use when defining our own classes. C# is very strict about which methods can be overridden however. Unless a method isn't virtual it's not possible to override it. The Node class for example had to define the _Ready & _Process methods somewhere something like this:

public virtual void _Process(double delta)
{
}

public virtual void _Ready()
{
}
public virtual void _Process(float delta)
{
}

public virtual void _Ready()
{
}

Quiz: can you define a better method than _Process

Like we discussed earlier _Process doesn't fully cover the purpose behind it. Can you use what you learned about inheritance & overriding methods to define a parent class with a better name?

Return types & parameter types

Let's look at this line again:

public override void _Process(double delta)

There are 3 more things here:

  • Return type: which in this case is void
  • Parameter type: which in this case is double
  • Paramater name: which in this case is delta

A return type in C# is often used to return the result of a calculation or some kind of status report. In our case however we don't have anything to return, so the return type is void. That's also how the _Process & _Ready methods are defined in the Node class of Godot. This also means you can not change this without breaking your Godot game.

The parameter type can be anything from a number type to a reference to another class. In our case its a number of type double meaning its a floating point number like 2.5 (2 and a half). Since this type was defined by Godot in the Node class, you can't change this one either.

Lastly, you have the parameter name delta, which is how the parameter inside the _Process method is named. Unlike the other 2 things, you can change the name delta to whatever you want. That is because C# doesn't usually care (see the warning below) about the parameter name. It looks at the type instead.

This means we can even get rid of the comment explaining delta by simply renaming it to something more descriptive like elapsedTimeSincePreviousFrame.

Just for your understanding, a method can also be defined like this:

public double CalculateSum(double number1, double number2)
{
    return number1 + number2;
}

To use this you can do something like this:

double sum = CalculateSum(2.5, 1.5);
GD.Print("The sum of 2.5 & 1.5 is " + sum);

Named parameters

Earlier we said that C# doesn't usually care about parameter names & looks at the types instead. That’s still true, unless you use something called a named parameter when calling a method.

For example, our CalculateSum method could also be called like this:
CalculateSum(number1: 2.5, number2: 1.5)

This is called a named parameter. It may seem helpful when a method has several values that are hard to tell apart, like:

CalculateDiscount(123, 19, true)
vs
CalculateDiscount(basePrice: 123, vatPercentage: 19, shouldLogSteps: true)

That second version is easier to read, but using named parameters is often just a quick fix to avoid improving the method itself.

In practice, overusing named parameters can lead to fragile code, especially if someone later renames the parameters in the method. It might look like a clean solution, but it’s often just a quick fix when the method design could use more work.

Don’t worry too much about that for now. Just be aware that named parameters are mostly used to make things look more readable. This is often not the best solution & there are better solutions which we'll explore later.

Quiz: can you avoid named parameters in the above examples?

Until this point you have had enough info to use one simple solution to avoid named parameters. Suppose you would encounter something like CalculateDiscount(123, 19, true) in some point of your career. What can you do with your current skill to avoid using that, but still keep the code clear? If you need a hint, you may want to look at the CalculateSum example right before the named parameters warning!

Method accessibility modifiers

By looking at this line again, you also notice the public keyword present here:

public override void _Process(double delta)

This is because just like with classes, methods in C# can have accessibility modifiers such as public, private, or internal. These words tell C# who is allowed to use the method:

  • public: Anyone can use this method, even from other scripts or projects.
  • private: Only code inside the same class can use this method.
  • internal: Only code inside the same project can use this method (this is the default if you don't write anything).

There is a lot more to cover, but that is enough for this page. In the upcoming pages we're going to apply some stuff we learned here as well as 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.

This is a work in progress

I still need to write those parts yet, so be patient with me and come back on a regular basis to see if those parts are there yet