DEV Community

Cover image for From GDScript to C#: A Practical Guide to Converting Your Godot Scripts
Jimmy McBride
Jimmy McBride Subscriber

Posted on

From GDScript to C#: A Practical Guide to Converting Your Godot Scripts

So you’ve been hacking away in GDScript for a while, and now you’re thinking about switching to C#. Good choice.
Static types are king.

The Godot editor gives you a ton of quality-of-life features out of the box — but so do IDEs like Rider and Visual Studio. When you combine Godot’s workflow with a full-power IDE, you get code completion, refactoring tools, and static analysis that GDScript just can’t match yet.

C# is faster, safer, and (honestly) just feels right if you already love strong typing and compiler-enforced correctness. Let’s walk through exactly how to take your GDScript and translate it into clean, idiomatic C#.


🧩 Basic Syntax Differences

GDScript and C# share a lot conceptually — both are high-level and friendly — but their syntax philosophies couldn’t be more different.

GDScript C#
extends CharacterBody3D public partial class Player : CharacterBody3D
func _ready(): public override void _Ready()
pass // empty body

Notes:

  • C# requires explicit return types (void, int, etc.).
  • Use braces {} instead of indentation.
  • Don’t forget the partial keyword — Godot uses it to inject engine bindings.

⚙️ Export and OnReady Variables

In GDScript, you’d write:

@export var speed := 10
@onready var sprite = $Sprite
Enter fullscreen mode Exit fullscreen mode

In C#, that becomes:

[Export] public int Speed { get; set; } = 10;
private Sprite2D _sprite;

public override void _Ready()
{
    _sprite = GetNode<Sprite2D>("Sprite");
}
Enter fullscreen mode Exit fullscreen mode

You can’t use @onready — you’ll grab your nodes in _Ready().
Think of [Export] as the C# equivalent of @export, just with attributes instead of decorators.


📡 Signals — The C# Way

Here’s a simple GDScript signal:

signal died

func die():
    emit_signal("died")
Enter fullscreen mode Exit fullscreen mode

And here’s its C# twin:

[Signal] public delegate void DiedEventHandler();

public void Die()
{
    EmitSignal(SignalName.Died);
}
Enter fullscreen mode Exit fullscreen mode

Connecting is where C# really shines.
Forget Connect() — use the event-style += syntax instead:

public override void _Ready()
{
    Died += OnDied;
}

private void OnDied()
{
    GD.Print("Player died!");
}
Enter fullscreen mode Exit fullscreen mode

It’s cleaner, type-safe, and plays nicely with your IDE’s autocomplete.
If you’ve used C# events before, you’ll feel right at home.


🌳 Node Access and the Scene Tree

This one’s easy: $NodePath in GDScript becomes GetNode<T>() in C#.

@onready var enemy = $Enemy
Enter fullscreen mode Exit fullscreen mode
private Node2D _enemy;

public override void _Ready()
{
    _enemy = GetNode<Node2D>("Enemy");
}
Enter fullscreen mode Exit fullscreen mode

Want null safety? Use ? and as:

var enemy = GetNode("Enemy") as Node2D;
enemy?.QueueFree();
Enter fullscreen mode Exit fullscreen mode

🌍 Autoloads and Global Classes

In GDScript, your autoload might look like this:

extends Node
var score = 0
Enter fullscreen mode Exit fullscreen mode

In C#:

public partial class Global : Node
{
    public int Score { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Then go to Project → Project Settings → AutoLoad, add Global.cs, and check “Enable”.
Now you can access it from anywhere:

var global = (Global)GetNode("/root/Global");
global.Score += 1;
Enter fullscreen mode Exit fullscreen mode

🔁 Lifecycle Methods

GDScript C#
_ready() _Ready()
_process(delta) _Process(double delta)
_physics_process(delta) _PhysicsProcess(double delta)
_input(event) _Input(InputEvent @event)

Each method name is PascalCase and usually takes a double delta instead of a float.


🎮 Input Handling

if Input.is_action_just_pressed("jump"):
    jump()
Enter fullscreen mode Exit fullscreen mode
if (Input.IsActionJustPressed("jump"))
    Jump();
Enter fullscreen mode Exit fullscreen mode

The method names are PascalCase (IsActionJustPressed) but otherwise identical.


🧱 Collections and Loops

Arrays and dictionaries work similarly, just more explicit in C#.

var enemies = []
for i in range(10):
    enemies.append(i)
Enter fullscreen mode Exit fullscreen mode
var enemies = new Godot.Collections.Array<int>();
for (int i = 0; i < 10; i++)
    enemies.Add(i);
Enter fullscreen mode Exit fullscreen mode

Or go pure C#:

var enemies = new List<int>();
Enter fullscreen mode Exit fullscreen mode

⏳ Async / Await vs Yield

GDScript’s await is awesome, but in C# it maps differently:

await get_tree().create_timer(1.0).timeout
print("done!")
Enter fullscreen mode Exit fullscreen mode
await ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout);
GD.Print("done!");
Enter fullscreen mode Exit fullscreen mode

You can use normal async/await patterns anywhere now — no coroutines or yield() required.


🧠 Common Gotchas

  • Double vs float: Godot 4 uses double for most math types.
  • Recompile: Adding a new C# file? You need to rebuild the project.
  • Export fields: [Export] only works on public properties (not private fields).
  • Partial keyword: If you forget it, the script won’t load.
  • Case sensitivity: _Ready, not _ready.

⚖️ When to Use C# vs. GDScript

  • Use GDScript for fast prototyping, quick editor tools, or when you just want to stay inside Godot’s ecosystem.
  • Use C# for large projects, stronger architecture, and cleaner maintenance long-term.
  • You can mix both. Godot lets C# and GDScript talk to each other easily.

💬 Final Thoughts

Switching from GDScript to C# isn’t just about syntax — it’s about mindset.
C# gives you structure, safety, and serious IDE power without losing what makes Godot fun.

Top comments (0)