Forráskód Böngészése

Rewrite step_by_step/signals.rst

Closes #4174
Nathan Lovato 4 éve
szülő
commit
1d2ff09f89
21 módosított fájl, 234 hozzáadás és 177 törlés
  1. 1 1
      getting_started/introduction/key_concepts_overview.rst
  2. BIN
      getting_started/step_by_step/img/signals_01_new_scene.png
  3. BIN
      getting_started/step_by_step/img/signals_02_2d_scene.png
  4. BIN
      getting_started/step_by_step/img/signals_03_dragging_scene.png
  5. BIN
      getting_started/step_by_step/img/signals_04_add_child_node.png
  6. BIN
      getting_started/step_by_step/img/signals_05_add_button.png
  7. BIN
      getting_started/step_by_step/img/signals_06_drag_button.png
  8. BIN
      getting_started/step_by_step/img/signals_07_select_tool.png
  9. BIN
      getting_started/step_by_step/img/signals_08_toggle_motion_text.png
  10. BIN
      getting_started/step_by_step/img/signals_09_scene_setup.png
  11. BIN
      getting_started/step_by_step/img/signals_10_node_dock.png
  12. BIN
      getting_started/step_by_step/img/signals_11_pressed_signals.png
  13. BIN
      getting_started/step_by_step/img/signals_12_node_connection.png
  14. BIN
      getting_started/step_by_step/img/signals_13_signals_connection_icon.png
  15. BIN
      getting_started/step_by_step/img/signals_14_signals_connection_info.png
  16. BIN
      getting_started/step_by_step/img/signals_15_scene_tree.png
  17. BIN
      getting_started/step_by_step/img/signals_16_click_script.png
  18. BIN
      getting_started/step_by_step/img/signals_17_custom_signal.png
  19. BIN
      getting_started/step_by_step/img/signals_advanced_connection_window.png
  20. 2 0
      getting_started/step_by_step/scripting_player_input.rst
  21. 231 176
      getting_started/step_by_step/signals.rst

+ 1 - 1
getting_started/introduction/key_concepts_overview.rst

@@ -76,7 +76,7 @@ flexibility in how you structure your scenes.
 .. image:: img/key_concepts_signals.png
 
 .. note:: Signals are Godot's version of the *observer* pattern. You can read
-          more about the observer pattern here:
+          more about it here:
           https://gameprogrammingpatterns.com/observer.html
 
 For example, buttons emit a signal when pressed. You can connect to this signal

BIN
getting_started/step_by_step/img/signals_01_new_scene.png


BIN
getting_started/step_by_step/img/signals_02_2d_scene.png


BIN
getting_started/step_by_step/img/signals_03_dragging_scene.png


BIN
getting_started/step_by_step/img/signals_04_add_child_node.png


BIN
getting_started/step_by_step/img/signals_05_add_button.png


BIN
getting_started/step_by_step/img/signals_06_drag_button.png


BIN
getting_started/step_by_step/img/signals_07_select_tool.png


BIN
getting_started/step_by_step/img/signals_08_toggle_motion_text.png


BIN
getting_started/step_by_step/img/signals_09_scene_setup.png


BIN
getting_started/step_by_step/img/signals_10_node_dock.png


BIN
getting_started/step_by_step/img/signals_11_pressed_signals.png


BIN
getting_started/step_by_step/img/signals_12_node_connection.png


BIN
getting_started/step_by_step/img/signals_13_signals_connection_icon.png


BIN
getting_started/step_by_step/img/signals_14_signals_connection_info.png


BIN
getting_started/step_by_step/img/signals_15_scene_tree.png


BIN
getting_started/step_by_step/img/signals_16_click_script.png


BIN
getting_started/step_by_step/img/signals_17_custom_signal.png


BIN
getting_started/step_by_step/img/signals_advanced_connection_window.png


+ 2 - 0
getting_started/step_by_step/scripting_player_input.rst

@@ -139,3 +139,5 @@ button presses from the users. There are quite a few more.
 
 The ``Input`` singleton allows you to react to the players' input anywhere in
 your code. In particular, you'll get to use it in the ``_process()`` loop.
+
+In the next lesson, we'll build upon

+ 231 - 176
getting_started/step_by_step/signals.rst

