信号

简介

信号是Godot的 观察者 模式的版本。它们允许一个节点发出其他节点可以监听和响应的消息。例如,与其持续检查按钮是否被按下,不如在按下按钮时发出信号。

注解

You can read more about the observer pattern here: https://gameprogrammingpatterns.com/observer.html

信号是一种使游戏对象 解耦 的方法,从而可以使代码组织得更好,更易于管理。游戏对象可以发出所有感兴趣的对象可以订阅并响应的信号,而非强制游戏对象期望其他对象始终存在。

接下来,您可以看到一些有关如何在自己的项目中使用信号的示例。

计时器示例

要查看信号如何工作,让我们尝试使用一个 Timer 节点。使用一个 Node 和两个子节点:一个 Timer 和一个 Sprite,来创建一个新场景。在场景停靠面板中,重命名 NodeTimerExample

对于 Sprite 的纹理,可以使用Godot图标,或您喜欢的任何其他图像。为此,请在 SpriteTexture 属性下拉菜单中选择 Load。将脚本附加到根节点,但尚未添加任何代码。

您的场景树应该是这样的:

../../_images/signals_node_setup.png

Timer 节点的属性中,勾选 自动启动 旁边的选框。这会令计时器在您运行场景时自动启动。您可以将 等待时间 保留为1秒。

在 “属性检查器” 选项卡旁边是一个标记为 “节点” 的选项卡。单击此选项卡,您将看到所选节点可以发出的所有信号。对于 Timer 节点,我们关注的是“timeout()”。每当计时器到达 0 时,就会发出这个信号。

../../_images/signals_node_tab_timer.png

点击“timeout()”信号,然后点击界面底部的“连接…”按钮。您将看到如下窗口,您可以在其中定义如何连接信号:

../../_images/signals_connect_dialog_timer.png

在左侧,你将看到场景中的节点,并可以选择要“监听”信号的节点。请注意,Timer 节点为蓝色——意思是它是发出信号的节点。选择根节点。

警告

目标节点 必须 附加一个脚本,否则您将收到一条错误消息。

If you toggle the Advanced menu, you’ll see on the right side that you can bind an arbitrary number of arguments of (possibly) different types. This can be useful when you have more than one signal connected to the same method, as each signal propagation will result in different values for those extra call arguments.

窗口底部是一个标有“Receiver Method”的字段。这是您要使用的目标节点脚本中的函数名称。默认情况下,Godot将使用命名约定 _on_<node_name>_<signal_name> 创建此函数,但如果你想要修改它也可以。

单击 连接(Connect),您将看到该函数已在脚本中创建:

GDScript

C#

  1. extends Node2D
  2. func _on_Timer_timeout():
  3. pass # Replace with function body.
  1. public class TimerExample : Node2D
  2. {
  3. public void _on_Timer_timeout()
  4. {
  5. // Replace with function body.
  6. }
  7. }

现在,我们可以用接收信号时要运行的任何代码替换占位符代码。让我们让 Sprite 闪烁一下:

GDScript

C#

  1. extends Node2D
  2. func _on_Timer_timeout():
  3. # Note: the `$` operator is a shorthand for `get_node()`,
  4. # so `$Sprite` is equivalent to `get_node("Sprite")`.
  5. $Sprite.visible = !$Sprite.visible
  1. public class TimerExample : Node2D
  2. {
  3. public void _on_Timer_timeout()
  4. {
  5. var sprite = GetNode<Sprite>("Sprite");
  6. sprite.Visible = !sprite.Visible;
  7. }
  8. }

运行场景,您将看到 Sprite 每秒闪烁一次。您可以更改计时器的 等待时间 属性来更改此设置。

用代码连接信号

您还可以用在代码进行信号连接而不是用编辑器。当您通过代码实例化节点时,通常这是必需的,因为您无法使用编辑器进行连接。

首先,通过在 计时器 的“节点”选项卡中选择连接并点击断开来断开信号。

../../_images/signals_disconnect_timer.png

要使用代码进行连接,我们可以使用 connect 函数。将把它放在 _ready() 函数中,这样连接就会在运行时创建。函数的语法是 <source_node>.connect(<signal_name>, <target_node>, <target_function_name>)。下面是我们的计时器连接的代码:

GDScript

C#

  1. extends Node2D
  2. func _ready():
  3. $Timer.connect("timeout", self, "_on_Timer_timeout")
  4. func _on_Timer_timeout():
  5. $Sprite.visible = !$Sprite.visible
  1. public class TimerExample : Node2D
  2. {
  3. public override void _Ready()
  4. {
  5. GetNode("Timer").Connect("timeout", this, nameof(_on_Timer_timeout));
  6. }
  7. public void _on_Timer_timeout()
  8. {
  9. var sprite = GetNode<Sprite>("Sprite");
  10. sprite.Visible = !sprite.Visible;
  11. }
  12. }

自定义信号

您还可以在Godot中声明自己的自定义信号:

GDScript

C#

  1. extends Node2D
  2. signal my_signal
  1. public class Main : Node2D
  2. {
  3. [Signal]
  4. public delegate void MySignal();
  5. }

声明后,您的自定义信号将出现在检查器中,并且可以按照与节点的内置信号相同的方式进行连接。

要通过代码发出信号,使用 emit_signal 函数:

GDScript

C#

  1. extends Node2D
  2. signal my_signal
  3. func _ready():
  4. emit_signal("my_signal")
  1. public class Main : Node2D
  2. {
  3. [Signal]
  4. public delegate void MySignal();
  5. public override void _Ready()
  6. {
  7. EmitSignal(nameof(MySignal));
  8. }
  9. }

信号还可以选择声明一个或多个参数。在括号之间指定参数名称:

GDScript

C#

  1. extends Node
  2. signal my_signal(value, other_value)
  1. public class Main : Node
  2. {
  3. [Signal]
  4. public delegate void MySignal(bool value, int other_value);
  5. }

注解

这些信号参数显示在编辑器的节点停靠面板中,Godot可以使用它们为您生成回调函数。但是,发出信号时仍然可以发出任意数量的参数;所以由你来决定是否发出正确的值。

要传递数值,请将数值作为第二个参数添加到 emit_signal 函数中:

GDScript

C#

  1. extends Node
  2. signal my_signal(value, other_value)
  3. func _ready():
  4. emit_signal("my_signal", true, 42)
  1. public class Main : Node
  2. {
  3. [Signal]
  4. public delegate void MySignal(bool value, int other_value);
  5. public override void _Ready()
  6. {
  7. EmitSignal(nameof(MySignal), true, 42);
  8. }
  9. }

总结

Godot的许多内置节点类型提供了可用于检测事件的信号。例如,代表硬币的 Area2D 在游戏角色的物理体进入碰撞形状时发出 body_entered 信号,让您知道游戏角色何时收集了它。

在下一节 您的第一个游戏 中,您将构建一个完整的游戏,其中包括使用多种信号来连接不同的游戏组件。