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
partialkeyword — Godot uses it to inject engine bindings.
⚙️ Export and OnReady Variables
In GDScript, you’d write:
@export var speed := 10
@onready var sprite = $Sprite
In C#, that becomes:
[Export] public int Speed { get; set; } = 10;
private Sprite2D _sprite;
public override void _Ready()
{
_sprite = GetNode<Sprite2D>("Sprite");
}
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")
And here’s its C# twin:
[Signal] public delegate void DiedEventHandler();
public void Die()
{
EmitSignal(SignalName.Died);
}
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!");
}
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
private Node2D _enemy;
public override void _Ready()
{
_enemy = GetNode<Node2D>("Enemy");
}
Want null safety? Use ? and as:
var enemy = GetNode("Enemy") as Node2D;
enemy?.QueueFree();
🌍 Autoloads and Global Classes
In GDScript, your autoload might look like this:
extends Node
var score = 0
In C#:
public partial class Global : Node
{
public int Score { get; set; }
}
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;
🔁 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()
if (Input.IsActionJustPressed("jump"))
Jump();
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)
var enemies = new Godot.Collections.Array<int>();
for (int i = 0; i < 10; i++)
enemies.Add(i);
Or go pure C#:
var enemies = new List<int>();
⏳ 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!")
await ToSignal(GetTree().CreateTimer(1.0), SceneTreeTimer.SignalName.Timeout);
GD.Print("done!");
You can use normal async/await patterns anywhere now — no coroutines or yield() required.
🧠 Common Gotchas
-
Double vs float: Godot 4 uses
doublefor 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)