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 theAddUserSignal
method 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'sTarget
is 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));}