本文へ移動
cccskills
無料GitHub で公開

csharp-signals

Use when implementing signals in C# — [Signal] delegates, EmitSignal patterns, async signal awaiting, and event-driven architecture. Triggers on declaring, emitting, connecting, or awaiting a signal from a C# script, and on a signal crossing between C# and GDScript in either direction.

インストール方法を見る

含まれるファイル(5)

  • SKILL.md10.0 KB
  • references/awaiting-signals.md2.8 KB
  • references/connecting-gdscript-signals.md1.7 KB
  • references/custom-signal-patterns.md4.9 KB
  • references/disconnecting.md3.1 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

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.

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 nameEngine signal name
HealthChangedEventHandlerHealthChanged
DiedEventHandlerDied
ItemCollectedEventHandlerItemCollected
PlayerSpawnedEventHandlerPlayerSpawned

Omitting the EventHandler suffix is a compile error: the source generator reports GD0201, "The name of the delegate must end with 'EventHandler'".

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.

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)

using Godot;

public partial class HudLayer : CanvasLayer
{
    private Player _player;

    public override void _Ready()
    {
        _player = GetNode<Player>("../Player");

        // Connect with += — mirrors C# event syntax.
        _player.HealthChanged += OnHealthChanged;
        _player.Died          += OnDied;
        _player.ItemCollected += OnItemCollected;
    }

    private void OnHealthChanged(int current, int maximum)
    {
        GetNode<ProgressBar>("HealthBar").Value = (double)current / maximum * 100.0;
        GetNode<Label>("HealthLabel").Text = $"{current} / {maximum}";
    }

    private void OnDied()
    {
        GetNode<Control>("DeathScreen").Show();
    }

    private void OnItemCollected(string itemName)
    {
        GetNode<Label>("PickupLabel").Text = $"Picked up: {itemName}";
    }
}

Lambda connections

Use lambdas for one-off, short-lived responses. Store the lambda in a field if you need to disconnect it later.

// Anonymous lambda — cannot be disconnected by reference later.
_player.Died += () => GetNode<AudioStreamPlayer>("DeathSound").Play();

// Stored lambda — can be disconnected.
private Action<string> _onItemCollected;

public override void _Ready()
{
    _onItemCollected = (itemName) =>
    {
        _collectCount++;
        UpdateCollectDisplay();
    };
    _player.ItemCollected += _onItemCollected;
}

public override void _ExitTree()
{
    _player.ItemCollected -= _onItemCollected;
}

Connecting in _Ready()

Connect inside _Ready() — or _EnterTree() for a node that is removed and re-added, since _Ready() runs only once. The node's references are resolved and the scene tree is available at that point. Connecting in the constructor or field initializers may fail because Godot node infrastructure is not yet initialised.


4. Disconnecting Signals

When a GodotObject is freed, Godot disconnects its connections, for emitters and receivers alike. Two cases are not cleaned up when the receiver is freed while the emitter lives on: a += on a custom [Signal], and a lambda that captures a variable. In both, the next emission reaches a disposed object and throws ObjectDisposedException, so disconnect with -= yourself in _ExitTree(). For a node that can leave and re-enter the tree, connect in _EnterTree() instead of _Ready(), which runs only once.

See references/disconnecting.md for the -= cleanup pattern, the two cases Godot does not clean up, and a SafeDisconnect helper.


5. Awaiting Signals

await ToSignal(node, SignalName.X) pauses until the signal fires. Returns Variant[] of the signal's args. Add a timeout with Task.WhenAny or a CancellationTokenSource to avoid permanent hangs.

See references/awaiting-signals.md for basic await, return-value extraction, and both timeout patterns.


6. Custom Signal Patterns

