agentleFS
fetastyfetasty/godot-skills/godot-csharp/SKILL.md

godot-csharp

Skill29 starsChanged 6 months agoMITView on GitHub

Godot 4 C# code quality: .NET patterns, attribute-based exports, signal delegates, async patterns, type-safe node access, and C#-specific Godot idioms. Use when writing or reviewing .cs files in Godot 4 projects for: (1) Enforcing partial class and nullable reference types, (2) Designing [Signal] delegate architecture, (3) Implementing C# design patterns with Godot integration, (4) Managing .csproj and NuGet dependencies, (5) Reviewing C# for Godot-specific anti-patterns.

3 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions3 projects use a version of this.

    Word for word · 0

    None found.

    Edited · 0

    None found.

    Wrote their own version · 3

    1. gamedev-skills/awesome-gamedev-agent-skills★ 6.4ksame name
    2. gosomea/dsh-godot-ai★ 9same name
    3. MindsMaster/godot-claude-code-skills★ 3same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository4 other files it gives its agents.

The file

We split this skill into the rules it gives an agent

11 rules. About 0 are common, 3 are distinctive, and 8 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    ALL node scripts MUST be declared as partial class:

    line 18
  2. Very specific:

    Use ? for nullable references.

    line 34
  3. Very specific:

    Prefer explicit types; var is acceptable when type is obvious from RHS

    line 41
  4. Very specific:

    Always use SignalName.X (not string literals) for type safety

    line 97
  5. Very specific:

    Use StringName for frequently compared strings: new StringName("group_name")

    line 175
  6. Very specific:

    Avoid LINQ in hot paths (_Process, collision callbacks) — allocates garbage

    line 176
  7. Very specific:

    Prefer List<T> over Godot.Collections.Array<T> for C#-internal collections

    line 177
  8. Distinctive:

    Use object pooling for frequently spawned objects

    line 178
  9. Distinctive:

    Keep in C#: complex game systems, data processing, AI, anything unit-tested

    line 183
  10. Distinctive:

    Keep in GDScript: scenes needing fast iteration, level scripts, simple behaviors

    line 184
  11. Very specific:

    Avoid GodotObject.Call() (string-based) — define typed interfaces

    line 186

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Godot C# Specialist

C# code quality, patterns, and performance for Godot 4.6 projects.

The partial class Requirement (Mandatory)

ALL node scripts MUST be declared as partial class:

// YES — partial class, matches node type
public partial class PlayerController : CharacterBody3D { }

// NO — source generator fails silently
public class PlayerController : CharacterBody3D { }

Godot 4's source generator requires partial to inject _GodotClass infrastructure. Missing this causes silent failures that are very hard to debug.

Static Typing and Null Safety

  • Enable nullable reference types: <Nullable>enable</Nullable> in .csproj
  • Use ? for nullable references; never assume a reference is non-null:
private HealthComponent? _healthComponent;   // nullable — may not be assigned yet
private Node3D _cameraRig = null!;            // non-nullable — guaranteed in _Ready()
  • Prefer explicit types; var is acceptable when type is obvious from RHS

Naming Conventions

ElementConventionExample
ClassesPascalCasePlayerController, WeaponData
Public membersPascalCaseMoveSpeed, JumpVelocity
Private fields_camelCase_currentHealth, _isGrounded
MethodsPascalCaseTakeDamage(), GetCurrentHealth()
ConstantsPascalCaseMaxHealth, DefaultMoveSpeed
Signal delegatesPascalCaseEventHandlerHealthChangedEventHandler
Signal callbacksOn prefixOnHealthChanged()
FilesPascalCase.csPlayerController.cs

Export and Inspector Attributes

using Godot;

[Export] public float MoveSpeed { get; set; } = 5.0f;
[Export] public bool IsEnabled { get; set; } = true;

// Enums
public enum DamageType { Physical, Magical, TrueDamage }
[Export] public DamageType DamageType { get; set; }

// Node references — use unique names (%) in .tscn
[Export] public HealthComponent? HealthComponent { get; set; }

