Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Utilizzo dei segnali
In questa lezione, studieremo i segnali. Sono messaggi che i nodi emettono quando accade loro qualcosa di specifico, come la pressione di un pulsante. Altri nodi possono connettersi a quel segnale e richiamare una funzione quando si verifica l'evento.
I segnali sono un meccanismo di delegazione integrato in Godot che consente a un oggetto di gioco di reagire a un cambiamento in un altro senza che i due oggetti si referenziano fra di loro. L'utilizzo dei segnali limita l'accoppiamento e mantiene il codice flessibile.
Ad esempio, potresti avere una barra della vita sullo schermo che rappresenta la salute del giocatore. Quando il giocatore subisce danni o usa una pozione di vita, vuoi che la barra rifletta il cambiamento. Per fare ciò, in Godot, si utilizzano i segnali.
Come i metodi (Callable), i segnali sono un tipo di prima classe a partire da Godot 4.0. Questo significa che è possibile passarli direttamente come argomenti di metodo senza doverli passare come stringhe, il che migliora il completamento automatico ed è meno soggetto a errori. Consulta il riferimento alla classe Signal per una lista di ciò che si può fare direttamente con il tipo Signal.
Vedi anche
Come accennato nell'introduzione, i segnali sono la versione di Godot dell'observer pattern. Puoi saperne di più in Game Programming Patterns.
Ora utilizzeremo un segnale per far muovere e fermare l'icona di Godot della lezione precedente (Ascoltare l'input del giocatore) premendo un pulsante.
Nota
Per questo progetto, seguiremo le convenzioni di denominazione di Godot.
GDScript: Le classi (nodi) usano PascalCase, le variabili e le funzioni snake_case e le costanti usano ALL_CAPS (Vedi Guida di stile di GDScript).
C#: Le classi, le variabili di esportazione e i metodi usano PascalCase, i campi privati usano _camelCase, le variabili locali e i parametri usano camelCase (Consultare Guida di stile C#). Bisogna fare attenzione a scrivere il nome dei metodi correttamente quando si connettono i segnali.
Impostazione della scena
Per aggiungere un pulsante al nostro gioco, creeremo una nuova scena che includerà sia un Button sia la scena sprite_2d.tscn che abbiamo creato nella lezione Creare il tuo primo script.
Crea una nuova scena andando al menu .
Nel pannello Scena, clicca sul pulsante . Questo aggiungerà un Node2D come radice.
Nel pannello FileSystem, clicca e trascina il file sprite_2d.tscn salvato in precedenza sul Node2D per crearne un'istanza.
Vogliamo aggiungere un altro nodo come fratello dello Sprite2D. Per farlo, facciamo clic destro sul Node2D e selezioniamo .
Cerca il nodo Button e aggiungilo.
Il nodo è piccolo normalmente. Clicca e trascina l'handle inferiore destro del pulsante nella viewport per ridimensionarlo.
Se non vedi gli handle, assicurati che lo strumento di selezione sia attivo nella barra degli strumenti.
Clicca e trascina il pulsante stesso per spostarlo più vicino allo sprite.
Puoi anche scrivere un'etichetta sul pulsante modificandone la proprietà Text nell'Inspector Inserisci Toggle motion.
L'albero della scena e la viewport dovrebbero apparire così.
Salva la scena appena creata come node_2d.tscn, se non l'hai già fatto. Puoi quindi eseguirla con F6 (Cmd + R su macOS). Al momento, il pulsante sarà visibile, ma nulla accadrà se viene premuto.
Collegare segnali nell'editor
Qui, vogliamo collegare il segnale "pressed" del pulsante al nostro Sprite2D e vogliamo chiamare una nuova funzione che ne attivi e disattivi il movimento. Dobbiamo avere uno script collegato al nodo Sprite2D, come abbiamo fatto nella lezione precedente.
È possibile collegare i segnali nel pannello Signals. Seleziona il nodo Button e, sul lato destro dell'editor, clicca sulla scheda chiamata Signals accanto all'Inspector.
Il pannello visualizza un elenco dei segnali disponibili nel nodo selezionato.
Fai doppio clic sul segnale "pressed" per aprire la finestra di connessione del nodo.
Da qui, puoi collegare il segnale al nodo Sprite2D. Il nodo ha bisogno di un metodo ricevitore, una funzione che Godot chiamerà quando il pulsante emette il segnale. L'editor ne genera uno automaticamente. Per convenzione, chiamiamo questi metodi di callback "_on_node_name_signal_name". Qui, sarà "_on_button_pressed".
Nota
Quando si collegano i segnali tramite il pannello Segnali dell'editor, è possibile utilizzare due modalità. Quella semplice consente solo di connettersi ai nodi a cui è allegato uno script e crea una nuova funzione di callback su di essi.
La vista avanzata consente di connettersi a qualsiasi nodo e a qualsiasi funzione integrata, aggiungere argomenti al callback e impostare certe opzioni. È possibile attivare la modalità cliccando sul pulsante in basso a destra alla finestra.
Nota
Se si utilizza un editor esterno (come VS Code), la generazione automatica del codice potrebbe non funzionare. In tal caso, è necessario collegare il segnale tramite codice, come spiegato nella sezione successiva.
Clicca sul pulsante per completare la connessione del segnale e passare allo spazio di lavoro Script. Dovresti vedere il nuovo metodo con un'icona di connessione sul margine sinistro.
Cliccando sull'icona, si apre una finestra con le informazioni sulla connessione. Questa funzione è disponibile solo quando si collegano nodi nell'editor.
Sostituiamo la riga con la parola chiave pass con il codice che attiverà e disattiverà il movimento del nodo.
Il nostro Sprite2D si muove grazie al codice nella funzione _process(). Godot fornisce un metodo per attivare o disattivare l'elaborazione: Node.set_process(). Un altro metodo della classe Node, is_processing(), restituisce true se l'elaborazione "idle" è attiva. Possiamo usare la parola chiave not per invertire il valore.
func _on_button_pressed():
set_process(not is_processing())
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
Questa funzione alterna l'elaborazione e, di conseguenza, il movimento dell'icona quando il pulsante viene premuto.
Prima di provare il gioco, dobbiamo semplificare la nostra funzione _process() per muovere il nodo automaticamente, senza attendere l'input dell'utente. Sostituiscila con il seguente codice, che abbiamo visto due lezioni fa:
func _process(delta):
rotation += angular_speed * delta
var velocity = Vector2.UP.rotated(rotation) * speed
position += velocity * delta
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
Il tuo codice completo sprite_2d.gd dovrebbe essere simile al seguente.
extends Sprite2D
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())
using Godot;
public partial class MySprite2D : Sprite2D
{
private float _speed = 400;
private float _angularSpeed = Mathf.Pi;
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
}
Esegui la scena attuale premendo F6 (Cmd + R su macOS) e clicca sul pulsante per vedere lo sprite partire e fermarsi.
Collegare un segnale tramite codice
È possibile collegare i segnali tramite codice anziché tramite l'editor. Questo è necessario quando si creano nodi o si istanziano scene all'interno di uno script.
Usiamo un nodo diverso qui. Godot ha un nodo Timer utile per implementare i tempi di recupero delle abilità, la ricarica delle armi e altro.
Torna allo spazio di lavoro 2D. Puoi cliccare sul testo "2D" nella parte superiore della finestra o premere Ctrl + F1 (Ctrl + Cmd + 1 su macOS).
Nel pannello Scena, fai clic destro sul nodo Sprite2D e aggiungi un nuovo nodo figlio. Cerca Timer e aggiungi il nodo corrispondente. La scena dovrebbe ora apparire così.
Con il nodo Timer selezionato, vai all'Inspector e abilita la proprietà Autostart.
Clicca sull'icona dello script accanto a Sprite2D per tornare allo spazio di lavoro Script.
Dobbiamo effettuare due operazioni per connettere i nodi tramite codice:
Ottenere un riferimento al Timer da Sprite2D.
Chiamare il metodo
connect()sul segnale "timeout" del Timer.
Nota
Per connettersi a un segnale tramite codice, è necessario chiamare il metodo connect() del segnale che si desidera ascoltare. In questo caso, vogliamo ascoltare il segnale di "timeout" del Timer.
Vogliamo connettere il segnale quando la scena viene istanziata, possiamo farlo attraverso la funzione integrata Node._ready(), che viene chiamata automaticamente dal motore quando un nodo è completamente istanziato.
Per ottenere un riferimento a un nodo relativo a quello attuale, utilizziamo il metodo Node.get_node(). Possiamo memorizzare il riferimento in una variabile.
func _ready():
var timer = get_node("Timer")
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
}
La funzione get_node() esamina i nodi figli dello Sprite2D e li ottiene per il loro nome. Ad esempio, se nell'editor hai rinominato il nodo Timer in "BlinkingTimer", dovresti modificare la chiamata in get_node("BlinkingTimer").
Ora possiamo collegare il Timer allo Sprite2D nella funzione _ready().
func _ready():
var timer = get_node("Timer")
timer.timeout.connect(_on_timer_timeout)
public override void _Ready()
{
var timer = GetNode<Timer>("Timer");
timer.Timeout += OnTimerTimeout;
}
La riga è interpreta così: colleghiamo il segnale "timeout" del Timer al nodo a cui è collegato lo script. Quando il Timer emette timeout, vogliamo chiamare la funzione _on_timer_timeout(), che dobbiamo definire. Aggiungiamola in fondo al nostro script e usiamola per alternare la visibilità del nostro sprite.
Nota
Per convenzione, in GDScript chiamiamo questi metodi di callback "_on_node_name_signal_name" e in C# "OnNodeNameSignalName". In questo caso, saranno "_on_timer_timeout" per GDScript e OnTimerTimeout() per C#.
func _on_timer_timeout():
visible = not visible
private void OnTimerTimeout()
{
Visible = !Visible;
}
La proprietà visible è un valore booleano che controlla la visibilità del nostro nodo. La riga visible = not visible ne inverte il valore. Se visible è true, diventa false, e viceversa.
Se ora esegui la scena Node2D, vedrai che lo sprite si accende e spegne, a intervalli di un secondo.
Script completo
Questo è tutto per la nostra iconcina di Godot che si muove e lampeggia! Ecco il file sprite_2d.gd completo come riferimento.
extends Sprite2D
var speed = 400
var angular_speed = PI
func _ready():
var timer = get_node("Timer")
timer.timeout.connect(_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
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.Timeout += OnTimerTimeout;
}
public override void _Process(double delta)
{
Rotation += _angularSpeed * (float)delta;
var velocity = Vector2.Up.Rotated(Rotation) * _speed;
Position += velocity * (float)delta;
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnButtonPressed()
{
SetProcess(!IsProcessing());
}
private void OnTimerTimeout()
{
Visible = !Visible;
}
}
Segnali personalizzati
Nota
Questa sezione è un riferimento su come definire e utilizzare i propri segnali e non continua dal progetto creato nelle lezioni precedenti.
È possibile definire segnali personalizzati in uno script. Supponiamo, ad esempio, che tu voglia visualizzare una schermata di fine partita quando la salute del giocatore raggiunge zero. Per farlo, potresti definire un segnale chiamato "died" o "health_depleted" quando la salute del giocatore raggiunge zero.
extends Node2D
signal health_depleted
var health = 10
using Godot;
public partial class MyNode2D : Node2D
{
[Signal]
public delegate void HealthDepletedEventHandler();
private int _health = 10;
}
Nota
Poiché i segnali rappresentano eventi appena accaduti, in genere nei loro nomi utilizziamo un verbo d'azione al passato (past tense).
Questi segnali funzionano allo stesso modo dei segnali integrati: compaiono nella scheda Signals e ci si può connettere a essi come a qualsiasi altro.
Per emettere un segnale nei tuoi script, chiama emit() sul segnale.
func take_damage(amount):
health -= amount
if health <= 0:
health_depleted.emit()
public void TakeDamage(int amount)
{
_health -= amount;
if (_health <= 0)
{
EmitSignal(SignalName.HealthDepleted);
}
}
Un segnale può anche facoltativamente dichiarare uno o più argomenti. Specifica i nomi degli argomenti tra parentesi:
extends Node2D
signal health_changed(old_value, new_value)
var health = 10
using Godot;
public partial class MyNode : Node
{
[Signal]
public delegate void HealthChangedEventHandler(int oldValue, int newValue);
private int _health = 10;
}
Nota
Gli argomenti del segnale appaiono nel pannello Segnali dell'editor, e Godot li può utilizzare per generare funzioni di callback. Tuttavia, è comunque possibile emettere un numero qualsiasi di argomenti quando si emettono segnali, quindi spetta a te fornire i valori corretti.
Per emettere i valori assieme al segnale, aggiungili come secondo argomento alla funzione emit():
func take_damage(amount):
var old_health = health
health -= amount
health_changed.emit(old_health, health)
public void TakeDamage(int amount)
{
int oldHealth = _health;
_health -= amount;
EmitSignal(SignalName.HealthChanged, oldHealth, _health);
}
Riepilogo
Ogni nodo in Godot emette segnali quando vi accade qualcosa di specifico, come la pressione di un pulsante. Altri nodi possono connettersi a singoli segnali e reagire a eventi selezionati.
I segnali hanno molti usi. Con essi, puoi reagire a un nodo che entra o esce dal mondo di gioco, a una collisione, a un personaggio che entra o esce da un'area, a un elemento dell'interfaccia che cambia dimensione e molto altro.
Pesempio, un Area2D che rappresenta una moneta emette un segnale body_entered quando il corpo del giocatore entra nella sua forma di collisione, facendoti sapere quando il giocatore l'ha raccolta.
Nella prossima sezione, Il tuo primo gioco 2D, creerai un gioco 2D completo e metterai in pratica tutto ciò che hai imparato finora.