@@ -1,281 +1,336 @@
+.. Intention: give the user a first taste of signals. We should write more
+   documentation in the scripting/ section.
+.. Note: GDScript snippets use one line return instead of two because they're
+   really short.
+
 .. meta::
     :keywords: Signal
 
 .. _doc_signals:
 
-Signals
-=======
+Using Signals
+=============
 
-Introduction
-------------
+In this lesson, we will look at signals. They are messages that nodes emit when
+something specific happens to them, like a button being pressed. Other nodes can
+connect to that signal and call a function when the event occurs.
 
-Signals are Godot's version of the *observer* pattern. They allow a node to
-send out a message that other nodes can listen for and respond to. For example,
-rather than continuously checking a button to see if it's being pressed, the
-button can emit a signal when it's pressed.
+It is a delegation mechanism built into Godot that allows one game object to
+react to a change in another without them referencing one another. Using signals
+limits `coupling
+<https://en.wikipedia.org/wiki/Coupling_(computer_programming)>`_ and keeps your
+code flexible.
 
-.. note:: You can read more about the observer pattern here: https://gameprogrammingpatterns.com/observer.html
+For example, you might have a life bar on the screen that represents the
+player’s health. When the player takes damage or uses a healing potion, you want
+the bar to reflect the change. To do so, in Godot, you would use signals.
 
-Signals are a way to *decouple* your game objects, which leads to better organized
-and more manageable code. Instead of forcing game objects to expect other objects
-to always be present, they can instead emit signals that all interested objects can
-subscribe to and respond to.
+.. note:: As mentioned in the introduction, signals are Godot's version of the
+          observer pattern. You can learn more about it here:
+          https://gameprogrammingpatterns.com/observer.html
 
-Below you can see some examples of how you can use signals in your own projects.
+We will now use a signal to make our Godot icon from last part move and stop
+by pressing a button.
 
-Timer example
--------------
+.. Example
 
-To see how signals work, let's try using a :ref:`Timer <class_Timer>` node. Create
-a new scene with a Node2D and two children: a Timer and a :ref:`Sprite <class_Sprite>`.
-In the Scene dock, rename Node2D to TimerExample.
+Scene setup
+-----------
 
-For the Sprite's texture, you can use the Godot icon, or any other image you
-like. Do so by selecting ``Load`` in the Sprite's Texture attribute drop-down menu.
-Attach a script to the root node, but don't add any code to it yet.
+Create a new scene by going to the menu Scene -> New Scene.
 
-Your scene tree should look like this:
+.. image:: img/signals_01_new_scene.png
 
-.. image:: img/signals_node_setup.png
+In the Scene dock, click the 2D Scene button. This will add a Node2D as our
+root.
 
-In the Timer node's properties, check the "On" box next to *Autostart*. This will
-cause the timer to start automatically when you run the scene. You can leave the
-*Wait Time* at 1 second.
+.. image:: img/signals_02_2d_scene.png
 
-Next to the "Inspector" tab is a tab labeled "Node". Click on this tab and you'll
-see all of the signals that the selected node can emit. In the case of the Timer
-node, the one we're concerned with is "timeout". This signal is emitted whenever
-the Timer reaches ``0``.
 
-.. image:: img/signals_node_tab_timer.png
+In the FileSystem dock, click and drag the ``Sprite.tscn`` file you saved
+previously onto the Node2D to instantiate it.
 
-Click on the "timeout()" signal and click "Connect..." at the bottom of the signals
-panel. You'll see the following window, where you can define how you want to connect
-the signal:
+.. image:: img/signals_03_dragging_scene.png
 
-.. image:: img/signals_connect_dialog_timer.png
+We want to add another node as a sibling of the Sprite. To do so, right-click on
+Node2D and select Add Child Node.
 
-On the left side, you'll see the nodes in your scene and can select the node that
-you want to "listen" for the signal. Note that the Timer node is blue, this is a
-visual indication that it's the node that is emitting the signal. Select the root
-node.
+.. image:: img/signals_04_add_child_node.png
 
-If you toggle the Advanced menu, you'll see on the right side that you can bind an arbitrary number of arguments of (possibly) different
-types. This can be useful when you have more than one signal connected to the same method,
-as each signal propagation will result in different values for those extra call arguments.
+Search for the Button node type and add it.
 
-On the bottom of the window is a field labeled "Receiver Method". This is the name
-of the function in the target node's script that you want to use. By default,
-Godot will create this function using the naming convention ``_on_<node_name>_<signal_name>``
-but you can change it if you wish.
+.. image:: img/signals_05_add_button.png
 