// Resources
[Export] public WeaponData? StartingWeapon { get; set; }

Signal Architecture

// Declare delegate outside class OR use delegate keyword
[Signal]
public delegate void HealthChangedEventHandler(float newHealth, float oldHealth);

[Signal]
public delegate void DiedEventHandler(Node source);

// Emit
EmitSignal(SignalName.HealthChanged, _currentHealth, _previousHealth);
EmitSignal(SignalName.Died, killer);

// Connect
HealthChanged += OnHealthChanged;

// In _ExitTree():
HealthChanged -= OnHealthChanged;
  • Signal delegate names MUST end with EventHandler — source generator requires this
  • Always use SignalName.X (not string literals) for type safety
  • Unsubscribe in _ExitTree() to prevent memory leaks

Async Patterns

// Timers — use SceneTree, not Task.Delay()
await ToSignal(GetTree().CreateTimer(0.5f), SceneTreeTimer.SignalName.Timeout);

// Await signal
await ToSignal(animationPlayer, AnimationMixer.SignalName.AnimationFinished);

// Tween
var tween = CreateTween();
tween.TweenProperty(sprite, "modulate:a", 0.0f, 0.3f);
await ToSignal(tween, Tween.SignalName.Finished);

Never use Task.Delay() — it breaks frame synchronization with the engine.

Design Patterns

Composition Over Inheritance

private HealthComponent _healthComponent = null!;
private HitboxComponent _hitboxComponent = null!;

public override void _Ready()
{
    _healthComponent = GetNode<HealthComponent>("%HealthComponent");
    _hitboxComponent = GetNode<HitboxComponent>("%HitboxComponent");
    _healthComponent.Died += OnDied;
    _hitboxComponent.HitReceived += OnHitReceived;
}

Maximum inheritance depth: 3 levels after GodotObject.

Resource Pattern

[GlobalClass]
public partial class WeaponData : Resource
{
    [Export] public float Damage { get; set; } = 10.0f;
    [Export] public float AttackSpeed { get; set; } = 1.0f;
    [Export] public WeaponType WeaponType { get; set; }
}

Autoload Access

// In GameManager.cs
public partial class GameManager : Node
{
    public static GameManager Instance { get; private set; } = null!;

    public override void _Ready()
    {
        Instance = this;
    }
}

// Usage elsewhere
GameManager.Instance.PauseGame();

Performance

  • Disable _Process and _PhysicsProcess when not needed:
    SetProcess(false);
    SetPhysicsProcess(false);
  • _Process(double delta) uses double — cast to float for engine math: (float)delta
  • Cache GetNode<T>() in _Ready() — never call inside _Process
  • Use StringName for frequently compared strings: new StringName("group_name")
  • Avoid LINQ in hot paths (_Process, collision callbacks) — allocates garbage
  • Prefer List<T> over Godot.Collections.Array<T> for C#-internal collections
  • Use object pooling for frequently spawned objects
  • Profile with Godot's profiler AND dotnet counters for GC pressure

GDScript/C# Boundary

  • Keep in C#: complex game systems, data processing, AI, anything unit-tested
  • Keep in GDScript: scenes needing fast iteration, level scripts, simple behaviors
  • At the boundary: prefer signals over direct cross-language method calls
  • Avoid GodotObject.Call() (string-based) — define typed interfaces
  • Threshold for C# → GDExtension: if a method runs >1000 times per frame AND profiling shows bottleneck

Common C# Anti-Patterns

  • Missing partial on node classes — source generator fails silently
  • Using Task.Delay() instead of GetTree().CreateTimer() — breaks frame sync
  • Calling GetNode() without generics — drops type safety
  • Forgetting to disconnect signals in _ExitTree() — memory leaks
  • Using Godot.Collections.* for internal C# data — unnecessary marshalling overhead
  • Static fields holding node references — breaks scene reload/multiple instances
  • Calling lifecycle methods directly — never call _Ready(), _Process() yourself
  • Capturing this in long-lived lambda signal handlers — prevents GC
  • Signal delegate names without EventHandler suffix — source generator fails

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.