Browse Source

Merge pull request #6643 from paulloz/csharp/signals

Create a dedicated "C# Signals" page
Max Hilbrunner 2 years ago
parent
commit
0c18a7cb67

+ 2 - 0
getting_started/introduction/key_concepts_overview.rst

@@ -66,6 +66,8 @@ represent characters, weapons, doors, or your user interface.
 
 
 .. image:: img/key_concepts_scene_tree.webp
 .. image:: img/key_concepts_scene_tree.webp
 
 
+.. _doc_key_concepts_signals:
+
 Signals
 Signals
 -------
 -------
 
 

+ 2 - 0
tutorials/scripting/c_sharp/c_sharp_differences.rst

@@ -493,6 +493,8 @@ Communicating with other scripting languages
 
 
 This is explained extensively in :ref:`doc_cross_language_scripting`.
 This is explained extensively in :ref:`doc_cross_language_scripting`.
 
 
+.. _doc_c_sharp_differences_yield:
+
 Yield
 Yield
 -----
 -----
 
 

+ 0 - 93
tutorials/scripting/c_sharp/c_sharp_features.rst

@@ -83,99 +83,6 @@ otherwise it returns true. Note that using the ``is`` operator against ``null``
 
 
 For more advanced type checking, you can look into `Pattern Matching <https://docs.microsoft.com/en-us/dotnet/csharp/pattern-matching>`_.
 For more advanced type checking, you can look into `Pattern Matching <https://docs.microsoft.com/en-us/dotnet/csharp/pattern-matching>`_.
 
 
-.. _doc_c_sharp_signals:
-
-C# signals
-----------
-
-For a complete C# example, see the :ref:`doc_signals` section in the step by step tutorial.
-
-Declaring a signal in C# is done with the ``[Signal]`` attribute on a delegate.
-
-.. code-block:: csharp
-
-    [Signal]
-    delegate void MySignalEventHandler();
-
-    [Signal]
-    delegate void MySignalWithArgumentsEventHandler(string foo, int bar);
-
-These signals can then be connected either in the editor or from code with ``Connect``.
-If you want to connect a signal in the editor, you need to (re)build the project assemblies to see the new signal. This build can be manually triggered by clicking the "Build" button at the top right corner of the editor window.
-
-.. code-block:: csharp
-
-    public void MyCallback()
-    {
-        GD.Print("My callback!");
-    }
-
-    public void MyCallbackWithArguments(string foo, int bar)
-    {
-        GD.Print($"My callback with: {foo} and {bar}!");
-    }
-
-    public void SomeFunction()
-    {
-        instance.MySignal += MyCallback;
-        instance.MySignalWithArguments += MyCallbackWithArguments;
-    }
-
-Emitting signals is done with the ``EmitSignal`` method.
-
-.. code-block:: csharp
-
-    public void SomeFunction()
-    {
-        EmitSignal(SignalName.MySignal);
-        EmitSignal(SignalName.MySignalWithArguments, "hello there", 28);
-    }
-
-Notice that you can always reference a signal name with its generated ``SignalName``.
-
-It is possible to bind values when establishing a connection by passing a Godot array.
-
-.. code-block:: csharp
-
-    public int Value { get; private set; } = 0;
-
-    private void ModifyValue(int modifier)
-    {
-        Value += modifier;
-    }
-
-    public void SomeFunction()
-    {
-        var plusButton = (Button)GetNode("PlusButton");
-        var minusButton = (Button)GetNode("MinusButton");
-
-        plusButton.Pressed += () => ModifyValue(1);
-        minusButton.Pressed += () => ModifyValue(-1);
-    }
-
-Signals support parameters and bound values of all the `built-in types <https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/built-in-types-table>`_ and Classes derived from :ref:`Godot.Object <class_Object>`.
-Consequently, any ``Node`` or ``Reference`` will be compatible automatically, but custom data objects will need to extend from `Godot.Object` or one of its subclasses.
-
-.. code-block:: csharp
-
-    using Godot;
-
-    public partial class DataObject : Godot.Object
-    {
-        public string Field1 { get; set; }
-        public string Field2 { get; set; }
-    }
-
-
-Finally, signals can be created by calling ``AddUserSignal``, but be aware that it should be executed before any use of said signals (with ``Connect`` or ``EmitSignal``).
-
-.. code-block:: csharp
-
-    public void SomeFunction()
-    {
-        AddUserSignal("MyOtherSignal");
-        EmitSignal("MyOtherSignal");
-    }
 
 
 Preprocessor defines
 Preprocessor defines
 --------------------
 --------------------

+ 152 - 0
tutorials/scripting/c_sharp/c_sharp_signals.rst

