virtualx-engine-docs/_sources/getting_started/step_by_step/signals.rst.txt
2024-10-23 09:41:33 -07:00

529 lines
15 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

.. 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:
Using signals
=============
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 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.
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.
.. 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
We will now use a signal to make our Godot icon from the previous lesson
(:ref:`doc_scripting_player_input`) move and stop by pressing a button.
.. Example
Scene setup
-----------
To add a button to our game, we will create a new "main" scene which will
include both a button and the ``Sprite.tscn`` scene that we scripted in previous
lessons.
Create a new scene by going to the menu Scene -> New Scene.
.. image:: img/signals_01_new_scene.png
In the Scene dock, click the 2D Scene button. This will add a Node2D as our
root.
.. image:: img/signals_02_2d_scene.png
In the FileSystem dock, click and drag the ``Sprite.tscn`` file you saved
previously onto the Node2D to instantiate it.
.. image:: img/signals_03_dragging_scene.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.
.. image:: img/signals_04_add_child_node.png
Search for the Button node type and add it.
.. image:: img/signals_05_add_button.png
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. Enter "Toggle motion".
.. image:: img/signals_08_toggle_motion_text.png
Your scene tree and viewport should look like this.
.. image:: img/signals_09_scene_setup.png
Save your newly created scene. You can then run it with :kbd:`F6`.
At the moment, the button will be visible, but nothing will happen if you
press it.
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 Advanced
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
func _on_Button_pressed():
set_process(not is_processing())
.. code-tab:: csharp C#
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
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
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
.. code-tab:: csharp C#
public override void _Process(float delta)
{
Rotation += _angularSpeed * 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):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
func _on_Button_pressed():
set_process(not is_processing())
.. code-tab:: csharp C#
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Process(float delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
}
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:`Ctrl + F1` (:kbd:`Alt + 1` on macOS).
In the Scene dock, right-click on the Sprite node and add a new child node.
Search for Timer and add the corresponding node. Your scene should now look like
this.
.. image:: img/signals_15_scene_tree.png
With the Timer node selected, go to the Inspector and check the **Autostart**
property.
.. image:: img/signals_18_timer_autostart.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.
We want to connect the signal when the scene is instantiated, and we can do that
using the :ref:`Node._ready() <class_Node_method__ready>` built-in function,
which is called automatically by the engine when a node is fully instantiated.
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
func _ready():
var timer = get_node("Timer")
.. code-tab:: csharp C#
public override void _Ready()
{
var timer = GetNode<Timer>("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")
.. code-tab:: csharp C#
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Connect("timeout", this, nameof(OnTimerTimeout));
}
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():
visible = not visible
.. code-tab:: csharp C#
private void OnTimerTimeout()
{
Visible = !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.
If you run the scene now, you will see that the sprite blinks on and off, at one
second intervals.
Complete script
---------------
That's it for our little moving and blinking Godot icon demo!
Here is the complete ``Sprite.gd`` file for reference.
.. tabs::
.. code-tab:: gdscript GDScript
extends Sprite
var speed = 400
var angular_speed = PI
func _ready():
var timer = get_node("Timer")
timer.connect("timeout", self, "_on_Timer_timeout")
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
func _on_Button_pressed():
set_process(not is_processing())
func _on_Timer_timeout():
visible = not visible
.. code-tab:: csharp C#
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Connect("timeout", this, nameof(OnTimerTimeout));
}
public override void _Process(float delta)
{
Rotation += _angularSpeed * delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * delta;
}
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
private void OnTimerTimeout()
{
Visible = !Visible;
}
}
Custom signals
--------------
.. note:: This section is a reference on how to define and use your own signals,
and does not build upon the project created in previous lessons.
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
var health = 10
.. code-tab:: csharp C#
using Godot;
public partial class MyNode2D : Node2D
{
[Signal]
public delegate void HealthDepleted();
private int _health = 10;
}
.. note:: As signals represent events that just occurred, we generally use an
action verb in the past tense in their names.
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.
.. image:: img/signals_17_custom_signal.png
To emit a signal in your scripts, call ``emit_signal()``.
.. tabs::
.. code-tab:: gdscript GDScript
func take_damage(amount):
health -= amount
if health <= 0:
emit_signal("health_depleted")
.. code-tab:: csharp C#
public void TakeDamage(int amount)
{
_health -= amount;
if (_health <= 0)
{
EmitSignal(nameof(HealthDepleted));
}
}
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)
.. code-tab:: csharp C#
using Godot;
public partial class MyNode : Node
{
[Signal]
public delegate void HealthChanged(int oldValue, int newValue);
private int _health = 10;
}
.. 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.
To emit values along with the signal, add them as extra arguments to the
``emit_signal()`` function:
.. tabs::
.. code-tab:: gdscript GDScript
func take_damage(amount):
var old_health = health
health -= amount
emit_signal("health_changed", old_health, health)
.. code-tab:: csharp C#
public void TakeDamage(int amount)
{
int oldHealth = _health;
_health -= amount;
EmitSignal(nameof(HealthChanged), oldHealth, _health);
}
Summary
-------
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.
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_2d_game`, you'll create a complete 2D
game and put everything you learned so far into practice.