c_sharp_signals.rst 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. .. _doc_c_sharp_signals:
  2. C# signals
  3. ==========
  4. For a detailed explanation of signals in general, see the :ref:`doc_signals` section in the step
  5. by step tutorial.
  6. Signals are implemented using C# events, the idiomatic way to represent
  7. :ref:`the observer pattern<doc_key_concepts_signals>` in C#. This is the
  8. recommended way to use signals in C# and the focus of this page.
  9. In some cases it's necessary to use the older
  10. :ref:`Connect()<class_object_method_connect>` and
  11. :ref:`Disconnect()<class_object_method_disconnect>` APIs.
  12. See :ref:`using_connect_and_disconnect` for more details.
  13. If you encounter a ``System.ObjectDisposedException`` while handling a signal,
  14. you might be missing a signal disconnection. See
  15. :ref:`disconnecting_automatically_when_the_receiver_is_freed` for more details.
  16. Signals as C# events
  17. --------------------
  18. To provide more type-safety, Godot signals are also all available through `events <https://learn.microsoft.com/en-us/dotnet/csharp/events-overview>`_.
  19. You can handle these events, as any other event, with the ``+=`` and ``-=`` operators.
  20. .. code-block:: csharp
  21. Timer myTimer = GetNode<Timer>("Timer");
  22. myTimer.Timeout += () => GD.Print("Timeout!");
  23. In addition, you can always access signal names associated with a node type through its nested
  24. ``SignalName`` class. This is useful when, for example, you want to await on a signal (see :ref:`doc_c_sharp_differences_await`).
  25. .. code-block:: csharp
  26. await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
  27. Custom signals as C# events
  28. ---------------------------
  29. To declare a custom event in your C# script, use the ``[Signal]`` attribute on a public delegate type.
  30. Note that the name of this delegate needs to end with ``EventHandler``.
  31. .. code-block:: csharp
  32. [Signal]
  33. public delegate void MySignalEventHandler();
  34. [Signal]
  35. public delegate void MySignalWithArgumentEventHandler(string myString);
  36. Once this is done, Godot will create the appropriate events automatically behind the scenes. You
  37. can then use said events as you'd do for any other Godot signal. Note that events are named using
  38. your delegate's name minus the final ``EventHandler`` part.
  39. .. code-block:: csharp
  40. public override void _Ready()
  41. {
  42. MySignal += () => GD.Print("Hello!");
  43. MySignalWithArgument += SayHelloTo;
  44. }
  45. private void SayHelloTo(string name)
  46. {
  47. GD.Print($"Hello {name}!");
  48. }
  49. .. warning::
  50. If you want to connect to these signals in the editor, you will need to (re)build the project
  51. to see them appear.
  52. You can click the **Build** button in the upper-right corner of the editor to do so.
  53. Signal emission
  54. ---------------
  55. To emit signals, use the ``EmitSignal`` method. Note that, as for signals defined by the engine,
  56. your custom signal names are listed under the nested ``SignalName`` class.
  57. .. code-block:: csharp
  58. public void MyMethodEmittingSignals()
  59. {
  60. EmitSignal(SignalName.MySignal);
  61. EmitSignal(SignalName.MySignalWithArgument, "World");
  62. }
  63. In contrast with other C# events, you cannot use ``Invoke`` to raise events tied to Godot signals.
  64. Signals support arguments of any :ref:`Variant-compatible type <c_sharp_variant_compatible_types>`.
  65. Consequently, any ``Node`` or ``RefCounted`` will be compatible automatically, but custom data objects will need
  66. to inherit from ``GodotObject`` or one of its subclasses.
  67. .. code-block:: csharp
  68. using Godot;
  69. public partial class DataObject : GodotObject
  70. {
  71. public string MyFirstString { get; set; }
  72. public string MySecondString { get; set; }
  73. }
  74. Bound values
  75. ------------
  76. Sometimes you'll want to bind values to a signal when the connection is established, rather than
  77. (or in addition to) when the signal is emitted. To do so, you can use an anonymous function like in
  78. the following example.
  79. Here, the :ref:`Button.Pressed <class_BaseButton_signal_pressed>` signal does not take any argument. But we
  80. want to use the same ``ModifyValue`` for both the "plus" and "minus" buttons. So we bind the
  81. modifier value at the time we're connecting the signals.
  82. .. code-block:: csharp
  83. public int Value { get; private set; } = 1;
  84. public override void _Ready()
  85. {
  86. Button plusButton = GetNode<Button>("PlusButton");
  87. plusButton.Pressed += () => ModifyValue(1);
  88. Button minusButton = GetNode<Button>("MinusButton");
  89. minusButton.Pressed += () => ModifyValue(-1);
  90. }
  91. private void ModifyValue(int modifier)
  92. {
  93. Value += modifier;
  94. }
  95. Signal creation at runtime
  96. --------------------------
  97. Finally, you can create custom signals directly while your game is running. Use the ``AddUserSignal``
  98. method for that. Be aware that it should be executed before any use of said signals (either
  99. connecting to them or emitting them). Also, note that signals created this way won't be visible through the
  100. ``SignalName`` nested class.
  101. .. code-block:: csharp
  102. public override void _Ready()
  103. {
  104. AddUserSignal("MyCustomSignal");
  105. EmitSignal("MyCustomSignal");
  106. }
  107. .. _using_connect_and_disconnect:
  108. Using Connect and Disconnect
  109. ----------------------------
  110. In general, it isn't recommended to use
  111. :ref:`Connect()<class_object_method_connect>` and
  112. :ref:`Disconnect()<class_object_method_disconnect>`. These APIs don't provide as
  113. much type safety as the events. However, they're necessary for
  114. :ref:`connecting to signals defined by GDScript <connecting_to_signals_cross_language>`
  115. and passing :ref:`ConnectFlags<enum_Object_ConnectFlags>`.
  116. In the following example, pressing the button for the first time prints
  117. ``Greetings!``. ``OneShot`` disconnects the signal, so pressing the button again
  118. does nothing.
  119. .. code-block:: csharp
  120. public override void _Ready()
  121. {
  122. Button button = GetNode<Button>("GreetButton");
  123. button.Connect(Button.SignalName.Pressed, Callable.From(OnButtonPressed), (uint)GodotObject.ConnectFlags.OneShot);
  124. }
  125. public void OnButtonPressed()
  126. {
  127. GD.Print("Greetings!");
  128. }
  129. .. _disconnecting_automatically_when_the_receiver_is_freed:
  130. Disconnecting automatically when the receiver is freed
  131. ------------------------------------------------------
  132. Normally, when any ``GodotObject`` is freed (such as any ``Node``), Godot
  133. automatically disconnects all connections associated with that object. This
  134. happens for both signal emitters and signal receivers.
  135. For example, a node with this code will print "Hello!" when the button is
  136. pressed, then free itself. Freeing the node disconnects the signal, so pressing
  137. the button again doesn't do anything:
  138. .. code-block:: csharp
  139. public override void _Ready()
  140. {
  141. Button myButton = GetNode<Button>("../MyButton");
  142. myButton.Pressed += SayHello;
  143. }
  144. private void SayHello()
  145. {
  146. GD.Print("Hello!");
  147. Free();
  148. }
  149. When a signal receiver is freed while the signal emitter is still alive, in some
  150. cases automatic disconnection won't happen:
  151. - The signal is connected to a lambda expression that captures a variable.
  152. - The signal is a custom signal.
  153. The following sections explain these cases in more detail and include
  154. suggestions for how to disconnect manually.
  155. .. note::
  156. Automatic disconnection is totally reliable if a signal emitter is freed
  157. before any of its receivers are freed. With a project style that prefers
  158. this pattern, the above limits may not be a concern.
  159. No automatic disconnection: a lambda expression that captures a variable
  160. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  161. If you connect to a lambda expression that captures variables, Godot can't tell
  162. that the lambda is associated with the instance that created it. This causes
  163. this example to have potentially unexpected behavior:
  164. .. code-block:: csharp
  165. Timer myTimer = GetNode<Timer>("../Timer");
  166. int x = 0;
  167. myTimer.Timeout += () =>
  168. {
  169. x++; // This lambda expression captures x.
  170. GD.Print($"Tick {x} my name is {Name}");
  171. if (x == 3)
  172. {
  173. GD.Print("Time's up!");
  174. Free();
  175. }
  176. };
  177. .. code-block:: text
  178. Tick 1, my name is ExampleNode
  179. Tick 2, my name is ExampleNode
  180. Tick 3, my name is ExampleNode
  181. Time's up!
  182. [...] System.ObjectDisposedException: Cannot access a disposed object.
  183. On tick 4, the lambda expression tries to access the ``Name`` property of the
  184. node, but the node has already been freed. This causes the exception.
  185. To disconnect, keep a reference to the delegate created by the lambda expression
  186. and pass that to ``-=``. For example, this node connects and disconnects using
  187. the ``_EnterTree`` and ``_ExitTree`` lifecycle methods:
  188. .. code-block:: csharp
  189. [Export]
  190. public Timer MyTimer { get; set; }
  191. private Action _tick;
  192. public override void _EnterTree()
  193. {
  194. int x = 0;
  195. _tick = () =>
  196. {
  197. x++;
  198. GD.Print($"Tick {x} my name is {Name}");
  199. if (x == 3)
  200. {
  201. GD.Print("Time's up!");
  202. Free();
  203. }
  204. };
  205. MyTimer.Timeout += _tick;
  206. }
  207. public override void _ExitTree()
  208. {
  209. MyTimer.Timeout -= _tick;
  210. }
  211. In this example, ``Free`` causes the node to leave the tree, which calls
  212. ``_ExitTree``. ``_ExitTree`` disconnects the signal, so ``_tick`` is never
  213. called again.
  214. The lifecycle methods to use depend on what the node does. Another option is to
  215. connect to signals in ``_Ready`` and disconnect in ``Dispose``.
  216. .. note::
  217. Godot uses `Delegate.Target <https://learn.microsoft.com/en-us/dotnet/api/system.delegate.target>`_
  218. to determine what instance a delegate is associated with. When a lambda
  219. expression doesn't capture a variable, the generated delegate's ``Target``
  220. is the instance that created the delegate. When a variable is captured, the
  221. ``Target`` instead points at a generated type that stores the captured
  222. variable. This is what breaks the association. If you want to see if a
  223. delegate will be automatically cleaned up, try checking its ``Target``.
  224. ``Callable.From`` doesn't affect the ``Delegate.Target``, so connecting a
  225. lambda that captures variables using ``Connect`` doesn't work any better
  226. than ``+=``.
  227. No automatic disconnection: a custom signal
  228. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  229. Connecting to a custom signal using ``+=`` doesn't disconnect automatically when
  230. the receiving node is freed.
  231. To disconnect, use ``-=`` at an appropriate time. For example:
  232. .. code-block:: csharp
  233. [Export]
  234. public MyClass Target { get; set; }
  235. public override void _EnterTree()
  236. {
  237. Target.MySignal += OnMySignal;
  238. }
  239. public override void _ExitTree()
  240. {
  241. Target.MySignal -= OnMySignal;
  242. }
  243. Another solution is to use ``Connect``, which does disconnect automatically with
  244. custom signals:
  245. .. code-block:: csharp
  246. [Export]
  247. public MyClass Target { get; set; }
  248. public override void _EnterTree()
  249. {
  250. Target.Connect(MyClass.SignalName.MySignal, Callable.From(OnMySignal));
  251. }