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<T>.
**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#
- Every node script is a
partialclass extending a Godot type (the source generator - Override lifecycle methods in PascalCase with
doubledelta:_Ready(), - Expose tunables with
[Export]; they show in the Inspector like GDScript@export. - Declare signals as
[Signal]delegates namedXxxEventHandler; emit with - Get nodes with
GetNode<T>("Path")(or%Unique), and call into GDScript with
script generates a .csproj/.sln. Build with the editor or dotnet build.
relies on partial). The file/class name should match the node script.
_Process(double delta), _PhysicsProcess(double delta).
EmitSignal(SignalName.Xxx, ...) and subscribe with the generated C# event.
Call/Get/Set when needed.
Patterns
1. A node script: lifecycle, [Export], GetNode<T>
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. Withoutpartial, the Godot source generator can't extend the - Wrong method case/signature. C# overrides are
_Ready,_Process(double), [Signal]delegate naming. It must end withEventHandler; the engine exposes theGD.PrintvsConsole.WriteLine. UseGD.Print/GD.PrintErrto reach the Godot- Value-type structs.
Vector2,Color,Transform2Dare structs — mutate a local - Needs the .NET build + SDK. The non-.NET editor can't run C#; mismatched/missing
QueueFree()vsFree()— same rules as GDScript; preferQueueFree(). Disposed- Export to some platforms differs for .NET (e.g. extra steps for web/mobile); check
class and [Export]/[Signal] break with confusing build errors.
_PhysicsProcess(double) — PascalCase and double delta (GDScript uses snake_case and float). A mismatched name just won't be called.
signal as the name without that suffix and generates SignalName.X and a C# event.
output panel; Console output may not appear.
copy (var v = Velocity; v.X = ...; Velocity = v;); editing Velocity.X directly won't compile/persist.
.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.
objects throw ObjectDisposedException if used after freeing.
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]+Resourcepattern.unity-csharp-scripting— C# in Unity, for developers coming from there.