-Click "Connect" and you'll see that the function has been created in the script:
+The node is small by default. Click and drag on the bottom-right handle of the
+Button in the viewport to resize it.
 
-.. tabs::
- .. code-tab:: gdscript GDScript
+.. image:: img/signals_06_drag_button.png
 
-    extends Node2D
+If you don't see the handles, ensure the select tool is active in the toolbar.
 
+.. image:: img/signals_07_select_tool.png
 
-    func _on_Timer_timeout():
-        pass # Replace with function body.
+Click and drag on the button itself to move it closer to the sprite.
 
- .. code-tab:: csharp
+You can also write a label on the Button by editing its Text property in the
+Inspector.
 
-    public class TimerExample : Node2D
-    {
-        public void _on_Timer_timeout()
-        {
-            // Replace with function body.
-        }
-    }
+.. image:: img/signals_08_toggle_motion_text.png
 
-Now we can replace the placeholder code with whatever code we want to run when
-the signal is received. Let's make the Sprite blink:
+Your scene tree and viewport should look like this.
 
-.. tabs::
- .. code-tab:: gdscript GDScript
+.. image:: img/signals_09_scene_setup.png
 
-    extends Node2D
+Connecting a signal in the editor
+---------------------------------
 
+Here, we want to connect the Button's "pressed" signal to our Sprite, and we
+want to call a new function that will toggle its motion on and off. We need to
+have a script attached to the Sprite node, which we do from the previous lesson.
 
-    func _on_Timer_timeout():
-        # Note: the `$` operator is a shorthand for `get_node()`,
-        # so `$Sprite` is equivalent to `get_node("Sprite")`.
-        $Sprite.visible = !$Sprite.visible
+You can connect signals in the Node dock. Select the Button node and, on the
+right side of the editor, click on the tab named "Node" next to the Inspector.
 
- .. code-tab:: csharp
+.. image:: img/signals_10_node_dock.png
 
-    public class TimerExample : Node2D
-    {
-        public void _on_Timer_timeout()
-        {
-            var sprite = GetNode<Sprite>("Sprite");
-            sprite.Visible = !sprite.Visible;
-        }
-    }
+The dock displays a list of signals available on the selected node.
 
-Run the scene and you'll see the Sprite blinking on and off every second. You can
-change the Timer's *Wait Time* property to alter this.
+.. image:: img/signals_11_pressed_signals.png
 
-Connecting signals in code
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+Double-click the "pressed" signal to open the node connection window.
 
-You can also make the signal connection in code rather than with the editor. This
-is usually necessary when you're instancing nodes via code and so you can't use
-the editor to make the connection.
+.. image:: img/signals_12_node_connection.png
 
-First, disconnect the signal by selecting the connection in the Timer's "Node"
-tab and clicking disconnect.
+There, you can connect the signal to the Sprite node. The node needs a receiver
+method, a function that Godot will call when the Button emits the signal. The
+editor generates one for you. By convention, we name these callback methods
+"_on_NodeName_signal_name". Here, it'll be "_on_Button_pressed".
 
-.. image:: img/signals_disconnect_timer.png
+.. note::
 
-To make the connection in code, we can use the ``connect`` function. We'll put it
-in ``_ready()`` so that the connection will be made on run. The syntax of the
-function is ``<source_node>.connect(<signal_name>, <target_node>, <target_function_name>)``.
-Here is the code for our Timer connection:
+   When connecting signals via the editor's Node dock, you can use two
+   modes. The simple one only allows you to connect to nodes that have a
+   script attached to them and creates a new callback function on them.
 
-.. tabs::
- .. code-tab:: gdscript GDScript
+   .. image:: img/signals_advanced_connection_window.png
 
-    extends Node2D
+   The advanced view lets you connect to any node and any built-in
+   function, add arguments to the callback, and set options. You can
+   toggle the mode in the window's bottom-right by clicking the radio
+   button.
 
+Click the connect button to complete the signal connection and jump to the
+Script workspace. You should see the new method with a connection icon in the
+left margin.
 
-    func _ready():
-        $Timer.connect("timeout", self, "_on_Timer_timeout")
+.. image:: img/signals_13_signals_connection_icon.png
 
+If you click the icon, a window pops up and displays information about the
+connection. This feature is only available when connecting nodes in the editor.
 
