You’re Doomed to Fail: The Sad Truth About GDScript in Godot

Chad Johnson
7 min readOct 5, 2023
Letters “GD”, with a small red cross underneath them.

TLDR / Summary

Unless you’re sticking to simple editor tools or isolated nodes, GDScript isn’t your friend.

You’ll face minor annoyances and huge roadblocks that will seriously mess up your game development

Ultimately, you’ll either abandon your project out of frustration or switch to a more reliable language. Make the smart choice.

Introduction

After a year of wrestling with GDScript, I was hesitant to switch to C#.

Having never coded in C# before, I wrongly assumed it was the problem, not the solution.

I was wrong — very wrong. Once I switched, the majority of my coding headaches vanished. Now, I regret not making the move sooner.

The Static Typing Circus

Starting with GDScript’s problematic static typing. Got a parsing issue in one script? Brace for impact — if it’s a dependency for others, the whole system implodes.

Sometimes, there’s not even a real error. Yet, you are forced to manually open and save each affected script, trudging down the dependency chain until things resolve. It’s not just an annoyance; it annihilates productivity. Got circular dependencies? That’s a whole new level of hell.

The Indentation Issues in GDScript

Mixed tabs and spaces in GDScript, maybe from copying code online or from a chat? Prepare for a parsing nightmare.

Auto-format to fix it? Ha, good luck. Godot might just jumble your indents further, effectively destroying your script’s logic.

Now, you’re left sifting through each line to fix what should’ve been a non-issue. Not only is it wasting your precious coding time, it’s downright infuriating.

Public, Private, What Now?

GDScript lacks access modifiers. You’re stuck prefixing private members with an underscore — a using workaround, instead of a language feature.

OOP Limitations

No abstract classes or methods, no interfaces, and you can’t even make structs. Plus, you can’t override properties in subclasses.

You’re basically handcuffed if you want to do any serious OOP.

The Debugging Black Hole

Need to debug multithreaded code? In GDScript? Not a chance. It becomes a pain to deal with errors on another thread.

Want to use Hot Reloading and async code together? Think again. Use await on signals or functions, and you're inviting a ton of crashes. GDScript isn’t just unstable in these scenarios — it’s downright unreliable.

Tuple and Error-Handling Troubles

Need to return multiple values? GDScript’s lack of tuples will have you either sacrificing static typing with arrays or eating performance costs with custom objects (which can’t even be generic!).

Godot’s “keep running” philosophy isn’t an excuse to skip on even basic error handling. If try-catch is too hard to implement and maintain, maybe don’t make a programming language?

The real part? Errors are inconsistent — sometimes it’s a Godot error, other times a GDScript one. The docs are silent on a function’s failure behaviour. It might continue, might return null; you’re left guessing.

Iteration? More Like Irritation

With GitHub Copilot, Powerful IDEs and language features like method extensions, C# offers speed and reliability that makes GDScript’s “quick iteration” claims look misleading.

You’ll not only code faster, but also skip repeated work in other languages.

GDScript lacks method extensions, complicating even simple tasks. For instance, fetching nodes of a given type becomes a mess due to poor function importing (untyped) and no method extension syntax.

Below is an example of how method extensions can be used, and their “alternative” in GDScript.

// A class that contains all your extenstion methods, you can add as many as you like.
public static class NodeExtensions {
public static IEnumerable<T> GetDescendantsWhichAreA<T>(this Node node) where T : class {
foreach (Node child in node.GetChildren()) {
if (child is T t)
yield return t; // Yield is because of IEnumerable.

foreach (T descendant in child.GetDescendantsWhichAreA<T>())
yield return descendant;
}
}
}

public partial class World : Node3D {
public override void _Ready() {
foreach (Character c in this.GetDescendantsWhichAreA<Character>())
c.Kill(); // Pretend that we have a character class, with a kill method.
}
}
# World.gd
extends Node3D

# Alternative import function way. You have to do this on every script you want to use it in.
# Untyped, you can pass any number of incorrect parameters in,
# or use the output incorrectly, and parser wont warn you.
# we use .bind to get something similar to a method extenstion.
var get_descendants_which_are_a := NodeExtensions.get_descendants_which_are_a.bind(self);

func _ready() -> void:
# Via class name
for c in NodeExtensions.get_descendants_which_are_a(self, Character):
var character: Character = c as Character
character.kill() # Type casting for safety. Matters when you have warnings on.

# Via untyped imported function.
for c in get_descendants_which_are_a.call(Character):
var character: Character = c as Character
character.kill() # Type casting for safety. Matters when you have warnings on.