@@ -0,0 +1,152 @@
+.. _doc_c_sharp_signals:
+
+C# Signals
+==========
+
+For a detailed explanation of signals in general, see the :ref:`doc_signals` section in the step
+by step tutorial.
+
+While it is still possible to use signals through the ``Connect``/``Disconnect`` API, C# gives us
+a more idiomatic way to implement the :ref:`observer pattern<doc_key_concepts_signals>`.
+
+Signals as C# events
+--------------------
+
+To provide more type-safety, Godot signals are also all available through `events <https://learn.microsoft.com/en-us/dotnet/csharp/events-overview>`_.
+You can handle these events, as any other event, with the ``+=`` and ``-=`` operators.
+
+.. code-block:: csharp
+
+    Timer myTimer = GetNode<Timer>("Timer");
+    myTimer.Timeout += () => GD.Print("Timeout!");
+
+In addition, you can always access signal names associated with a node type through its nested
+``SignalName`` class. This is useful when, for example, you want to await on a signal (see :ref:`doc_c_sharp_differences_yield`).
+
+.. code-block:: csharp
+
+    await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
+
+.. note::
+
+    Godot will take care of disconnecting all the signals you connected through events when your
+    nodes are freed. Meaning that: as you don't need to call ``Disconnect`` on all signals you used
+    ``Connect`` on, you don't need to ``-=`` all the signals you used ``+=`` on.
+
+Custom signals as C# events
+---------------------------
+
+To declare a custom event in your C# script, use the ``[Signal]`` attribute on a delegate type.
+Note that the name of this delegate needs to end with ``EventHandler``.
+
+.. code-block:: csharp
+
+    [Signal]
+    public delegate void MySignalEventHandler();
+
+    [Signal]
+    public delegate void MySignalWithArgumentEventHandler(string myString);
+
+Once this is done, Godot will create the appropriate events automatically behind the scenes. You
+can then use said events as you'd do for any other Godot signal. Note that events are named using
+your delegate's name minus the final ``EventHandler`` part.
+
+.. code-block:: csharp
+
+    public override void _Ready()
+    {
+        MySignal += () => GD.Print("Hello!");
+        MySignalWithArgument += SayHelloTo;
+    }
+
+    private void SayHelloTo(string name)
+    {
+        GD.Print($"Hello {name}!");
+    }
+
+.. warning::
+
+    If you want to connect to these signals in the editor, you will need to (re)build the project
+    to see them appear.
+
+    You can click the **Build** button in the upper-right corner of the editor to do so.
+
+Signal emission
+---------------
+
+To emit signals, use the ``EmitSignal`` method. Note that, as for signals defined by the engine,
+your custom signal names are listed under the nested ``SignalName`` class.
+
+.. code-block:: csharp
+
+    public void MyMethodEmittingSignals()
+    {
+        EmitSignal(SignalName.MySignal);
+        EmitSignal(SignalName.MySignalWithArgument, "World");
+    }
+
+In contrast with other C# events, you cannot use ``Invoke`` to raise events tied to Godot signals.
+
+Signals support arguments of:
+
+* All the `built-in value types <https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/built-in-types-table>`_,
+  except ``decimal``, ``nint`` and ``nuint``
+* ``string``
+* Classes derived from :ref:`Godot.Object <class_Object>`
+* Collections types defined in the ``Godot.Collections`` namespace
+
+Consequently, any ``Node`` or ``Reference`` will be compatible automatically, but custom data objects will need
+to inherit from ``Godot.Object`` or one of its subclasses.
+
+.. code-block:: csharp
+
+    public partial class DataObject : Godot.Object
+    {
+        public string MyFirstString { get; set; }
+        public string MySecondString { 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 in
+the following example.
+
+Here, the :ref:`Button.Pressed <class_BaseButton_signal_pressed>` signal do not take any argument. But we
+want to use the same ``ModifyValue`` for both the "plus" and "minus" buttons. So we bind the
+modifier value at the time we're connecting the signals.
+
+.. code-block:: csharp
+
+    public int Value { get; private set; } = 1;
+
+    public override void _Ready()
+    {
+        Button plusButton = GetNode<Button>("PlusButton");
+        plusButton.Pressed += () => ModifyValue(1);
+
+        Button minusButton = GetNode<Button>("MinusButton");
+        minusButton.Pressed += () => ModifyValue(-1);
+    }
+
+    private void ModifyValue(int modifier)
+    {
+        Value += modifier;
+    }
+
+Signal creation at runtime
+--------------------------
+
+Finally, you can create custom signals directly while your game is running. Use the ``AddUserSignal``
+method for that. Be aware that it should be executed before any use of said signals (either
+connecting to them or emitting them). Also, note that signals created this way won't be visible through the
+``SignalName`` nested class.
+
+.. code-block:: csharp
+
+    public override void _Ready()
+    {
+        AddUserSignal("MyCustomSignal");
+        EmitSignal("MyCustomSignal");
+    }

+ 1 - 0
tutorials/scripting/c_sharp/index.rst

@@ -8,5 +8,6 @@ C#
    c_sharp_basics
    c_sharp_basics
    c_sharp_features
    c_sharp_features
    c_sharp_differences
    c_sharp_differences
+   c_sharp_signals
    c_sharp_exports
    c_sharp_exports
    c_sharp_style_guide
    c_sharp_style_guide