Rewrite step_by_step/signals.rst

Closes #4174
This commit is contained in:
Nathan Lovato
2020-12-02 15:47:29 -06:00
parent f68abde1e8
commit 1d2ff09f89
21 changed files with 253 additions and 196 deletions
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -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
+250 -195
View File
@@ -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
players 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.
.. image:: img/signals_06_drag_button.png
If you don't see the handles, ensure the select tool is active in the toolbar.
.. image:: img/signals_07_select_tool.png
Click and drag on the button itself to move it closer to the sprite.
You can also write a label on the Button by editing its Text property in the
Inspector.
.. image:: img/signals_08_toggle_motion_text.png
Your scene tree and viewport should look like this.
.. image:: img/signals_09_scene_setup.png
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.
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.
.. image:: img/signals_10_node_dock.png
The dock displays a list of signals available on the selected node.
.. image:: img/signals_11_pressed_signals.png
Double-click the "pressed" signal to open the node connection window.
.. image:: img/signals_12_node_connection.png
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".
.. note::
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.
.. image:: img/signals_advanced_connection_window.png
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.
.. 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.
.. image:: img/signals_14_signals_connection_info.png
Let's replace the line with the ``pass`` keyword with code that'll toggle the
node's motion.
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.
.. tabs::
.. code-tab:: gdscript GDScript
extends Node2D
func _on_Button_pressed():
set_process(not is_processing())
This function will toggle processing and, in turn, the icon's motion on and off
upon pressing the button.
func _on_Timer_timeout():
pass # Replace with function body.
.. code-tab:: csharp
public class TimerExample : Node2D
{
public void _on_Timer_timeout()
{
// Replace with function body.
}
}
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:
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
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
.. code-tab:: csharp
public class TimerExample : Node2D
{
public void _on_Timer_timeout()
{
var sprite = GetNode<Sprite>("Sprite");
sprite.Visible = !sprite.Visible;
}
}
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.
Connecting signals in code
~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
First, disconnect the signal by selecting the connection in the Timer's "Node"
tab and clicking disconnect.
.. image:: img/signals_disconnect_timer.png
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:
Your complete Sprite.gd code should look like the following.
.. tabs::
.. code-tab:: gdscript GDScript
extends Node2D
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.
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).
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.
.. image:: img/signals_15_scene_tree.png
Click the script icon next to Sprite to jump back to the scripting workspace.
.. 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 Sprite
#...
func _ready():
$Timer.connect("timeout", self, "_on_Timer_timeout")
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")``.
.. 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():
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
func _on_Timer_timeout():
$Sprite.visible = !$Sprite.visible
visible = not visible
.. code-tab:: csharp
public class TimerExample : Node2D
{
public override void _Ready()
{
GetNode("Timer").Connect("timeout", this, nameof(_on_Timer_timeout));
}
public void _on_Timer_timeout()
{
var sprite = GetNode<Sprite>("Sprite");
sprite.Visible = !sprite.Visible;
}
}
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.
Custom signals
--------------
You can also declare your own custom signals in Godot:
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 Node2D
signal health_depleted
signal my_signal
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 : Node2D
{
[Signal]
public delegate void MySignal();
}
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.
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.
.. image:: img/signals_17_custom_signal.png
To emit a signal via code, use the ``emit_signal`` function:
To emit a signal in your scripts, call ``emit_signal()``.
.. tabs::
.. code-tab:: gdscript GDScript
extends Node2D
func take_damage(amount):
health -= amount
if health <= 0:
emit_signal("health_depleted")
signal my_signal
func _ready():
emit_signal("my_signal")
.. code-tab:: csharp
public class Main : Node2D
{
[Signal]
public delegate void MySignal();
public override void _Ready()
{
EmitSignal(nameof(MySignal));
}
}
A signal can also optionally declare one or more arguments. Specify the
argument names between parentheses:
A signal can optionally declare one or more arguments. Specify the argument
names between parentheses:
.. tabs::
.. code-tab:: gdscript GDScript
extends Node
signal my_signal(value, other_value)
.. code-tab:: csharp
public class Main : Node
{
[Signal]
public delegate void MySignal(bool value, int other_value);
}
signal health_changed(old_value, new_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.
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 pass values, add them as the second argument to the ``emit_signal`` function:
To emit values along with the signal, add them as extra arguments to the
``emit_signal()`` function:
.. tabs::
.. code-tab:: gdscript GDScript
extends Node
func take_damage(amount):
var old_health = health
health -= amount
emit_signal("health_changed", old_health, health)
Summary
-------
signal my_signal(value, other_value)
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.
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.
func _ready():
emit_signal("my_signal", true, 42)
.. code-tab:: csharp
public class Main : Node
{
[Signal]
public delegate void MySignal(bool value, int other_value);
public override void _Ready()
{
EmitSignal(nameof(MySignal), true, 42);
}
}
Conclusion
----------
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.