ソースを参照

Rewrite GDScript basics - Signals section with more concrete examples (#2526)

Close #2526
Nathan Lovato 6 年 前
コミット
b9a9aea6da

+ 112 - 55
getting_started/scripting/gdscript/gdscript_basics.rst

@@ -1463,104 +1463,161 @@ freed.
 Signals
 ~~~~~~~
 
-Signals are a way to send notification messages from an object that
-other objects can listen to in a generic way. Create custom signals for
-a class using the ``signal`` keyword.
+Signals are a tool to emit messages from an object that other objects can react
+to. To create custom signals for a class, use the ``signal`` keyword.
 
 ::
 
-   # Signal with no arguments
-   signal your_signal_name
+   extends Node
+
+   # A signal named health_depleted
+   signal health_depleted
+
+.. note:: 
 
-   # Signal with two arguments
-   signal your_signal_name_with_args(a, b)
+   Signals are a `Callback
+   <https://en.wikipedia.org/wiki/Callback_(computer_programming)>`
+   mechanism. They also fill the role of Observers, a common programming
+   pattern. For more information, read the `Observer
+   tutorial<http://gameprogrammingpatterns.com/observer.html>`_ in the
+   Game Programming Patterns ebook.
 
-These signals may be connected to methods in the same manner as you connect
-built-in signals of nodes such as :ref:`class_Button` or :ref:`class_RigidBody`.
+You can connect these signals to methods the same way you connect built-in
+signals of nodes like :ref:`class_Button` or :ref:`class_RigidBody`.
 
-Here's an example that creates a custom signal in one script and connects
-the custom signal to a method in a separate script, using the
-:ref:`Object.connect() <class_Object_method_connect>` method:
+In the example below, we connect the ``health_depleted`` signal from a
+``Character`` node to a ``Game`` node. When the ``Character`` node emits the
+signal, the game node's ``_on_Character_health_depleted`` is called:
 
 ::
 
-   # your_notifier.gd
+   # Game.gd
+
+   func _ready():
+      var character_node = get_node('Character')
+      character_node.connect("health_depleted", self, "_on_Character_health_depleted")
+
+   func _on_Character_health_depleted():
+      get_tree().reload_current_scene()
 
-   signal data_found
+You can emit as many arguments as you want along with a signal. 
 
-   var your_data = 42
+Here is an example where this is useful. Let's say we want a life bar on screen
+to react to health changes with an animation, but we want to keep the user
+interface separate from the player in our scene tree.
+
+In our ``Character.gd`` script, we define a ``health_changed`` signal and emit
+it with :ref:`Object.emit_signal() <class_Object_method_emit_signal>`, and from
+a ``Game`` node higher up our scene tree, we connect it to the ``Lifebar`` using
+the :ref:`Object.connect() <class_Object_method_connect>` method:
 
 ::
 
-   # your_handler.gd
+    # Character.gd
+
+    ...
+    signal health_changed
+
+    func take_damage(amount):
+        var old_health = health
+        health -= amount
 
-   func your_handler():
-      print("Your handler was called!")
+        # We emit the health_changed signal every time the
+        # character takes damage
+        emit_signal(health_changed", old_health, health)
+    ...
 
 ::
 
-   # your_game.gd
+    # Lifebar.gd 
 
-   func _ready():
-      var notifier = your_notifier.new()
-      var handler = your_handler.new()
+    # Here, we define a function to use as a callback when the
+    # character's health_changed signal is emitted
+
+    ...
+    func _on_Character_health_changed(old_value, new_value):
+        if old_value > new_value:
+            progress_bar.modulate = Color.red
+        else:
+            progress_bar.modulate = Color.green
 
-      notifier.connect("data_found", handler, "your_handler")
+        # Imagine that `animate` is a user-defined function that animates the
+        # bar filling up or emptying itself
+        progress_bar.animate(old_value, new_value)
+    ...
 
-GDScript can bind arguments to connections between a signal and a method.
-When the signal is emitted, calling the connected method, the bound argument is
-given to the method. These bound arguments are specific to the connection
-rather than the signal or the method, meaning that each connection has
-unique bindings.
+.. note::
+   
+    To use signals, your class has to extend the ``Object`` class or any
+    type extending it like ``Node``, ``KinematicBody``, ``Control``...
 
-Here is an example that creates a connection between a button's ``pressed`` signal and
-a method, binding the button instance to the connection. The handler uses the
-bound argument to print which button instance was pressed.
+In the ``Game`` node, we get both the ``Character`` and ``Lifebar`` nodes, then
+connect the character, that emits the signal, to the receiver, the ``Lifebar``
+node in this case.
 
 ::
-   
-   func pressed_handler(which):
-      print("A button was pressed! Button's name was:", which.get_name())
+
+   # Game.gd
 
    func _ready():
-      for button in get_node("buttons").get_children()
-         # Connect the button's 'pressed' signal to our 'pressed_handler' method
-         # Bind the button to the connection so we know which button was pressed
-         button.connect("pressed", self, "pressed_handler", [button])
+      var character_node = get_node('Character')
+      var lifebar_node = get_node('UserInterface/Lifebar')
+
+      character_node.connect("health_changed", lifebar_node, "_on_Character_health_changed")
 
-Signals are generated by the :ref:`Object.emit_signal() <class_Object_method_emit_signal>`
-method which broadcasts the signal and arguments.
+This allows the ``Lifebar`` to react to health changes without coupling it to
+the ``Character`` node.
 
-Extending a previous example to use all the features of GDScript signals:
+you can write optional argument names in parentheses after the signal's
+definition. 
 
 ::
 
-   # your_notifier.gd
+   # Defining a signal that forwards two arguments
+   signal health_changed(old_value, new_value)
+
+These 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.
 
-   signal data_found(data)
+.. image:: img/gdscript_basics_signals_node_tab_1.png
 
-   var your_data = 42
+GDScript can bind an array of values to connections between a signal and a method. When
+the signal is emitted, the callback method receives the bound values. These bound
+arguments are unique to each connection, and the values will stay the same.
 
-   func _process(delta):
-      if delta == your_data:
-         emit_signal("data_found", your_data)
+You can use this array of values to add extra constant information to the
+connection if the emitted signal itself doesn't give you access to all the data
+that you need.
+
+Building on the example above, let's say we want to display a log of the damage
+taken by each character on the screen, like ``Player1 took 22 damage.``. The
+``health_changed`` signal doesn't give us the name of the character that took
+damage. So when we connect the signal to the in-game console, we can add the
+character's name in the binds array argument:
 
 ::
+   
+   # Game.gd
 
-   # your_handler.gd
+   func _ready():
+      var character_node = get_node('Character')
+      var battle_log_node = get_node('UserInterface/BattleLog')
 
-   func your_handler(data, obj):
-      print("Your handler was called from: ", obj.get_name(), " with data: ", data)
+      character_node.connect("health_changed", battle_log_node, "_on_Character_health_changed", [character_node.name])
+
+Our ``BattleLog`` node receives each element in the binds array as an extra argument:
 
 ::
 
-   # your_game.gd
+   # BattleLog.gd
 
-   func _ready():
-      var notifier = your_notifier.new()
-      var handler = your_handler.new()
+   func _on_Character_health_changed(old_value, new_value, character_name):
+      if not new_value <= old_value:
+         return
+      var damage = new_value - old_value
+      label.text += character_name + " took " + str(damage) + " damage."
 
-      notifier.connect("data_found", handler, "your_handler", [notifier])
 
 Coroutines with yield
 ~~~~~~~~~~~~~~~~~~~~~

BIN
getting_started/scripting/gdscript/img/gdscript_basics_signals_node_tab_1.png