C# signals

For a detailed explanation of signals in general, see theUsing signals section in the stepby step tutorial.

Signals are implemented using C# events, the idiomatic way to representthe observer pattern in C#. This is therecommended way to use signals in C# and the focus of this page.

In some cases it's necessary to use the olderConnect() andDisconnect() APIs.SeeUsing Connect and Disconnect for more details.

If you encounter aSystem.ObjectDisposedException while handling a signal,you might be missing a signal disconnection. SeeDisconnecting automatically when the receiver is freed for more details.

Signals as C# events

To provide more type-safety, Godot signals are also all available throughevents.You can handle these events, as any other event, with the+= and-= operators.

TimermyTimer=GetNode<Timer>("Timer");myTimer.Timeout+=()=>GD.Print("Timeout!");

In addition, you can always access signal names associated with a node type through its nestedSignalName class. This is useful when, for example, you want to await on a signal (seeawait keyword).

awaitToSignal(GetTree(),SceneTree.SignalName.ProcessFrame);

Custom signals as C# events

To declare a custom event in your C# script, use the[Signal] attribute on a public delegate type.Note that the name of this delegate needs to end withEventHandler.

[Signal]publicdelegatevoidMySignalEventHandler();[Signal]publicdelegatevoidMySignalWithArgumentEventHandler(stringmyString);

Once this is done, Godot will create the appropriate events automatically behind the scenes. Youcan then use said events as you'd do for any other Godot signal. Note that events are named usingyour delegate's name minus the finalEventHandler part.

publicoverridevoid_Ready(){MySignal+=()=>GD.Print("Hello!");MySignalWithArgument+=SayHelloTo;}privatevoidSayHelloTo(stringname){GD.Print($"Hello {name}!");}

Warning

If you want to connect to these signals in the editor, you will need to (re)build the projectto see them appear.

You can click theBuild button in the upper-right corner of the editor to do so.

Signal emission

To emit signals, use theEmitSignal method. Note that, as for signals defined by the engine,your custom signal names are listed under the nestedSignalName class.

publicvoidMyMethodEmittingSignals(){EmitSignal(SignalName.MySignal);EmitSignal(SignalName.MySignalWithArgument,"World");}

In contrast with other C# events, you cannot useInvoke to raise events tied to Godot signals.

Signals support arguments of anyVariant-compatible type.

Consequently, anyNode orRefCounted will be compatible automatically, but custom data objects will needto inherit fromGodotObject or one of its subclasses.

usingGodot;publicpartialclassDataObject:GodotObject{publicstringMyFirstString{get;set;}publicstringMySecondString{get;set;}}

Bound values

Sometimes you'll want to bind values to a signal when the connection is established, rather than(or in addition to) when the signal is emitted. To do so, you can use an anonymous function like inthe following example.

Here, theButton.Pressed signal does not take any argument. But wewant to use the sameModifyValue for both the "plus" and "minus" buttons. So we bind themodifier value at the time we're connecting the signals.

publicintValue{get;privateset;}=1;publicoverridevoid_Ready(){ButtonplusButton=GetNode<Button>("PlusButton");plusButton.Pressed+=()=>ModifyValue(1);ButtonminusButton=GetNode<Button>("MinusButton");minusButton.Pressed+=()=>ModifyValue(-1);}privatevoidModifyValue(intmodifier){Value+=modifier;}

Signal creation at runtime

Finally, you can create custom signals directly while your game is running. Use theAddUserSignalmethod for that. Be aware that it should be executed before any use of said signals (eitherconnecting to them or emitting them). Also, note that signals created this way won't be visible through theSignalName nested class.

publicoverridevoid_Ready(){AddUserSignal("MyCustomSignal");EmitSignal("MyCustomSignal");}

Using Connect and Disconnect

In general, it isn't recommended to useConnect() andDisconnect(). These APIs don't provide asmuch type safety as the events. However, they're necessary forconnecting to signals defined by GDScriptand passingConnectFlags.

In the following example, pressing the button for the first time printsGreetings!.OneShot disconnects the signal, so pressing the button againdoes nothing.