# NodeExtensions.gd
# Required to access from World.gd, without preloading it as a constant.
class_name NodeExtensions
static func get_descendants_which_are_a(node: Node, clazz) -> Array[Node]:
var nodes: Array[Node] = []
for child in node.get_children():
if (is_instance_of(child, clazz)):
nodes.append(child)

# Recursive
for decendant in child.get_children():
nodes += get_descendants_which_are_a(child, clazz)

return nodes

Namespace Negligence and Syntax Limitations

No namespaces. No typed for-loops. You’re also missing out on convenient C# syntax, like type-checking within an if statement that also handles casting. It's not just about missing out on syntactical sugar; these limitations slow down development and make your code less robust and readable.

// ---- C# Type check and cast example ----------
var someNode = GetChild(0);

if (someNode is Player player)
player.PerformAction();

# ----- GDScript equivalent ---------------------
var some_node: Node = get_child(0)
if (some_node is Player):
var player: Player = some_node as Player
player.perform_action()

Boilerplate Myths in C#

People who label C# as boilerplate-heavy are often just looking for an excuse to avoid learning something new.

In reality, its syntax is flexible. You can drop the braces for single-line loops and if-statements. Plus, you don’t have to explicitly declare variables as private.

// C#

// Partial keyword required for godot code generation.
public partial class SomeClass {
bool somePrivateBool = true;
List<string> names = new();

public void PrintNames() {
// No braces required.
for (string name in names)
if (somePrivateBool)
SomePrivatePrintMethod(names);
}

// Using expression syntax.
void SomePrivatePrintMethod(string message) => GD.Print(message);
}

Async Await and The Absence of Tasks

Using await in GDScript is a disappointment. There’s no native support or classes for Taskor TaskCompletionSource, forcing you to hack together your own untyped, makeshift solutions.

Want to await multiple asynchronous functions for whatever reason? Too bad, GDScript doesn't make it easy for you.

IDE Problems: Navigation.

In good C# IDEs like Rider, Ctrl+Click code navigation is seamless. However, with GDScript in VS Code or the Built-In editor, it’s a different story. Here, it’s unreliable, often forcing you to resort to manual searches (Ctrl+Shift+F anyone?) and wasting a ton of valuable time.

IDE Problems: The VS Code Debacle

If you’re thinking of using VS Code with the official Godot plugin, think again, especially for decently-large projects. It’s so unstable that it crashes the entire Godot Editor every time you save a GDScript file. I even had to write a Python script to automate restarting the editor because of these constant crashes. And don’t forget, you have to save to get accurate autocomplete and syntax checks. So, you’re essentially forced to save, crash, and restart. It’s a vicious cycle: save, crash, restart, lose unsaved in-editor work.

IDE Problems: The Built-In Text Editor

Don’t expect the built-in text editor to be your saviour. It’s laggy, stutters, and frequently freezes. While you might argue that it has plugin support, let’s be real — no one is developing text-editor plugins for it. So, you’re coding in what essentially feels like a glitchy notepad.

UGC/Mods? Just Don’t allow GDScript?

Thinking about GDScript for mods or UGC? You can’t, its lack of sandboxing makes it a security risk, opening the door for bad actors to execute shell code and install malware on another player’s system.

Instead, this forces you to rely on and integrate with third-party languages like Luau for anything safe.

And just to clarify, this isn’t a C# vs GDScript argument. Even if your core game is in any other language, if you were hoping to allow modding through GDScript, it’s just not a viable option.

Scalability: The Refactor Hell

Imagine you’ve got hundreds of scripts, and you want to refactor a core functionality, like a commonly used variable, method, or class. In GDScript, without strong typing or advanced IDE features, you’ll find yourself manually hunting down every reference, risking a cascade of errors with every change.

In contrast, C# provides powerful refactoring tools integrated into IDEs like Rider or Visual Studio. With just a shortcut, you can rename variables, methods, or classes across all your files, without breaking a sweat.

Scalability: Lack of Interfaces and Duck Typing

GDScript skips on interfaces, pushing you towards duck typing with has_method based on string names. It’s a rigid system, especially for large projects. Misspell a method name, and you’re in for runtime errors. It adds unnecessary complexity and risk, making GDScript less than ideal for scalable, robust applications. Good luck refactoring it as well.

Cross-Language Scripting: Why not?

It’s a workaround, sure, but type safety goes out the window.

It’s manageable with primitive types but becomes a nightmare when you deal with custom objects. At that point, it’s just not worth the hassle.

We are done here.

You’ve read the rest, now follow the best. You’ll get more no-BS deep-dives here on Medium. Don’t miss out.

--

--

Chad Johnson

Tearing down tech myths and industry BS. Expert in Programming and Game Development. No fluff, just facts.