Quick answer: Check listener count with GetSignalConnectionList(signalName).Count in debug builds. Or replace with a typed C# event whose null-check enforces a subscriber.
A boss death signal was supposed to trigger a level-end screen. Boss dies; no screen appears. EmitSignal ran without error. The Connect call lived in a different scene file that wasn’t loaded. Signal fired into the void.
Silent Emit by Design
Godot signals are loosely coupled. EmitSignal broadcasts to any subscriber; zero subscribers is valid. This avoids hard dependencies (a publisher doesn’t need to know if anyone’s listening) but hides “I forgot to wire it up” mistakes.
Debug-Mode Listener Check
public void EmitWithCheck(string signalName, params Variant[] args)
{
#if DEBUG
int count = GetSignalConnectionList(signalName).Count;
if (count == 0) {
GD.PrintErr($"Signal {signalName} emitted with no listeners");
}
#endif
EmitSignal(signalName, args);
}
In debug builds, prints a warning when emitting with no listeners. Catches missing Connect calls during dev; ships zero-overhead in release.
Type-Safe C# Events
public partial class Boss : Node
{
[Signal] public delegate void DiedEventHandler();
public void Die() {
// Cross-language; loose
EmitSignal(SignalName.Died);
}
}
Godot 4 generates an event from the [Signal] delegate. Cross-language compatible but still silent on no listeners. For pure-C# scenarios, define a regular C# event:
public event System.Action OnDied;
public void Die() {
if (OnDied == null) {
GD.PrintErr("OnDied has no subscribers!");
}
OnDied?.Invoke();
}
Null-check before invoke gives you a place to react to missing subscribers (log, throw, default behavior).
Default Listeners
For systems where listeners are optional but you want a default:
boss.OnDied += () => GD.Print("Boss died (no other listeners)"); // fallback
// Other systems can also subscribe
Ensures the signal is never silent in shipping; debug log says “default fired” rather than nothing.
Verifying
Re-trigger the boss death. In debug, see the log warning if no listeners. After connecting the actual listener, no warning and the level-end screen appears.
“Silent signals are convenient until they hide bugs. Listener-count check or default subscriber turns silence into observable behavior.”
Critical game-flow signals should always have an asserted listener — gameplay milestones aren’t optional emissions.