--- name: csharp-signals description: Use when implementing signals in C# — [Signal] delegates, EmitSignal patterns, async signal awaiting, and event-driven architecture --- # Signals in C# (Godot 4.x) This skill is **C# only**. For general C# conventions and project setup, see the **csharp-godot** skill. Godot signals in C# require a different mental model from GDScript: delegates declared with `[Signal]`, strongly-typed `+=`/`-=` connections, and mandatory disconnection in `_ExitTree()`. All examples target Godot 4.x with no deprecated APIs. > **Related skills:** **csharp-godot** for C# conventions and project setup, **event-bus** for global signal hub architecture, **component-system** for signal-based component communication. --- ## 1. Signal Declaration Signals are declared as `public delegate void` with the `[Signal]` attribute inside a `partial class` that extends a Godot type. The delegate name **must** end with `EventHandler` — Godot strips that suffix to produce the signal name exposed to the engine. ```csharp using Godot; public partial class Player : CharacterBody2D { // Signal name in engine: "HealthChanged" [Signal] public delegate void HealthChangedEventHandler(int current, int maximum); // Signal name in engine: "Died" [Signal] public delegate void DiedEventHandler(); // Signal name in engine: "ItemCollected" [Signal] public delegate void ItemCollectedEventHandler(string itemName); } ``` **Naming rules:** | Delegate name | Engine signal name | |---------------------------------|---------------------| | `HealthChangedEventHandler` | `HealthChanged` | | `DiedEventHandler` | `Died` | | `ItemCollectedEventHandler` | `ItemCollected` | | `PlayerSpawnedEventHandler` | `PlayerSpawned` | Omitting the `EventHandler` suffix compiles without error but registers no Godot signal — the signal will not appear in the editor and `EmitSignal` will throw at runtime. **Parameter type constraints:** Signal parameters must be Godot-marshallable types: `int`, `float`, `bool`, `string`, `Vector2`, `Vector3`, `Color`, `GodotObject` subclasses, `GodotDictionary`, `GodotArray`. Plain C# classes, structs, and generics are not allowed as parameters. --- ## 2. Emitting Signals Use `EmitSignal(SignalName.SignalName, args...)`. The `SignalName` nested class is auto-generated by the Godot source generators at build time — one static string constant per declared signal. ```csharp using Godot; public partial class Player : CharacterBody2D { [Signal] public delegate void HealthChangedEventHandler(int current, int maximum); [Signal] public delegate void DiedEventHandler(); [Signal] public delegate void ItemCollectedEventHandler(string itemName); [Export] public int MaxHealth { get; set; } = 100; private int _currentHealth; public override void _Ready() { _currentHealth = MaxHealth; } public void TakeDamage(int amount) { _currentHealth = Mathf.Clamp(_currentHealth - amount, 0, MaxHealth); // Type-safe emission — SignalName.HealthChanged is a generated constant. EmitSignal(SignalName.HealthChanged, _currentHealth, MaxHealth); if (_currentHealth == 0) EmitSignal(SignalName.Died); } public void CollectItem(string itemName) { EmitSignal(SignalName.ItemCollected, itemName); } } ``` `EmitSignal` validates argument count and types at runtime in debug builds. Passing the wrong number of arguments raises an error immediately, making bugs easy to locate. --- ## 3. Connecting Signals ### The `+=` operator (preferred) ```csharp using Godot; public partial class HudLayer : CanvasLayer { private Player _player; public override void _Ready() { _player = GetNode("../Player"); // Connect with += — mirrors C# event syntax. _player.HealthChanged += OnHealthChanged; _player.Died += OnDied; _player.ItemCollected += OnItemCollected; } private void OnHealthChanged(int current, int maximum) { GetNode("HealthBar").Value = (double)current / maximum * 100.0; GetNode