Godot C# / .NET (4.x)
Write Godot game code in C#: node subclasses, the engine lifecycle, exports, signals as
events, and GDScript interop. Targets Godot 4.7 (.NET / C#) and .NET 8.
When to use
- Use when scripting a Godot game in C# (
.cs + .csproj), translating GDScript idioms
to C#, exposing [Export] fields, or wiring [Signal] delegates and GetNode.
When not to use: GDScript-specific syntax → godot-gdscript; engine concepts that
are language-neutral (scenes, physics, animation) → the relevant godot-* skill. You need
the Godot .NET build + the .NET SDK installed; the standard build can't run C#.
Core workflow
- Use the Godot .NET editor build and install the matching .NET 8 SDK. Creating the first C#
script generates a
.csproj/.sln. Build with the editor or dotnet build.
- Every node script is a
partial class extending a Godot type (the source generator
relies on partial). The file/class name should match the node script.
- Override lifecycle methods in PascalCase with
double delta: _Ready(),
_Process(double delta), _PhysicsProcess(double delta).
- Expose tunables with
[Export]; they show in the Inspector like GDScript @export.
- Declare signals as
[Signal] delegates named XxxEventHandler; emit with
EmitSignal(SignalName.Xxx, ...) and subscribe with the generated C# event.
- Get nodes with
GetNode<T>("Path") (or %Unique), and call into GDScript with
Call/Get/Set when needed.
Patterns
1. A node script: lifecycle, [Export], GetNode
using Godot;
public partial class Player : CharacterBody2D
{
[Export] public float Speed = 200.0f; // editable in the Inspector
[Export] public float JumpVelocity = -400.0f;
private const float Gravity = 1200.0f;
private AnimatedSprite2D _sprite;
public override void _Ready()
{
_sprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
}
public override void _PhysicsProcess(double delta)
{
Vector2 v = Velocity; // Velocity is a property here
if (!IsOnFloor())
v.Y += Gravity * (float)delta; // delta is double; cast for float math
if (Input.IsActionJustPressed("jump") && IsOnFloor())
v.Y = JumpVelocity;
float dir = Input.GetAxis("move_left", "move_right");
v.X = dir != 0 ? dir * Speed : Mathf.MoveToward(v.X, 0, Speed);
Velocity = v;
MoveAndSlide(); // no args, like GDScript 4.x
}
}
2. Signals as C# events
using Godot;
public partial class Health : Node
{
// Delegate name MUST end with "EventHandler"; generator creates the event + SignalName.
[Signal] public delegate void HealthChangedEventHandler(int current, int max);
private int _hp = 100;
public void TakeDamage(int amount)
{
_hp = Mathf.Max(_hp - amount, 0);
EmitSignal(SignalName.HealthChanged, _hp, 100); // type-safe signal name
}
public override void _Ready()
{
HealthChanged += OnHealthChanged; // subscribe like a normal C# event
}
private void OnHealthChanged(int current, int max) => GD.Print($"HP {current}/{max}");
}
3. Instancing a scene in C#
public partial class Spawner : Node2D
{
// Load once; PackedScene is the C# equivalent of preload's result.
private readonly PackedScene _bullet = GD.Load<PackedScene>("res://bullet.tscn");
public void Shoot(Vector2 at)
{
var b = _bullet.Instantiate<Node2D>(); // typed instantiate
b.GlobalPosition = at;
AddChild(b);
}
}
4. Interop with GDScript nodes
public override void _Ready()
{
Node gd = GetNode("GDScriptNode");
// Call a GDScript method and read/write its properties dynamically.
gd.Call("take_damage", 10);
int score = (int)gd.Get("score");
gd.Set("score", score + 5);
// Connect to a GDScript signal by name:
gd.Connect("died", Callable.From(OnDied));
}
private void OnDied() => GD.Print("entity died");
Pitfalls
- Forgetting
partial. Without partial, the Godot source generator can't extend the
class and [Export]/[Signal] break with confusing build errors.
- Wrong method case/signature. C# overrides are
_Ready, _Process(double),
_PhysicsProcess(double) — PascalCase and double delta (GDScript uses snake_case and
float). A mismatched name just won't be called.
[Signal] delegate naming. It must end with EventHandler; the engine exposes the
signal as the name without that suffix and generates SignalName.X and a C# event.
GD.Print vs Console.WriteLine. Use GD.Print/GD.PrintErr to reach the Godot
output panel; Console output may not appear.
- Value-type structs.
Vector2, Color, Transform2D are structs — mutate a local
copy (var v = Velocity; v.X = ...; Velocity = v;); editing Velocity.X directly won't
compile/persist.
- Needs the .NET build + SDK. The non-.NET editor can't run C#; mismatched/missing
.NET SDK causes build failures. Godot 4.7 targets .NET 8; check current platform
export notes because Android and other AOT targets can require newer SDK tooling.
QueueFree() vs Free() — same rules as GDScript; prefer QueueFree(). Disposed
objects throw ObjectDisposedException if used after freeing.
- Export to some platforms differs for .NET (e.g. extra steps for web/mobile); check
the .NET export notes for your target.
References
- For export attribute variants (
[ExportGroup], ranges, typed arrays), async with
await ToSignal(...), Godot.Collections vs System collections, custom Resources in
C#, and project/build setup, read references/csharp-setup-and-interop.md.
Related skills
godot-gdscript — the GDScript equivalents of these patterns.
godot-signals-groups — signal/event architecture (language-neutral).
godot-resources — data resources; the C# [Export] + Resource pattern.
unity-csharp-scripting — C# in Unity, for developers coming from there.
1---2name: godot-csharp3description: Use C#/.NET in Godot 4.7: partial classes extending nodes, the PascalCase lifecycle (_Ready/_Process/_PhysicsProcess), [Export] fields, [Signal] delegates as C# events, type-safe node lookup, and calling between C# and GDScript. Use when writing Godot game code in C# (.cs files, .csproj), needing the Godot .NET build, converting GDScript patterns to C#, or wiring Godot signals as C# events.4---5
6# Godot C# / .NET (4.x)
7
8Write Godot game code in C#: node subclasses, the engine lifecycle, exports, signals as
9events, and GDScript interop. Targets **Godot 4.7 (.NET / C#)** and **.NET 8**.
10
11## When to use
12
13- Use when scripting a Godot game in C# (`.cs` + `.csproj`), translating GDScript idioms
14 to C#, exposing `[Export]` fields, or wiring `[Signal]` delegates and GetNode<T>.
15
16**When *not* to use:** GDScript-specific syntax → `godot-gdscript`; engine concepts that
17are language-neutral (scenes, physics, animation) → the relevant `godot-*` skill. You need
18the **Godot .NET build** + the .NET SDK installed; the standard build can't run C#.
19
20## Core workflow
21
221. **Use the Godot .NET editor build** and install the matching .NET 8 SDK. Creating the first C#
23 script generates a `.csproj`/`.sln`. Build with the editor or `dotnet build`.
242. **Every node script is a `partial` class** extending a Godot type (the source generator
25 relies on `partial`). The file/class name should match the node script.
263. **Override lifecycle methods in PascalCase** with `double` delta: `_Ready()`,
27 `_Process(double delta)`, `_PhysicsProcess(double delta)`.
284. **Expose tunables with `[Export]`**; they show in the Inspector like GDScript `@export`.
295. **Declare signals as `[Signal]` delegates** named `XxxEventHandler`; emit with
30 `EmitSignal(SignalName.Xxx, ...)` and subscribe with the generated C# `event`.
316. **Get nodes with `GetNode<T>("Path")`** (or `%Unique`), and call into GDScript with
32 `Call`/`Get`/`Set` when needed.
33
34## Patterns
35
36### 1. A node script: lifecycle, [Export], GetNode<T>
37
38```csharp
39using Godot;
40
41public partial class Player : CharacterBody2D
42{
43 [Export] public float Speed = 200.0f; // editable in the Inspector
44 [Export] public float JumpVelocity = -400.0f;
45
46 private const float Gravity = 1200.0f;
47 private AnimatedSprite2D _sprite;
48
49 public override void _Ready()
50 {
51 _sprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
52 }
53
54 public override void _PhysicsProcess(double delta)
55 {
56 Vector2 v = Velocity; // Velocity is a property here
57 if (!IsOnFloor())
58 v.Y += Gravity * (float)delta; // delta is double; cast for float math
59 if (Input.IsActionJustPressed("jump") && IsOnFloor())
60 v.Y = JumpVelocity;
61
62 float dir = Input.GetAxis("move_left", "move_right");
63 v.X = dir != 0 ? dir * Speed : Mathf.MoveToward(v.X, 0, Speed);
64
65 Velocity = v;
66 MoveAndSlide(); // no args, like GDScript 4.x
67 }
68}
69```
70
71### 2. Signals as C# events
72
73```csharp
74using Godot;
75
76public partial class Health : Node
77{
78 // Delegate name MUST end with "EventHandler"; generator creates the event + SignalName.
79 [Signal] public delegate void HealthChangedEventHandler(int current, int max);
80
81 private int _hp = 100;
82
83 public void TakeDamage(int amount)
84 {
85 _hp = Mathf.Max(_hp - amount, 0);
86 EmitSignal(SignalName.HealthChanged, _hp, 100); // type-safe signal name
87 }
88
89 public override void _Ready()
90 {
91 HealthChanged += OnHealthChanged; // subscribe like a normal C# event
92 }
93
94 private void OnHealthChanged(int current, int max) => GD.Print($"HP {current}/{max}");
95}
96```
97
98### 3. Instancing a scene in C#
99
100```csharp
101public partial class Spawner : Node2D
102{
103 // Load once; PackedScene is the C# equivalent of preload's result.
104 private readonly PackedScene _bullet = GD.Load<PackedScene>("res://bullet.tscn");
105
106 public void Shoot(Vector2 at)
107 {
108 var b = _bullet.Instantiate<Node2D>(); // typed instantiate
109 b.GlobalPosition = at;
110 AddChild(b);
111 }
112}
113```
114
115### 4. Interop with GDScript nodes
116
117```csharp
118public override void _Ready()
119{
120 Node gd = GetNode("GDScriptNode");
121 // Call a GDScript method and read/write its properties dynamically.
122 gd.Call("take_damage", 10);
123 int score = (int)gd.Get("score");
124 gd.Set("score", score + 5);
125 // Connect to a GDScript signal by name:
126 gd.Connect("died", Callable.From(OnDied));
127}
128
129private void OnDied() => GD.Print("entity died");
130```
131
132## Pitfalls
133
134- **Forgetting `partial`.** Without `partial`, the Godot source generator can't extend the
135 class and `[Export]`/`[Signal]` break with confusing build errors.
136- **Wrong method case/signature.** C# overrides are `_Ready`, `_Process(double)`,
137 `_PhysicsProcess(double)` — PascalCase and `double` delta (GDScript uses snake_case and
138 `float`). A mismatched name just won't be called.
139- **`[Signal]` delegate naming.** It must end with `EventHandler`; the engine exposes the
140 signal as the name without that suffix and generates `SignalName.X` and a C# `event`.
141- **`GD.Print` vs `Console.WriteLine`.** Use `GD.Print`/`GD.PrintErr` to reach the Godot
142 output panel; `Console` output may not appear.
143- **Value-type structs.** `Vector2`, `Color`, `Transform2D` are structs — mutate a local
144 copy (`var v = Velocity; v.X = ...; Velocity = v;`); editing `Velocity.X` directly won't
145 compile/persist.
146- **Needs the .NET build + SDK.** The non-.NET editor can't run C#; mismatched/missing
147 .NET SDK causes build failures. Godot 4.7 targets .NET 8; check current platform
148 export notes because Android and other AOT targets can require newer SDK tooling.
149- **`QueueFree()` vs `Free()`** — same rules as GDScript; prefer `QueueFree()`. Disposed
150 objects throw `ObjectDisposedException` if used after freeing.
151- **Export to some platforms differs for .NET** (e.g. extra steps for web/mobile); check
152 the .NET export notes for your target.
153
154## References
155
156- For export attribute variants (`[ExportGroup]`, ranges, typed arrays), async with
157 `await ToSignal(...)`, `Godot.Collections` vs System collections, custom Resources in
158 C#, and project/build setup, read `references/csharp-setup-and-interop.md`.
159
160## Related skills
161
162- `godot-gdscript` — the GDScript equivalents of these patterns.
163- `godot-signals-groups` — signal/event architecture (language-neutral).
164- `godot-resources` — data resources; the C# `[Export]` + `Resource` pattern.
165- `unity-csharp-scripting` — C# in Unity, for developers coming from there.