# C# in Godot C#-in-Godot idioms for editing a C# project. Pair with the csharp group: setup scaffolds the solution, build compiles with structured diagnostics. # C# idioms in Godot A reference for working in a **C# Godot project**. Idioms verified against a shipped commercial Godot C# game (decompiled), so they reflect real shipping practice rather than tutorials. > **Scope / fit.** The `csharp` group handles the project plumbing: `csharp info` reports the > dotnet and .NET-editor state, `csharp setup` scaffolds the `.csproj` and `.sln`, and > `csharp build` runs `dotnet build` and returns structured diagnostics. `script create` on a > `.cs` path writes a `public partial class` template, and `script validate --path X.cs` builds > and reports that file's diagnostics. Running C# needs a Godot .NET editor build plus the > dotnet SDK. This file covers the idioms you write inside those scripts. > The live `ClassDB` API is identical whether you call it from C# or GDScript, so > `engine class-info --class Tween` etc. remain your source of truth for current signatures. > For *what to build*, `project-structure.md` and `deckbuilder-patterns.md` are > language-agnostic and apply here too. ## Node classes: `partial`, inherit a Godot type Every script that Godot instantiates is a `partial class` deriving from a Godot node type. **`partial` is mandatory**. Godot's source generator emits the other half (the `MethodName`/`PropertyName`/`SignalName` lookup classes, marshalling, signal glue) into a sibling partial. Omitting it breaks generation. ```csharp using Godot; public partial class CardView : Control // partial + Godot base type { // ... } ``` - File-scoped namespaces (`namespace Game.Cards;`) and one public type per file. - `[GlobalClass]` to register the type for the editor's "create node" / inspector lists. - `[Tool]` **only** for scripts that must run in the editor (editor plugins, `@tool`-style preview behavior), never for normal gameplay nodes. ## Reference child nodes: `GetNode("%Unique")` in `_Ready` Cache children in `_Ready()` as private fields. The dominant idiom in the shipped game is **unique-name access** (`%Name`), with plain relative paths for simple cases: ```csharp private MegaLabel _goldLabel; private TextureRect _icon; public override void _Ready() { _goldLabel = GetNode("%GoldLabel"); // % = scene-unique, survives reparenting _icon = GetNode("Icon"); // relative path for a stable direct child } ``` - Prefer `%Unique` over deep `"A/B/C"` paths, the same rule as GDScript. - `[Export]` a `Node`/`NodePath` field when the wiring should be done in the inspector (the `.tscn` stores it as `node_paths=PackedStringArray(...)`). - Resolve `GetNode` in `_Ready`, never in the constructor (the tree isn't ready yet). - Pass non-node dependencies via an explicit `Initialize(state)` method after instantiation rather than the constructor. ## Signals: `[Signal]` for nodes, `event Action` for models Two distinct mechanisms, used deliberately: ```csharp // Node-to-node: Godot signal (shows in editor, crosses the C#/GDScript boundary) [Signal] public delegate void ReleasedEventHandler(NClickableControl button); // emit: EmitSignal(SignalName.Released, this); // Plain C# data/model class (not a Node): use a C# event public event Action BlockChanged; public event Action Died; ``` - Use `[Signal] delegate …EventHandler` for **Node** classes and anything the editor or GDScript must see/connect. - Use plain C# `event Action<…>` for **non-Node** model/state classes (a `Creature`, `RunManager`): lighter, strongly typed, no marshalling. The shipped game's data layer is almost entirely C# events; its nodes use `[Signal]`. - Connect built-in signals with `Connect(Control.SignalName.MouseEntered, Callable.From(OnMouseEntered))`. `Callable.From` wraps a C# method with no boilerplate. ## Exports ```csharp [Export] private float _duration = 0.5f; [Export] private Godot.Collections.Array _particles; [Export] private Curve _particlesCurve; ``` Inline C# defaults; use Godot collection types (`Godot.Collections.Array`, `Dictionary<,>`) for exported containers so they marshal. Custom `Resource` subclasses export and edit in the inspector like built-ins. ## Lifecycle and async - Grab children and set up in `_Ready`. **Subscribe and unsubscribe** external events in `_EnterTree`/`_ExitTree` (subscribe in enter, unsubscribe in exit; nodes get re-parented and leak otherwise): ```csharp public override void _EnterTree() { RunManager.Instance.ActEntered += OnActEntered; } public override void _ExitTree() { RunManager.Instance.ActEntered -= OnActEntered; } ``` - Async is `Task`-based and integrates with Godot via `ToSignal` and tweens: ```csharp Tween t = CreateTween().SetParallel(); t.TweenProperty(_banner, "modulate:a", 1f, 0.5) .SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Quad); await ToSignal(t, Tween.SignalName.Finished); // await an animation await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); // wait one frame ``` - `await Task.Delay(ms)` for plain delays (thread a `CancellationToken` for cancelable waits). Chain tween steps with `.Chain()`; intervals with `.TweenInterval()`. ## Data: `Resource` for inspector data, plain classes for logic - Serializable/inspector-editable data → a `Resource` subclass with `[Export]` fields. - Transient runtime models / game logic → **plain C# classes** (not `Resource`/`Node`), with C# events for change notification. Keep rules out of the scene tree so they're unit-testable. (This is the model/entity/view split from `deckbuilder-patterns.md`.) ## Interop: calling GDScript from C# Some engine features ship as GDExtension/GDScript-only APIs (FMOD audio is the canonical case). Bridge them with a **GDScript autoload proxy** and call it via `Node.Call`, caching method names as `StringName` to avoid per-call allocation: ```csharp private static readonly StringName _playOneShot = new StringName("play_one_shot"); private Node _audio; public override void _EnterTree() => _audio = GetNode("%AudioProxy"); public void PlaySfx(string path) => _audio.Call(_playOneShot, path); ``` Keep GDScript to these proxies, `[Tool]`/`EditorPlugin` scripts, and quick VFX-iteration tools; keep gameplay/logic in C#. (See the GDScript-usage notes in this skill set.) ## Safe-node extension helpers Build a small set of `static` extension methods on `Node`/`Control` and use them everywhere. The shipped game leans on these heavily: ```csharp node.QueueFreeSafely(); // validity check (+ object-pool return) before freeing parent.AddChildSafely(child); // main-thread check, else CallDeferred(AddChild) control.TryGrabFocus(); // no-op if invalid var hud = node.GetAncestorOfType(); foreach (var c in node.GetChildrenRecursive()) { ... } ``` They centralize the recurring guards (is-valid, on-main-thread, deferred tree mutation) that otherwise get forgotten at individual call sites. ## Top idioms 1. `public partial class X : `. `partial` is required for source generation. 2. Cache children in `_Ready` via `GetNode("%Unique")`; avoid `../../` paths and constructors. 3. `[Signal] delegate …EventHandler` for nodes; plain `event Action<…>` for non-node models. 4. `Connect(SignalName.X, Callable.From(Handler))` avoids lambda boilerplate. 5. Subscribe in `_EnterTree` and unsubscribe in `_ExitTree` to prevent leaks on reparent. 6. `[Export]` with inline defaults; `Godot.Collections.Array`/`Dictionary` for exported containers. 7. `await ToSignal(tween, Tween.SignalName.Finished)` / `ProcessFrame`; `Task.Delay` with a token. 8. `Resource` subclass for inspector data; plain C# class for logic (testable, tree-free). 9. Bridge GDScript-only APIs via an autoload proxy + `Node.Call(StringName)`; cache the `StringName`. 10. Wrap node ops in safe extension helpers (`QueueFreeSafely`, `AddChildSafely`, …). 11. Verify any API against the live engine (`engine class-info`/`engine search`), never from memory.