Godot GDScript and C#

GDS is a very capable language, no doubt about it. And it’s very easy to learn and you can do some amazing things with it without having to learn in-depth programming concepts like Object Oriented Programming, Inheritance, classes vs structs, modularity, interfaces, etc.

But it’s not a great language to build maintainable, modular code.

For that, C# is a much better option.

Don’t get me wrong, I think GDScript is a great idea for people to start learning and be able to get something running very quickly. And it’s got some wonderful features like @ready variables, or the fact that you can Cmd-drag (Ctrl-drag on Windows) a node to the GDS editor and it will write the node access for you! That’s beautiful. 

But in the long run, it makes things a lot more difficult than they should be. I learned this the hard way. And I’ve got the hair loss to prove it.

I would encourage people to try to write the meat of the game systems in C# and then simply expose some type of API for GDS to call them. 

The reasons for this are numerous:

Everything is public

In GDS everything is public, every property, every method is public. 

This can lead to all sorts of trouble and confusion, not only when writing the code and using autocompletion (every method and property will be suggested for you by the compiler) but when actually running your code and finding out that your player explodes instead of walking, WTF?

Because everything is public, it means it is accessible from anywhere in your code. Any class can call any method in any other class! There is no encapsulation and no protection.

This can lead to very hard-to-find bugs because you’re never sure who is modifying your property outside its own class.

You’ll be swearing at the screen a lot asking “How the fuck are you changing? Who is modifying you? What the hell is happening???!!!” 😅

It also doesn’t help that GDS doesn’t have a way to show you a method or property’s hierarchy call (who is calling or changing it; more on that later), something trivial to do in C# with a decent compiler.

In short, no privacy, no decency!

No type safety

Version 4.3 of Godot has added the ability to specify a variable’s type, giving us a bit of type safety. But by default, every variable is dynamic. 

This is a great idea to make a language easier to learn but a terrible one when your code grows or gets more complicated. 

Anyone who has spent any serious amount of time using a dynamic language (Javascript is an especially egregious offender) will recall countless wasted hours and hair-pulling trying to debug an issue only to find out later that a string variable was turned into an integer or vice-versa. Time much better spent looking at dog videos online.

There is nothing more soul-crushing than finding out that you were expecting “3” and all along that variable was giving you 3. Or worse, getting confused between 0 and “0”

“Is it an Oh, a zero? A bird? A plane? A spaceship? What the fuck is it???? Ahhh!”

Unhelpful errors

In GDS you get a lot of these errors:

Expected end of statement after variable declaration, found “Identifier” instead.

What the hell does that mean GDS? What Identifier? Just tell me what the problem is, don’t get all mystical and existencial on me!

It could mean a number of different things, and it’s terribly ambiguous. It would be a lot more helpful if the IDE pointed out what the unexpected statement was but it doesn’t, you have to guess. And unless you are very familiar with GDS, it’s very hard to figure out what it’s complaining about. Which only helps to make you feel even more stupid, never a good thing.

No method calls hierarchy

There is no way in GDS to find out which methods are modifying which properties, or which methods are being called from another class or script. 

Sure, there is a search everything feature but that’s extremely tedious and inaccurate, it will show you everywhere that search key is used, including comments or dialogue!!! Leading, again, to more hair-pulling and waning confidence.

With C# in and an IDE like JetBrains Rider, you’ve got  Find usages, which not only shows yo all the method calls but even displays a very helpful pane where the exact call is being made!

It even works for properties 😻

This might seem like a trivial thing but when you are debugging or revising your architecture, it’s a life-saver.

Modularity is hard to achieve

Without interfaces and limited inheritance, making your code modular and thus a lot more maintainable, is very hard to achieve.

You can inherit from built-in base classes and that’s about it. No protocols, no interfaces, no contracts of any kind that you can enforce or even design.

Sure, you can include a script within another script, trying to separate functionality but when you run the game, that simply becomes a huge, messy class with all properties and methods part of that monolithic, Godzilla-like class. That’s awful. It leads to all sorts of Frankenstein monster issues. God-classes are never a good idea.

Debugger is crippled

Sorry to say this but the built-in debugger in Godot is… not great, to use a euphemism. In fact it’s pretty bad. I love Godot but the GDS debugger is close to useless.

There is no way to run or even inspect your code when debugging! This is the number one feature any debugger should have. 

You place a breakpoint in your code, run your game, the game halts and you can see the value of properties on the right hand side, but there his no way to interactively run code or even type a property or method to see what its state or value is at that point in time!

This leaves you the only option of writing print outputs all over your code! 

Does this look familiar?

printerr("What the hell happens here? What is the value of myVar: " + myVar)

In C# you have a perfectly good, interactive debugger where you can output property values, call methods and even run new code. The way a debugger should work.

What’s worse, the editor will not even warn you of non-existent methods! This line will happily compile:

var a = spacing * b.get_position_in_parent() - PI / 2

Only to explode when you run your game with this error:

Invalid call. Nonexistent function ‘get_position_in_parent’ in base ‘TextureButton’.

Why the hell can’t the editor tell me that before I run the game?

Encourages bad practices

Granted, this is not necessarily a direct side effect of GDS but almost. Because everything is public and you can’t modularise your code, GDS actually encourages people to dump functionality in its main methods and not worry about modularity or which methods should actually be exposed or violating the law of Demeter or the SOLID principles.

How many times have you seen examples of the _ready() and _process() functions filled with all sorts of code?

Does this look familiar?

func _physics_process(delta: float) -> void:

var input_direction := Vector2.ZERO

input_direction = Vector2(

Input.get_axis("move_left", "move_right"), Input.get_axis("move_up", "move_down")

)

var desired_velocity := input_direction * RUN_SPEED

var steering = (desired_velocity - _velocity) * DRAG_FACTOR * delta

_velocity += steering

move_and_slide()

That’s very common and very bad practice. Inside the _physics_process() method, which gets called every frame, we have all this separate functionality:

  1. Declaring a variable to be used later. This should really be done outside in an instance variable
  2. Getting the input from the keyboard. This should be done inside its own method that does only that: gets keyboard input and calls another method
  3. Setting 2 more variables based on the keyboard input
  4. Finally calling move_and_slide()

Granted, you can do the same thing with C# or any other language but since GDS is geared towards ease of use and getting quick results, I think it is particularly problematic with the examples you can find for it. And, after all, we all learn by doing.

And this is a very succinct example. I’ve seen code in the _process() method that does so much more. Remember, this method gets called for every single frame in your game, constantly! This method should be precious, almost untouchable, not a bin to dump all your code in and then wonder why the hell your game is behaving badly. Bad dog!

Best of both worlds

But it’s not all doom and gloom. 

Fortunately, C# and GDS are good friends and play well together, so there is nothing stopping you from using both languages. 

Build your main systems in C# with a good IDE (Rider for example is superb) and then expose only the necessary methods or classes to GDS so you can still leverage its ease of use and timesaving features.

Just don’t be lured into writing all your game in GDS, I think you will later regret it like I did 😅.

Happy coding!