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...
Segnali in C#
Per una spiegazione dettagliata dei segnali in generale, consultare la sezione Utilizzo dei segnali nel tutorial passo per passo.
I segnali sono implementati attraverso gli eventi C#, il modo idiomatico per rappresentare l'observer pattern in C#. Questo è il modo consigliato per utilizzare i segnali in C# ed è l'obiettivo di questa pagina.
In alcuni casi è necessario utilizzare le vecchie API Connect() e Disconnect(). Consultare Utilizzo di Connect e Disconnect per maggiori dettagli.
Se viene generata un'eccezione System.ObjectDisposedException durante la gestione di un segnale, potrebbe mancare una disconnessione del segnale. Consultare Disconnessione automatica quando il ricevitore viene liberato per maggiori dettagli.
Segnali come eventi C#
Per garantire una maggiore sicurezza di tipo, i segnali Godot sono disponibili anche tramite eventi. È possibile gestire questi eventi, come qualsiasi altro evento, con gli operatori += e -=.
Timer myTimer = GetNode<Timer>("Timer");
myTimer.Timeout += () => GD.Print("Timeout!");
Inoltre, è sempre possibile accedere ai nomi dei segnali associati a un tipo di nodo tramite la classe annidata SignalName. Questa è utile quando, ad esempio, si desidera attendere un segnale (consultare Parola chiave await).
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
Segnali personalizzati come eventi C#
Per dichiarare un evento personalizzato nel proprio script C#, usare l'attributo [Signal] su un tipo delegato pubblico. Si noti che il nome di questo delegato deve terminare con EventHandler.
[Signal]
public delegate void MySignalEventHandler();
[Signal]
public delegate void MySignalWithArgumentEventHandler(string myString);
Una volta fatto ciò, Godot creerà automaticamente gli eventi appropriati dietro le quinte. Si potrà quindi utilizzare tali eventi come si farebbe per qualsiasi altro segnale Godot. Si noti che gli eventi sono denominati attraverso il nome del delegato meno la parte EventHandler finale.
public override void _Ready()
{
MySignal += () => GD.Print("Hello!");
MySignalWithArgument += SayHelloTo;
}
private void SayHelloTo(string name)
{
GD.Print($"Hello {name}!");
}
Avvertimento
Se si desidera connettersi a questi segnali nell'editor, sarà necessario (ri)compilare il progetto per farli apparire.
Si può cliccare sul pulsante Crea nell'angolo in alto a destra dell'editor per farlo.
Emissione di segnali
Per emettere segnali, utilizzare il metodo EmitSignal. Si noti che, come per i segnali definiti dal motore, i nomi dei segnali personalizzati sono elencati nella classe annidata SignalName.
public void MyMethodEmittingSignals()
{
EmitSignal(SignalName.MySignal);
EmitSignal(SignalName.MySignalWithArgument, "World");
}
A differenza di altri eventi C#, non è possibile utilizzare Invoke per generare eventi associati ai segnali Godot.
I segnali supportano argomenti di qualsiasi tipo compatibile con Variant.
Pertanto, qualsiasi Node o RefCounted sarà automaticamente compatibile, ma gli oggetti dati personalizzati dovranno ereditare da GodotObject o da una delle sue sottoclassi.
using Godot;
public partial class DataObject : GodotObject
{
public string MyFirstString { get; set; }
public string MySecondString { get; set; }
}
Valori vincolati
A volte può essere necessario associare dei valori a un segnale quando viene stabilita la connessione, anziché (o in aggiunta a) quando il segnale viene emesso. Per farlo, è possibile utilizzare una funzione anonima come nell'esempio seguente.
In questo caso, il segnale Button.Pressed non accetta argomenti. Tuttavia, vogliamo utilizzare lo stesso ModifyValue per entrambi i pulsanti "plus" e "minus". Quindi, associamo il valore modificatore al momento che connettiamo i segnali.
public int Value { get; private set; } = 1;
public override void _Ready()
{
Button plusButton = GetNode<Button>("PlusButton");
plusButton.Pressed += () => ModifyValue(1);
Button minusButton = GetNode<Button>("MinusButton");
minusButton.Pressed += () => ModifyValue(-1);
}
private void ModifyValue(int modifier)
{
Value += modifier;
}
Creazione di segnali durante l'esecuzione
Infine, è possibile creare segnali personalizzati direttamente durante l'esecuzione del gioco. Utilizzare il metodo AddUserSignal. Tenere presente che si deve chiamare prima di qualsiasi utilizzo di tali segnali (sia che ci si connetta ad essi, sia che si emettano). Inoltre, si noti che i segnali creati in questo modo non saranno visibili attraverso la classe annidata SignalName.
public override void _Ready()
{
AddUserSignal("MyCustomSignal");
EmitSignal("MyCustomSignal");
}
Utilizzo di Connect e Disconnect
In generale, non si consiglia utilizzare Connect() e Disconnect(). Queste API non garantiscono la stessa sicurezza di tipo degli eventi. Tuttavia, sono necessarie per connettersi ai segnali definiti da GDScript e passare ConnectFlags.
Nell'esempio seguente, premendo il pulsante per la prima volta viene visualizzato Greetings!. OneShot disconnette il segnale, quindi premendo nuovamente il pulsante non succede nulla.
public override void _Ready()
{
Button button = GetNode<Button>("GreetButton");
button.Connect(Button.SignalName.Pressed, Callable.From(OnButtonPressed), (uint)GodotObject.ConnectFlags.OneShot);
}
public void OnButtonPressed()
{
GD.Print("Greetings!");
}
Disconnessione automatica quando il ricevitore viene liberato
Normalmente, quando un GodotObject viene liberato (ad esempio un Node), Godot disconnette automaticamente tutte le connessioni associate a quell'oggetto. Questo accade sia per gli emettitori sia per i ricevitori dei segnali.
Ad esempio, un nodo con questo codice stamperà "Hello!" quando il pulsante viene premuto, per poi liberarsi. Liberare il nodo disconnette il segnale, quindi premere nuovamente il pulsante non succede nulla:
public override void _Ready()
{
Button myButton = GetNode<Button>("../MyButton");
myButton.Pressed += SayHello;
}
private void SayHello()
{
GD.Print("Hello!");
Free();
}
Quando un ricevitore di un segnale viene liberato mentre l'emettitore del segnale è ancora attivo, in alcuni casi la disconnessione automatica non avviene:
Il segnale è collegato a un'espressione lambda che cattura una variabile.
Il segnale è un segnale personalizzato.
Nelle sezioni seguenti sono spiegati questi casi in modo più dettagliato e sono forniti suggerimenti su come disconnetterli manualmente.
Nota
La disconnessione automatica è totalmente affidabile se un emettitore di segnale viene liberato prima che lo siano tutti i suoi ricevitori. Con uno stile di progetto che preferisce questo schema, i limiti menzionati in precedenza potrebbero non essere un problema.
Nessuna disconnessione automatica: un'espressione lambda che cattura una variabile
Se ci si connette a un'espressione lambda che cattura variabili, Godot non può stabilire se la lambda è associata all'istanza che l'ha creata. Ciò significa che questo esempio potrebbe avere un comportamento inaspettato:
Timer myTimer = GetNode<Timer>("../Timer");
int x = 0;
myTimer.Timeout += () =>
{
x++; // This lambda expression captures x.
GD.Print($"Tick {x} my name is {Name}");
if (x == 3)
{
GD.Print("Time's up!");
Free();
}
};
Tick 1, my name is ExampleNode
Tick 2, my name is ExampleNode
Tick 3, my name is ExampleNode
Time's up!
[...] System.ObjectDisposedException: Cannot access a disposed object.
Al tick 4, l'espressione lambda tenta di accedere alla proprietà Name del nodo, ma il nodo è già stato liberato. Questo causa l'eccezione.
Per disconnettere, mantenere un riferimento al delegato creato dall'espressione lambda e passarlo a -=. Ad esempio, questo nodo si connette e si disconnette attraverso i metodi del ciclo di vita _EnterTree e _ExitTree:
[Export]
public Timer MyTimer { get; set; }
private Action _tick;
public override void _EnterTree()
{
int x = 0;
_tick = () =>
{
x++;
GD.Print($"Tick {x} my name is {Name}");
if (x == 3)
{
GD.Print("Time's up!");
Free();
}
};
MyTimer.Timeout += _tick;
}
public override void _ExitTree()
{
MyTimer.Timeout -= _tick;
}
In questo esempio, Free assicura che il nodo esca dall'albero, il che richiama _ExitTree. _ExitTree disconnette il segnale, quindi _tick non viene più richiamato.
I metodi del ciclo di vita da utilizzare dipendono dalle attività del nodo. Un'altra opzione è connettersi ai segnali in _Ready e disconnettersi in Dispose.
Nota
Godot utilizza Delegate.Target per determinare a quale istanza è associato un delegato. Quando un'espressione lambda non cattura una variabile, il Target del delegato generato è l'istanza che ha creato il delegato. Quando una variabile viene catturata, il Target punta invece a un tipo generato che memorizza la variabile catturata. Questo è ciò che interrompe l'associazione. Per confermare se un delegato sarà ripulito automaticamente, si potrebbe verificare il suo Target.
Callable.From non influisce su Delegate.Target, quindi connettere una lambda che cattura variabili tramite Connect non funziona meglio di +=.
Nessuna disconnessione automatica: un segnale personalizzato
La connessione a un segnale personalizzato tramite += non si disconnette automaticamente quando il nodo ricevente viene liberato.
Per disconnettere, utilizzare -= al momento opportuno. Ad esempio:
[Export]
public MyClass Target { get; set; }
public override void _EnterTree()
{
Target.MySignal += OnMySignal;
}
public override void _ExitTree()
{
Target.MySignal -= OnMySignal;
}
Un'altra soluzione è quella di utilizzare Connect, il quale si disconnette automaticamente con i segnali personalizzati:
[Export]
public MyClass Target { get; set; }
public override void _EnterTree()
{
Target.Connect(MyClass.SignalName.MySignal, Callable.From(OnMySignal));
}