-    func _on_Timer_timeout():
-        $Sprite.visible = !$Sprite.visible
+.. image:: img/signals_14_signals_connection_info.png
 
- .. code-tab:: csharp
+Let's replace the line with the ``pass`` keyword with code that'll toggle the
+node's motion.
 
-    public class TimerExample : Node2D
-    {
-        public override void _Ready()
-        {
-            GetNode("Timer").Connect("timeout", this, nameof(_on_Timer_timeout));
-        }
+Our Sprite moves thanks to code in the ``_process()`` function. Godot provides a
+method to toggle processing on and off: :ref:`Node.set_process()
+<class_Node_method_set_process>`. Another method of the Node class,
+``is_processing()``, returns ``true`` if idle processing is active. We can use
+the ``not`` keyword to invert the value.
 
-        public void _on_Timer_timeout()
-        {
-            var sprite = GetNode<Sprite>("Sprite");
-            sprite.Visible = !sprite.Visible;
-        }
-    }
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
-Custom signals
---------------
+    func _on_Button_pressed():
+        set_process(not is_processing())
 
-You can also declare your own custom signals in Godot:
+This function will toggle processing and, in turn, the icon's motion on and off
+upon pressing the button.
 
+Before trying the game, we need to simplify our ``_process()`` function to move
+the node automatically and not wait for user input. Replace it with the
+following code, which we saw two lessons ago:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
-    extends Node2D
+    func _process(delta: float) -> void:
+        rotation += angular_speed * delta
+        var velocity = Vector2.UP.rotated(rotation) * speed
+        position += velocity * delta
+
+Your complete Sprite.gd code should look like the following.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    extends Sprite
+
+    var speed = 400
+    var angular_speed = PI
+
+    func _process(delta: float) -> void:
+        rotation += angular_speed * delta
+        var velocity = Vector2.UP.rotated(rotation) * speed
+        position += velocity * delta
+
+    func _on_Button_pressed():
+        set_process(not is_processing())
+
+Run the scene now and click the button to see the sprite start and stop.
+
+Connecting a signal via code
+----------------------------
+
+You can connect signals via code instead of using the editor. This is necessary
+when you create nodes or instantiate scenes inside of a script.
 
+Let's use a different node here. Godot has a :ref:`Timer <class_Timer>` node
+that's useful to implement skill cooldown times, weapon reloading, and more.
 
-    signal my_signal
+Head back to the 2D workspace. You can either click the "2D" text at the top of
+the window or press :kbd:`F2` (:kbd:`Alt + 2` on macOS).
 
- .. code-tab:: csharp
+In the Scene dock, right-click on the Sprite node and add a new node. Search for
+Timer and add the corresponding node. Your scene should now look like this.
 
-    public class Main : Node2D
-    {
-        [Signal]
-        public delegate void MySignal();
-    }
+.. image:: img/signals_15_scene_tree.png
 
-Once declared, your custom signals will appear in the Inspector and can be connected
-in the same way as a node's built-in signals.
+Click the script icon next to Sprite to jump back to the scripting workspace.
 
-To emit a signal via code, use the ``emit_signal`` function:
+.. image:: img/signals_16_click_script.png
+
+We need to do two operations to connect the nodes via code:
+
+1. Get a reference to the Timer from the Sprite.
+2. Call the Timer's ``connect()`` method.
+
+.. note:: To connect to a signal via code, you need to call the ``connect()``
+          method of the node you want to listen to. In this case, we want to
+          listen to the Timer's "timeout" signal.
+
+To get a reference to a node relative to the current one, we use the method
+:ref:`Node.get_node() <class_Node_method_get_node>`. We can store the reference
+in a variable.
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
-    extends Node2D
+    extends Sprite
+
+    #...
+
+    func _ready():
+        var timer = get_node("Timer")
 
+The function ``get_node()`` looks at the Sprite's children and gets nodes by
+their name. For example, if you renamed the Timer node to "BlinkingTimer" in the
+editor, you would have to change the call to ``get_node("BlinkingTimer")``.
 
-    signal my_signal
+.. add seealso to a page that explains node features.
 
+We can now connect the Timer to the Sprite in the ``_ready()`` function.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
     func _ready():
