mcpField
C
csharp-signals

csharp-signals

github.com/jame581開発889161 回インストール4 日前に更新
リポジトリ

csharp-signals について

Godot 4.xでC#からシグナルを扱うためのスキルです。型付き宣言、接続、後処理、待機方法と、よくある問題を解説します。

主な機能

  1. 01型付きシグナルの宣言と発行
  2. 02型安全なハンドラー接続
  3. 03安全な切断と後処理
  4. 04タイムアウト付きシグナル待機
  5. 05言語間のシグナル接続パターン

活用シーン

  1. 01プレイヤー状態の変化をHUDに通知
  2. 02ノード解放後の古いハンドラーを防止
  3. 03タイムアウト付きでシグナルを非同期に待機
  4. 04GDScriptのシグナルをC#リスナーに接続

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jame581/godotprompter --skill csharp-signals

インストール後、Claude Code で/csharp-signalsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
awaiting-signals.md
connecting-gdscript-signals.md
custom-signal-patterns.md
disconnecting.md

詳細

C# のシグナル(Godot 4.x)

このスキルはC# 専用です。C# の一般的な規約とプロジェクトのセットアップについては、csharp-godot スキルを参照してください。Godot の C# におけるシグナルは、GDScript とは異なる考え方が必要です。[Signal] で宣言するデリゲート、型安全な +=/-= による接続、そして _ExitTree() での必須の切断が特徴です。すべての例は、非推奨 API を使わず Godot 4.x を対象としています。

関連スキル: C# の規約とプロジェクトのセットアップについては csharp-godot、グローバルなシグナルハブのアーキテクチャについては event-bus、シグナルベースのコンポーネント間通信については component-system を参照してください。


1. シグナルの宣言

シグナルは、Godot 型を継承する partial class 内で、[Signal] 属性を付けた public delegate void として宣言します。デリゲート名は必ず EventHandler で終わる必要があります。Godot はこの接尾辞を取り除き、エンジンに公開するシグナル名を生成します。

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);
}

命名規則:

デリゲート名エンジンのシグナル名
HealthChangedEventHandlerHealthChanged
DiedEventHandlerDied
ItemCollectedEventHandlerItemCollected
PlayerSpawnedEventHandlerPlayerSpawned

EventHandler 接尾辞を省略するとコンパイルエラーになります。ソースジェネレーターは GD0201「デリゲート名は 'EventHandler' で終わる必要があります」と報告します。

パラメーター型の制約: シグナルのパラメーターは Variant と互換性がある必要があります。数値型、bool、string、列挙型、Godot の構造体(Vector2、Color、…)、StringName、NodePath、GodotObject の派生クラス、Godot.Collections.Array/Dictionary(ジェネリックの Array<T>/Dictionary<TKey, TValue> を含む)、および int[] などの Packed 配列相当が該当します。それ以外の C# クラスや構造体を使うと、GD0202 によりビルドに失敗します。


2. シグナルの発行

EmitSignal(SignalName.SignalName, args...) を使います。ネストされた SignalName クラスは、ビルド時に Godot のソースジェネレーターによって自動生成され、宣言されたシグナルごとにキャッシュされた StringName が用意されます。

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 は、引数がデリゲートの定義に合っているか確認しません。引数の数が正しくない場合、カスタム [Signal] に対する += ハンドラーはエラーなしでスキップされ、Connect/GDScript の受信側には Error calling from signal … Method expected N argument(s), but called with M がログに記録されます。型が正しくない場合、C# ハンドラーには変換または既定値(0、null)が渡されます。型付きの GDScript 受信側には Cannot convert argument N from X to Y がログに記録され、呼び出されません。


3. シグナルの接続

+= 演算子(推奨)
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}";
    }
}
ラムダ接続

一度限りの短期間の応答にはラムダを使います。後で切断する必要がある場合は、ラムダをフィールドに保存してください。

// 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;
}
_Ready() での接続

_Ready() 内で接続します。ノードが削除されて再び追加される場合は、_Ready() は一度しか実行されないため、代わりに _EnterTree() で接続してください。この時点ではノードの参照が解決され、シーンツリーを利用できます。コンストラクターやフィールド初期化子で接続すると、Godot のノードインフラストラクチャがまだ初期化されていないため、失敗することがあります。


4. シグナルの切断

GodotObject が解放されると、Godot は発行側と受信側のどちらについても、その接続を切断します。ただし、発行側が生存したまま受信側が解放された場合、クリーンアップされないケースが2 つあります。カスタム [Signal] に対する += と、変数をキャプチャするラムダです。いずれの場合も、次にシグナルが発行されると解放済みオブジェクトが呼び出され、ObjectDisposedException がスローされるため、_ExitTree() で自分で -= を使って切断してください。ツリーを出入りするノードでは、一度しか実行されない _Ready() ではなく、_EnterTree() で接続してください。

