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
- MindsMaster/godot-claude-code-skillstheir own version, first seen Oct 25, 2026
- gamedev-skills/awesome-gamedev-agent-skillstheir own version, first seen Oct 23, 2026
- gosomea/dsh-godot-aitheir own version, first seen Oct 8, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- gamedev-skills/awesome-gamedev-agent-skills★ 6.4ksame name
- gosomea/dsh-godot-ai★ 9same name
- 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.
- godot-gdextensionSkill
- godot-gdscriptSkill
- godot-shaderSkill
- godotSkill
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
- Very specific: line 18
ALL node scripts MUST be declared as partial class:
- Very specific: line 34
Use ? for nullable references.
- Very specific: line 41
Prefer explicit types; var is acceptable when type is obvious from RHS
- Very specific: line 97
Always use SignalName.X (not string literals) for type safety
- Very specific: line 175
Use StringName for frequently compared strings: new StringName("group_name")
- Very specific: line 176
Avoid LINQ in hot paths (_Process, collision callbacks) — allocates garbage
- Very specific: line 177
Prefer List<T> over Godot.Collections.Array<T> for C#-internal collections
- Distinctive: line 178
Use object pooling for frequently spawned objects
- Distinctive: line 183
Keep in C#: complex game systems, data processing, AI, anything unit-tested
- Distinctive: line 184
Keep in GDScript: scenes needing fast iteration, level scripts, simple behaviors
- Very specific: line 186
Avoid GodotObject.Call() (string-based) — define typed interfaces
≈ “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.
Files
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;
varis acceptable when type is obvious from RHS
Naming Conventions
| Element | Convention | Example |
| Classes | PascalCase | PlayerController, WeaponData |
| Public members | PascalCase | MoveSpeed, JumpVelocity |
| Private fields | _camelCase | _currentHealth, _isGrounded |
| Methods | PascalCase | TakeDamage(), GetCurrentHealth() |
| Constants | PascalCase | MaxHealth, DefaultMoveSpeed |
| Signal delegates | PascalCaseEventHandler | HealthChangedEventHandler |
| Signal callbacks | On prefix | OnHealthChanged() |
| Files | PascalCase.cs | PlayerController.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
_Processand_PhysicsProcesswhen not needed:SetProcess(false); SetPhysicsProcess(false); _Process(double delta)usesdouble— cast tofloatfor engine math:(float)delta- Cache
GetNode<T>()in_Ready()— never call inside_Process - Use
StringNamefor frequently compared strings:new StringName("group_name") - Avoid LINQ in hot paths (
_Process, collision callbacks) — allocates garbage - Prefer
List<T>overGodot.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
partialon node classes — source generator fails silently - Using
Task.Delay()instead ofGetTree().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
thisin long-lived lambda signal handlers — prevents GC - Signal delegate names without
EventHandlersuffix — source generator fails
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
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.

