Skip to content
Development
Skill

/godot-csharp

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,

From plugin
awesome-gamedev-agent-skills
6.3k77 skills

Claude Code

$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill godot-csharp --agent claude-code

SKILL.md

godot-csharp.SKILL.md
name: godot-csharp
description: >
  C# and .NET in Godot 4.7: partial node classes, the PascalCase lifecycle, [Export] and [Signal],
  signals as C# events, the .csproj and calling GDScript. Use for .cs scripts in Godot, porting
  GDScript to C#, a failed build, or a failed web or Android export. Not for GDScript
  (`godot-gdscript`), Unity C# (`unity-csharp-scripting`) or export presets (`godot-export`).

Godot C# / .NET (4.x)

Write Godot game code in C# that the source generators accept, build it from the command line, and check it runs. Targets **Godot 4.7 .NET** with the **.NET 8** SDK (9 for Android). Read the `.csproj` first: its `Sdk="Godot.NET.Sdk/x.y.z"` is the engine version.

Workflow

1. **Confirm the toolchain.** The project needs the Godot **.NET** editor build (the standard build cannot load C#) and a .NET SDK: `dotnet --list-sdks` should show 8.0 or later, and 9.0 or later if the project exports to Android. 2. **Every script is a `public partial class`** whose name matches its `.cs` file name and that extends a Godot type. The generators that wire `[Export]`, `[Signal]` and method bindings only work on `partial` classes. 3. **Override lifecycle methods in PascalCase** with `double` delta: `_Ready()`, `_Process(double delta)`, `_PhysicsProcess(double delta)`. 4. **Signals:** a `[Signal]` delegate named `XxxEventHandler` becomes the `Xxx` signal, an `Xxx` C# event and `SignalName.Xxx`. Emit with `EmitSignal(SignalName.Xxx, ...)`. 5. **Verify before you finish:**

  • Build: `dotnet build <project>.csproj` from the project folder must exit 0 with no

errors. Godot's runtime does not compile C#, so a headless run uses whatever was built last. (`godot --headless --build-solutions --quit` also builds, through the editor.)

  • Smoke run with the .NET editor binary: `godot --headless --path <project> --quit-after 300 > run.log 2>&1`,

then `grep -nE "SCRIPT ERROR|^ERROR:" run.log`. An unhandled exception in a callback is logged as an `ERROR:` with its stack trace and the game keeps running, so the exit code stays 0.

  • If `godot-nodes-scenes` is installed, `python3 ../godot-nodes-scenes/scripts/check_godot_project.py <project>`

catches broken script and scene paths in `.tscn` files.

  • For behaviour tests in CI, follow `godot-gdscript-headless-testing`; build before the

test step.

Patterns

1. A node script: lifecycle, [Export], GetNode<T>

using Godot;

public partial class Player : CharacterBody2D
{
    [Export] public float Speed { get; set; } = 200.0f;
    [Export] public float JumpVelocity { get; set; } = -400.0f;

    private AnimatedSprite2D _sprite;

    public override void _Ready()
    {
        _sprite = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
    }

    public override void _PhysicsProcess(double delta)
    {
        Vector2 v = Velocity;                       // struct copy: edit, then assign back
        if (!IsOnFloor())
            v += GetGravity() * (float)delta;
        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();
    }
}

2. Signals as C# events

using Godot;

public partial class Health : Node
{
    [Signal] public delegate void HealthChangedEventHandler(int current, int max);

    private int _hp = 100;

    public override void _Ready() => HealthChanged += OnHealthChanged;

    public override void _ExitTree() => HealthChanged -= OnHealthChanged;

    public void TakeDamage(int amount)
    {
        _hp = Mathf.Max(_hp - amount, 0);
        EmitSignal(SignalName.HealthChanged, _hp, 100);
    }

    private void OnHealthChanged(int current, int max) => GD.Print($"HP {current}/{max}");
}

3. Instancing a scene

public partial class Spawner : Node2D
{
    private static readonly PackedScene Bullet = GD.Load<PackedScene>("res://bullet.tscn");

    public void Shoot(Vector2 at)
    {
        var b = Bullet.Instantiate<Node2D>();
        b.Position = ToLocal(at);   // world -> this parent's space, before _Ready runs
        AddChild(b);
    }
}

4. Calling GDScript from C#

Node gd = GetNode("Enemy");                       // a node with a .gd script
gd.Call("take_damage", 10);                       // snake_case names, checked at runtime
int score = gd.Get("score").AsInt32();
gd.Connect("died", Callable.From(OnDied));

Gotchas

  • **`Cannot find class X for script res://X.cs`** → the class name differs from the file name.
  • **Build errors from generated code, or `[Export]` missing in the Inspector** → the class is

not `partial`, or the project was not rebuilt. The Inspector only shows members from the last successful build.

  • **`CallDeferred("AddChild", node)` does not work** → string-based engine calls (`Call`,

`CallDeferred`, `Get`, `Set`, `Connect`) use the engine's snake_case names. Pass `MethodName.AddChild` (or `"add_child"`) instead.

  • **An override never runs** → wrong case or signature (`_process`, or `float delta`).

C# uses PascalCase and `double`.

  • **`[Signal]` not found** → the delegate name must end in `EventHandler`; the signal is the

name without it.

  • **`ObjectDisposedException` inside a signal handler** → the receiver was freed while the

emitter lived on, and Godot could not disconnect it because the signal is a custom `[Signal]` or the handler is a lambda that captures variables. Unsubscribe with `-=` in `_ExitTree()` (Pattern 2), and check `GodotObject.IsInstanceValid(node)` before using nodes that may be gone.

  • **Editing `Velocity.X` will not compile** → `Vector2`, `Color` and `Transform2D` are

structs. Copy, change, assign back.

  • **A cross-language call does nothing** → names are not translated: GDScript calls a C#

node by its C# names (`node.T

Read more
@gamedev-skills
Ships withawesome-gamedev-agent-skills

<img src="docs/assets/awesome-gamedev-agent-skills-banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.

Stars on GitHub

Momentum

stars in total
6.3k
Star history of gamedev-skills/awesome-gamedev-agent-skills on GitHub, a chart by star-history.com

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills12d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc13d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react2d252k
@facebook@facebookInstall Skill

Questions about godot-csharp.

Which coding agents can use godot-csharp?
godot-csharp installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.