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...
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.
信号作为 C# 事件¶
为了提供更多的类型安全,Godot 信号也都可以通过 事件 获取。你可以用 += 和 -= 运算符来处理这些事件,就像其他任何事件一样。
Timer myTimer = GetNode<Timer>("Timer");
myTimer.Timeout += () => GD.Print("Timeout!");
此外,你可以通过节点类型的嵌套 SignalName 类来访问与之相关的信号名称。这在你想要等待一个信号时很有用,例如(参见 await 关键字 )。
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
自定义信号作为 C# 事件¶
要在你的 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.
因此,任何 Node 或 RefCounted 都会自动兼容,但自定义数据对象需要继承自 GodotObject 或其子类之一。
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));
}