-        emit_signal("my_signal")
+        var timer = get_node("Timer")
+        timer.connect("timeout", self, "_on_Timer_timeout")
+
+The line reads like so: we connect the Timer's "timeout" signal to the node to
+which the script is attached (``self``). When the Timer emits "timeout", we want
+to call the function "_on_Timer_timeout", that we need to define. Let's add it
+at the bottom of our script and use it to toggle our sprite's visibility.
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
- .. code-tab:: csharp
+    func _on_Timer_timeout():
+        visible = not visible
 
-    public class Main : Node2D
-    {
-        [Signal]
-        public delegate void MySignal();
+The ``visible`` property is a boolean that controls the visibility of our node.
+The line ``visible = not visible`` toggles the value. If ``visible`` is
+``true``, it becomes ``false``, and vice-versa.
 
-        public override void _Ready()
-        {
-            EmitSignal(nameof(MySignal));
-        }
-    }
+Custom signals
+--------------
 
-A signal can also optionally declare one or more arguments. Specify the
-argument names between parentheses:
+You can define custom signals in a script. Say, for example, that you want to
+show a game over screen when the player's health reaches zero. To do so, you
+could define a signal named "died" or "health_depleted" when their health
+reaches 0.
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
-    extends Node
+    extends Node2D
 
+    signal health_depleted
 
-    signal my_signal(value, other_value)
+    var health = 10
 
- .. code-tab:: csharp
+.. note:: As signals represent events that just occurred, we generally use an
+          action verb in the past tense in their names.
 
-    public class Main : Node
-    {
-        [Signal]
-        public delegate void MySignal(bool value, int other_value);
-    }
+Your signals work the same way as built-in ones: they appear in the Node tab and
+you can connect to them like any other.
 
-.. note::
+.. image:: img/signals_17_custom_signal.png
 
-    The signal arguments show up in the editor's node dock, and Godot
-    can use them to generate callback functions for you. However, you can still
-    emit any number of arguments when you emit signals. So it's up to you to
-    emit the correct values.
+To emit a signal in your scripts, call ``emit_signal()``.
 
-To pass values, add them as the second argument to the ``emit_signal`` function:
+.. tabs::
+ .. code-tab:: gdscript GDScript
+
+    func take_damage(amount):
+        health -= amount
+        if health <= 0:
+            emit_signal("health_depleted")
+
+A signal can optionally declare one or more arguments. Specify the argument
+names between parentheses:
 
 .. tabs::
  .. code-tab:: gdscript GDScript
 
     extends Node
 
+    signal health_changed(old_value, new_value)
 
-    signal my_signal(value, other_value)
+.. note::
 
+    The signal arguments show up in the editor's node dock, and Godot can use
+    them to generate callback functions for you. However, you can still emit any
+    number of arguments when you emit signals. So it's up to you to emit the
+    correct values.
 
-    func _ready():
-        emit_signal("my_signal", true, 42)
+To emit values along with the signal, add them as extra arguments to the
+``emit_signal()`` function:
+
+.. tabs::
+ .. code-tab:: gdscript GDScript
 
- .. code-tab:: csharp
+    func take_damage(amount):
+        var old_health = health
+        health -= amount
+        emit_signal("health_changed", old_health, health)
 
-    public class Main : Node
-    {
-        [Signal]
-        public delegate void MySignal(bool value, int other_value);
+Summary
+-------
 
-        public override void _Ready()
-        {
-            EmitSignal(nameof(MySignal), true, 42);
-        }
-    }
+Any node in Godot emits signals when something specific happens to them, like a
+button being pressed. Other nodes can connect to individual signals and react to
+selected events.
 
-Conclusion
-----------
+Signals have many uses. With them, you can react to a node entering or exiting
+the game world, to a collision, to a character entering or leaving an area, to
+an element of the interface changing size, and much more.
 
-Many of Godot's built-in node types provide signals you can use to detect
-events. For example, an :ref:`Area2D <class_Area2D>` representing a coin emits
-a ``body_entered`` signal whenever the player's physics body enters its collision
+For example, an :ref:`Area2D <class_Area2D>` representing a coin emits a
+``body_entered`` signal whenever the player's physics body enters its collision
 shape, allowing you to know when the player collected it.
 
-In the next section, :ref:`doc_your_first_game`, you'll build a complete game
-including several uses of signals to connect different game components.
+In the next section, :ref:`doc_your_first_game`, you'll create a complete 2D
+game and put everything you learned so far into practice.