publicoverridevoid_Ready(){Buttonbutton=GetNode<Button>("GreetButton");button.Connect(Button.SignalName.Pressed,Callable.From(OnButtonPressed),(uint)GodotObject.ConnectFlags.OneShot);}publicvoidOnButtonPressed(){GD.Print("Greetings!");}

Disconnecting automatically when the receiver is freed

Normally, when anyGodotObject is freed (such as anyNode), Godotautomatically disconnects all connections associated with that object. Thishappens for both signal emitters and signal receivers.

For example, a node with this code will print "Hello!" when the button ispressed, then free itself. Freeing the node disconnects the signal, so pressingthe button again doesn't do anything:

publicoverridevoid_Ready(){ButtonmyButton=GetNode<Button>("../MyButton");myButton.Pressed+=SayHello;}privatevoidSayHello(){GD.Print("Hello!");Free();}

When a signal receiver is freed while the signal emitter is still alive, in somecases automatic disconnection won't happen:

  • The signal is connected to a lambda expression that captures a variable.

  • The signal is a custom signal.

The following sections explain these cases in more detail and includesuggestions for how to disconnect manually.

Note

Automatic disconnection is totally reliable if a signal emitter is freedbefore any of its receivers are freed. With a project style that prefersthis pattern, the above limits may not be a concern.

No automatic disconnection: a lambda expression that captures a variable

If you connect to a lambda expression that captures variables, Godot can't tellthat the lambda is associated with the instance that created it. This causesthis example to have potentially unexpected behavior:

TimermyTimer=GetNode<Timer>("../Timer");intx=0;myTimer.Timeout+=()=>{x++;// This lambda expression captures x.GD.Print($"Tick {x} my name is {Name}");if(x==3){GD.Print("Time's up!");Free();}};
Tick 1, my name is ExampleNodeTick 2, my name is ExampleNodeTick 3, my name is ExampleNodeTime's up![...] System.ObjectDisposedException: Cannot access a disposed object.

On tick 4, the lambda expression tries to access theName property of thenode, but the node has already been freed. This causes the exception.

To disconnect, keep a reference to the delegate created by the lambda expressionand pass that to-=. For example, this node connects and disconnects usingthe_EnterTree and_ExitTree lifecycle methods:

[Export]publicTimerMyTimer{get;set;}privateAction_tick;publicoverridevoid_EnterTree(){intx=0;_tick=()=>{x++;GD.Print($"Tick {x} my name is {Name}");if(x==3){GD.Print("Time's up!");Free();}};MyTimer.Timeout+=_tick;}publicoverridevoid_ExitTree(){MyTimer.Timeout-=_tick;}

In this example,Free causes the node to leave the tree, which calls_ExitTree._ExitTree disconnects the signal, so_tick is nevercalled again.

The lifecycle methods to use depend on what the node does. Another option is toconnect to signals in_Ready and disconnect inDispose.

Note

Godot usesDelegate.Targetto determine what instance a delegate is associated with. When a lambdaexpression doesn't capture a variable, the generated delegate'sTargetis the instance that created the delegate. When a variable is captured, theTarget instead points at a generated type that stores the capturedvariable. This is what breaks the association. If you want to see if adelegate will be automatically cleaned up, try checking itsTarget.

Callable.From doesn't affect theDelegate.Target, so connecting alambda that captures variables usingConnect doesn't work any betterthan+=.

No automatic disconnection: a custom signal

Connecting to a custom signal using+= doesn't disconnect automatically whenthe receiving node is freed.

To disconnect, use-= at an appropriate time. For example:

[Export]publicMyClassTarget{get;set;}publicoverridevoid_EnterTree(){Target.MySignal+=OnMySignal;}publicoverridevoid_ExitTree(){Target.MySignal-=OnMySignal;}

Another solution is to useConnect, which does disconnect automatically withcustom signals:

[Export]publicMyClassTarget{get;set;}publicoverridevoid_EnterTree(){Target.Connect(MyClass.SignalName.MySignal,Callable.From(OnMySignal));}

User-contributed notes

Please read theUser-contributed notes policy before submitting a comment.