Three patterns: typed event args (wrap signal payload in a Resource subclass), static event bus (use C# static event as a pure-C# alternative when both peers are C#), generic signal helper (one helper per SignalName.X to reduce repetition).

See references/custom-signal-patterns.md for full code on each pattern.


7. Connecting GDScript Signals from C#

When the signal is declared in a GDScript node and the listener is C#, use node.Connect("signal_name", new Callable(this, MethodName.Handler)). The MethodName generated symbol still works for C# methods.

See references/connecting-gdscript-signals.md for the full pattern with both string-name and MethodName variants.


8. Common Mistakes

MistakeSymptomFix
Forgetting EventHandler suffix on the delegateBuild fails with GD0201: the delegate name must end with 'EventHandler'Rename to XxxEventHandler
Wrong parameter types or count in EmitSignalRuntime error in debug build: "Signal parameter mismatch"Match EmitSignal args exactly to the delegate signature
Connecting to a freed objectObjectDisposedException or silent crash on next signal emissionGuard with IsInstanceValid before connecting; store reference safely
Not disconnecting a custom [Signal] or a capturing lambdaObjectDisposedException on the next emission after the receiver is freed; built-in signals with a method handler are cleaned up automaticallyAdd a matching -= in _ExitTree() (or Dispose()) for those two cases
Passing wrong argument count to EmitSignalDebug-build error: "Expected N arguments, got M"Count signal delegate parameters and match exactly
Using static C# events for cross-language signalsGDScript cannot observe the event; no error, just silent non-deliveryUse Godot [Signal] for any signal GDScript must receive
Double-connecting the same handlerHandler fires twice per emission; subtle logic bugsCheck if already connected, or use ConnectFlags.OneShot; never call += twice for the same method
Awaiting a signal in _Ready() before the tree is readyNullReferenceException or signal never resolves because the source node is not yet in the treeDefer with await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame) first, or move the await to a method called after _Ready()

9. Checklist

  • Every [Signal] delegate name ends with EventHandler
  • EmitSignal uses SignalName.XxxName constants (not raw strings)
  • EmitSignal argument count and types match the delegate signature exactly
  • All += connections are in _Ready() (not in constructors or field initializers)
  • Every += on a custom [Signal], and every capturing lambda, has a matching -=
  • IsInstanceValid guards are in place where the signal source may be freed before the subscriber
  • Lambdas stored in fields if disconnection is needed later
  • async signal awaits are not blocking _Ready() directly; deferred if necessary
  • Cross-language signals (GDScript emitter, C# subscriber) use Connect("snake_case_name", Callable.From<T>(Method))
  • Static C# events used only for pure-C# communication paths
  • RefCounted-derived wrapper classes used for complex signal payloads, not plain C# classes or structs

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use when working with 2D-specific systems — TileMaps, parallax scrolling, 2D lights and shadows, canvas layers, particles 2D, custom drawing, and 2D meshes in Godot 4.3+

日本語の概要は準備中です。原文の説明を表示しています。

jame581/GodotPrompter8152026年10月12日 更新

Use when working with 3D-specific systems — materials, lighting, shadows, environment, global illumination, fog, LOD, occlusion culling, and decals in Godot 4.3+

日本語の概要は準備中です。原文の説明を表示しています。

jame581/GodotPrompter8152026年10月12日 更新

Use when building character abilities — Resource-based abilities with cost/cooldown/cast, buffs/debuffs, stat modifiers, gameplay tags, and HUD binding

日本語の概要は準備中です。原文の説明を表示しています。

jame581/GodotPrompter8152026年10月12日 更新

Use when creating Godot editor plugins — EditorPlugin, @tool scripts, custom inspectors, and dock panels

日本語の概要は準備中です。原文の説明を表示しています。

jame581/GodotPrompter8152026年10月12日 更新

Use when implementing AI movement — NavigationAgent2D/3D, steering behaviors, behavior trees, and patrol patterns

日本語の概要は準備中です。原文の説明を表示しています。

jame581/GodotPrompter8152026年10月12日 更新

Use when implementing animations — AnimationPlayer, AnimationTree, blend trees, state machines, sprite animation, and code-driven animation

日本語の概要は準備中です。原文の説明を表示しています。

jame581/GodotPrompter8152026年10月12日 更新

jame581 のスキルをすべて見る

このスキルの問題を報告する