Quick answer: Use the Godot 4 source-generated signal API. Declare a [Signal] delegate and emit by raising the generated event. Connect with += on the typed event. Stop using string-based EmitSignal where possible.

EmitSignal returns. Listener never fires. No error. The string name was wrong by one letter, or the signal was renamed and the call site didn’t move with it. The source-gen API removes the entire class of bug.

The Symptom

Game logic depends on a signal handler running. EmitSignal is called from the publisher; the subscriber’s handler never executes. No exception, no output. Logs may show “signal ‘X’ emitted with no connections.”

The Fix: Source-Generated Signals

Declare the signal.

using Godot;

public partial class Enemy : Node3D
{
    [Signal]
    public delegate void DiedEventHandler(int xpDropped);

    private int _hp = 100;

    public void TakeDamage(int amount)
    {
        _hp -= amount;
        if (_hp <= 0)
            EmitSignalDied(25);   // generated method, type-safe
    }
}

The [Signal] attribute on a delegate produces a generated EmitSignalDied method and a typed Died event. Both are name- and parameter-checked at compile time.

Connect from elsewhere.

public partial class QuestTracker : Node
{
    [Export] public Enemy boss;

    public override void _Ready()
    {
        boss.Died += OnBossDied;
    }

    private void OnBossDied(int xp)
    {
        Player.AddXp(xp);
    }
}

Subscribed via standard C# event syntax. If you rename Died, both publisher and subscriber sites fail to compile until updated. No silent breakage.

If You Must Use Strings

EmitSignal(SignalName.Died, 25) uses the generated SignalName class which is also compile-time-checked — better than the raw string “Died” but more verbose than the EmitSignalDied method.

EmitSignal(SignalName.Died, 25);   // safer than "Died"
EmitSignalDied(25);                // safest
EmitSignal("Died", 25);            // string — avoid

Disconnecting

Disconnect with -= in _ExitTree to avoid leaking handlers across scene changes:

public override void _ExitTree()
{
    if (boss != null && IsInstanceValid(boss))
        boss.Died -= OnBossDied;
}

Verifying

Hit Play. Trigger the signal’s emit path. The handler should run; place a breakpoint or print to confirm. If you build with the strings API and the handler doesn’t run, switch to the source-gen API and the fix typically reveals itself as a typo.

“[Signal] delegate. += to subscribe. EmitSignalX. Compiler catches the typos.”

Related Issues

For Godot C# async/await, see async/await. For C# export trim issues, see trim removed types.

Source-gen signals. Compiler is the listener.