Up to date
This page is up to date for Godot 4.3.
If you still find outdated information, please open an issue.
C# 訊號
完整的 C# 例子請參考按部就班 為場景編寫腳本 教學中的 處理訊號 一節。
Signals are implemented using C# events, the idiomatic way to represent the observer pattern in C#. This is the recommended way to use signals in C# and the focus of this page.
In some cases it's necessary to use the older Connect() and Disconnect() APIs. See Using Connect and Disconnect for more details.
If you encounter a System.ObjectDisposedException while handling a signal,
you might be missing a signal disconnection. See
Disconnecting automatically when the receiver is freed for more details.
訊號與屬性
為了提供更多的型別安全性,Godot 訊號也可以透過「事件 <https://learn.microsoft.com/en-us/dotnet/csharp/events-overview>」_ 取得。您可以像處理任何其他事件一樣使用“+=”和“-=”運算符來處理這些事件。
Timer myTimer = GetNode<Timer>("Timer");
myTimer.Timeout += () => GD.Print("Timeout!");
此外,您始終可以透過其巢狀的「SignalName」類別存取與節點型別關聯的訊號名稱。例如,當您想要等待訊號時,這很有用(請參閱:ref:doc_c_sharp_differences_await)。
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
自定訊號
若要在 C# 腳本中宣告自訂事件,請在公共委派型別上使用「[Signal]」屬性。請注意,該委派的名稱需要以“EventHandler”結尾。
[Signal]
public delegate void MySignalEventHandler();
[Signal]
public delegate void MySignalWithArgumentEventHandler(string myString);
一旦完成,Godot 將在幕後自動建立適當的事件。然後,您可以像處理任何其他 Godot 訊號一樣使用所述事件。請注意,事件是使用委派名稱減去最後的“EventHandler”部分來命名的。
public override void _Ready()
{
MySignal += () => GD.Print("Hello!");
MySignalWithArgument += SayHelloTo;
}
private void SayHelloTo(string name)
{
GD.Print($"Hello {name}!");
}
警告
如果您想在編輯器中連接到這些訊號,您將需要(重新)建置專案才能看到它們出現。
你可以使用右上角的按鈕來。
訊號
若要發出訊號,請使用“EmitSignal”方法。請注意,對於引擎定義的訊號,您的自訂訊號名稱會列在巢狀的“SignalName”類別下。
public void MyMethodEmittingSignals()
{
EmitSignal(SignalName.MySignal);
EmitSignal(SignalName.MySignalWithArgument, "World");
}
與其他 C# 事件相比,您不能使用「Invoke」來引發與 Godot 訊號相關的事件。
Signals support arguments of any Variant-compatible type.
Consequently, any Node or RefCounted will be compatible automatically, but custom data objects will need
to inherit from GodotObject or one of its subclasses.
using Godot;
public partial class DataObject : GodotObject
{
public string MyFirstString { get; set; }
public string MySecondString { get; set; }
}
列舉值
有時,您需要在建立連線時將值綁定到訊號,而不是在發出訊號時(或除此之外)。為此,您可以使用匿名函式,如下例所示。
Here, the Button.Pressed signal does not take any argument. But we
want to use the same ModifyValue for both the "plus" and "minus" buttons. So we bind the
modifier value at the time we're connecting the signals.
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;
}
運作時建立訊號
最後,您可以在遊戲運作時直接建立自訂訊號。為此,請使用“AddUserSignal”方法。請注意,應在使用所述訊號(連接到它們或發出它們)之前執行它。另請注意,以這種方式建立的訊號將不會透過「SignalName」巢狀類別可見。
public override void _Ready()
{
AddUserSignal("MyCustomSignal");
EmitSignal("MyCustomSignal");
}
Using Connect and Disconnect
In general, it isn't recommended to use Connect() and Disconnect(). These APIs don't provide as much type safety as the events. However, they're necessary for connecting to signals defined by GDScript and passing ConnectFlags.
In the following example, pressing the button for the first time prints
Greetings!. OneShot disconnects the signal, so pressing the button again
does nothing.
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!");
}
Disconnecting automatically when the receiver is freed
Normally, when any GodotObject is freed (such as any Node), Godot
automatically disconnects all connections associated with that object. This
happens for both signal emitters and signal receivers.
For example, a node with this code will print "Hello!" when the button is pressed, then free itself. Freeing the node disconnects the signal, so pressing the button again doesn't do anything:
public override void _Ready()
{
Button myButton = GetNode<Button>("../MyButton");
myButton.Pressed += SayHello;
}
private void SayHello()
{
GD.Print("Hello!");
Free();
}
When a signal receiver is freed while the signal emitter is still alive, in some cases automatic disconnection won't happen:
The signal is connected to a lambda expression that captures a variable.
The signal is a custom signal.
The following sections explain these cases in more detail and include suggestions for how to disconnect manually.
備註
Automatic disconnection is totally reliable if a signal emitter is freed before any of its receivers are freed. With a project style that prefers this pattern, the above limits may not be a concern.
No automatic disconnection: a lambda expression that captures a variable
If you connect to a lambda expression that captures variables, Godot can't tell that the lambda is associated with the instance that created it. This causes this example to have potentially unexpected behavior:
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.
On tick 4, the lambda expression tries to access the Name property of the
node, but the node has already been freed. This causes the exception.
To disconnect, keep a reference to the delegate created by the lambda expression
and pass that to -=. For example, this node connects and disconnects using
the _EnterTree and _ExitTree lifecycle methods:
[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 this example, Free causes the node to leave the tree, which calls
_ExitTree. _ExitTree disconnects the signal, so _tick is never
called again.
The lifecycle methods to use depend on what the node does. Another option is to
connect to signals in _Ready and disconnect in Dispose.
備註
Godot uses Delegate.Target
to determine what instance a delegate is associated with. When a lambda
expression doesn't capture a variable, the generated delegate's Target
is the instance that created the delegate. When a variable is captured, the
Target instead points at a generated type that stores the captured
variable. This is what breaks the association. If you want to see if a
delegate will be automatically cleaned up, try checking its Target.
Callable.From doesn't affect the Delegate.Target, so connecting a
lambda that captures variables using Connect doesn't work any better
than +=.
No automatic disconnection: a custom signal
Connecting to a custom signal using += doesn't disconnect automatically when
the receiving node is freed.
To disconnect, use -= at an appropriate time. For example:
[Export]
public MyClass Target { get; set; }
public override void _EnterTree()
{
Target.MySignal += OnMySignal;
}
public override void _ExitTree()
{
Target.MySignal -= OnMySignal;
}
Another solution is to use Connect, which does disconnect automatically with
custom signals:
[Export]
public MyClass Target { get; set; }
public override void _EnterTree()
{
Target.Connect(MyClass.SignalName.MySignal, Callable.From(OnMySignal));
}