-= によるクリーンアップのパターン、Godot がクリーンアップしない 2 つのケース、SafeDisconnect ヘルパーについては、references/disconnecting.md を参照してください。


5. シグナルを待機する

await ToSignal(node, SignalName.X) は、シグナルが発火するまで一時停止します。シグナルの引数を Variant[] で返します。永久に停止しないよう、Task.WhenAny または CancellationTokenSource でタイムアウトを追加してください。

基本的な await、戻り値の抽出、2 つのタイムアウトパターンについては、references/awaiting-signals.md を参照してください。


6. カスタムシグナルのパターン

パターンは 3 つあります。型付きイベント引数(シグナルのペイロードを Resource のサブクラスでラップする)、静的イベントバス(両方のピアが C# の場合に、C# の static event を純粋な C# の代替手段として使用する)、ジェネリックシグナルヘルパー(繰り返しを減らすために、SignalName.X ごとに 1 つのヘルパーを用意する)です。

各パターンの完全なコードについては、references/custom-signal-patterns.md を参照してください。


7. C# から GDScript のシグナルを接続する

シグナルが GDScript のノードで宣言され、リスナーが C# の場合は、node.Connect("signal_name", new Callable(this, MethodName.Handler)) を使用します。生成された MethodName シンボルは、C# のメソッドでも引き続き機能します。

文字列名と MethodName の両方のバリエーションを使った完全なパターンについては、references/connecting-gdscript-signals.md を参照してください。


8. よくある間違い

間違い症状修正方法
デリゲート名に EventHandler サフィックスを付け忘れるGD0201 でビルドに失敗します。デリゲート名は 'EventHandler' で終わる必要がありますXxxEventHandler に名前を変更します
EmitSignal のパラメーター型が間違っているC# ハンドラーではエラーにならず、値が変換またはデフォルト値(0、null)になります。型付き GDScript 受信側では Cannot convert argument N from X to Y が表示され、ハンドラーは呼び出されませんEmitSignal の引数をデリゲートのシグネチャと正確に一致させます
解放済みのオブジェクトに接続する次のシグナル発火時に ObjectDisposedException が発生するか、何も表示されずにクラッシュします接続前に IsInstanceValid で確認し、参照を安全に保持します
カスタム [Signal] またはキャプチャするラムダ式の接続を解除しない受信側が解放された後、次の発火時に ObjectDisposedException が発生します。メソッドハンドラーを使った組み込みシグナルは自動的にクリーンアップされますこの 2 つのケースでは、_ExitTree()(または Dispose())に対応する -= を追加します
EmitSignal に誤った数の引数を渡す+= ハンドラーは何も表示されずに呼び出されません。Connect した受信側では「Method expected N argument(s), but called with M」と記録されますシグナルデリゲートのパラメーター数を数え、正確に一致させます
言語をまたぐシグナルに C# の静的イベントを使用するGDScript からイベントを認識できません。エラーはなく、何も通知されませんGDScript が受信する必要のあるシグナルには Godot [Signal] を使用します
同じハンドラーを二重に接続するカスタム [Signal] では発火ごとにハンドラーが 2 回呼び出されます。組み込みシグナルでは、2 回目の += で「already connected」と記録されますすでに接続済みか確認するか、ConnectFlags.OneShot を使用します。同じメソッドに対して += を 2 回呼び出さないでください
ツリーの準備ができる前に _Ready() でシグナルを待機するNullReferenceException が発生するか、発生元のノードがまだツリー内にないためシグナルが解決されません先に await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame) で遅延するか、_Ready() の後に呼び出すメソッドへ await を移します

9. チェックリスト

  • すべての [Signal] デリゲート名が EventHandler で終わっている
  • EmitSignal では、生の文字列ではなく SignalName.XxxName 定数を使用している
  • EmitSignal の引数の数と型が、デリゲートのシグネチャと完全に一致している
  • すべての += 接続が _Ready() 内にある(コンストラクターやフィールド初期化子内ではない)
  • カスタム [Signal] へのすべての += と、キャプチャするすべてのラムダ式に、対応する -= がある
  • シグナルの発行元がサブスクライバーより先に解放される可能性がある場合、IsInstanceValid によるガードが設けられている
  • 後で切断する必要がある場合、ラムダ式をフィールドに格納している
  • シグナルの async 待機が _Ready() を直接ブロックしていない。必要に応じて遅延実行している
  • 言語をまたぐシグナル(GDScript の発行元、C# のサブスクライバー)では、Connect("snake_case_name", Callable.From<T>(Method)) を使用している
  • 静的な C# イベントは、純粋な C# の通信経路でのみ使用している
  • 複雑なシグナルのペイロードには、単純な C# クラスや構造体ではなく、RefCounted 派生のラッパークラスを使用している

コメント